feature: Shared Storage Filesystem as a First Class Feature (#9208)

This PR implements Storage filesystem as a first class feature.
https://cwiki.apache.org/confluence/display/CLOUDSTACK/Storage+Filesystem+as+a+First+Class+Feature

Documentation PR: apache/cloudstack-documentation#420

Co-authored-by: Wei Zhou <weizhou@apache.org>
This commit is contained in:
Abhisar Sinha 2024-09-05 17:22:32 +05:30 committed by GitHub
parent 72d0546d8b
commit 605534b417
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
95 changed files with 8938 additions and 567 deletions

View File

@ -30,6 +30,7 @@ import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.ha.HAConfig;
import org.apache.cloudstack.quota.QuotaTariff;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.object.Bucket;
import org.apache.cloudstack.storage.object.ObjectStore;
import org.apache.cloudstack.usage.Usage;
@ -744,6 +745,18 @@ public class EventTypes {
public static final String EVENT_QUOTA_TARIFF_DELETE = "QUOTA.TARIFF.DELETE";
public static final String EVENT_QUOTA_TARIFF_UPDATE = "QUOTA.TARIFF.UPDATE";
// SharedFS
public static final String EVENT_SHAREDFS_CREATE = "SHAREDFS.CREATE";
public static final String EVENT_SHAREDFS_START = "SHAREDFS.START";
public static final String EVENT_SHAREDFS_UPDATE = "SHAREDFS.UPDATE";
public static final String EVENT_SHAREDFS_CHANGE_SERVICE_OFFERING = "SHAREDFS.CHANGE.SERVICE.OFFERING";
public static final String EVENT_SHAREDFS_CHANGE_DISK_OFFERING = "SHAREDFS.CHANGE.DISK.OFFERING";
public static final String EVENT_SHAREDFS_STOP = "SHAREDFS.STOP";
public static final String EVENT_SHAREDFS_RESTART = "SHAREDFS.RESTART";
public static final String EVENT_SHAREDFS_DESTROY = "SHAREDFS.DESTROY";
public static final String EVENT_SHAREDFS_EXPUNGE = "SHAREDFS.EXPUNGE";
public static final String EVENT_SHAREDFS_RECOVER = "SHAREDFS.RECOVER";
static {
// TODO: need a way to force author adding event types to declare the entity details as well, with out braking
@ -1203,6 +1216,18 @@ public class EventTypes {
entityEventDetails.put(EVENT_QUOTA_TARIFF_CREATE, QuotaTariff.class);
entityEventDetails.put(EVENT_QUOTA_TARIFF_DELETE, QuotaTariff.class);
entityEventDetails.put(EVENT_QUOTA_TARIFF_UPDATE, QuotaTariff.class);
// SharedFS
entityEventDetails.put(EVENT_SHAREDFS_CREATE, SharedFS.class);
entityEventDetails.put(EVENT_SHAREDFS_START, SharedFS.class);
entityEventDetails.put(EVENT_SHAREDFS_STOP, SharedFS.class);
entityEventDetails.put(EVENT_SHAREDFS_UPDATE, SharedFS.class);
entityEventDetails.put(EVENT_SHAREDFS_CHANGE_SERVICE_OFFERING, SharedFS.class);
entityEventDetails.put(EVENT_SHAREDFS_CHANGE_DISK_OFFERING, SharedFS.class);
entityEventDetails.put(EVENT_SHAREDFS_RESTART, SharedFS.class);
entityEventDetails.put(EVENT_SHAREDFS_DESTROY, SharedFS.class);
entityEventDetails.put(EVENT_SHAREDFS_EXPUNGE, SharedFS.class);
entityEventDetails.put(EVENT_SHAREDFS_RECOVER, SharedFS.class);
}
public static boolean isNetworkEvent(String eventType) {

View File

@ -102,8 +102,12 @@ public interface VolumeApiService {
boolean deleteVolume(long volumeId, Account caller);
Volume changeDiskOfferingForVolumeInternal(Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean autoMigrateVolume, boolean shrinkOk) throws ResourceAllocationException;
Volume attachVolumeToVM(AttachVolumeCmd command);
Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId, Boolean allowAttachForSharedFS);
Volume detachVolumeViaDestroyVM(long vmId, long volumeId);
Volume detachVolumeFromVM(DetachVolumeCmd cmd);

View File

@ -85,7 +85,8 @@ public enum ApiCommandResourceType {
Bucket(org.apache.cloudstack.storage.object.Bucket.class),
QuotaTariff(org.apache.cloudstack.quota.QuotaTariff.class),
KubernetesCluster(null),
KubernetesSupportedVersion(null);
KubernetesSupportedVersion(null),
SharedFS(org.apache.cloudstack.storage.sharedfs.SharedFS.class);
private final Class<?> clazz;

View File

@ -188,6 +188,7 @@ public class ApiConstants {
public static final String EXTERNAL_UUID = "externaluuid";
public static final String FENCE = "fence";
public static final String FETCH_LATEST = "fetchlatest";
public static final String FILESYSTEM = "filesystem";
public static final String FIRSTNAME = "firstname";
public static final String FORCED = "forced";
public static final String FORCED_DESTROY_LOCAL_STORAGE = "forcedestroylocalstorage";
@ -432,6 +433,7 @@ public class ApiConstants {
public static final String SIGNATURE_VERSION = "signatureversion";
public static final String SINCE = "since";
public static final String SIZE = "size";
public static final String SIZEGB = "sizegb";
public static final String SNAPSHOT = "snapshot";
public static final String SNAPSHOT_ID = "snapshotid";
public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid";
@ -504,6 +506,7 @@ public class ApiConstants {
public static final String VIRTUAL_MACHINE_ID_IP = "vmidipmap";
public static final String VIRTUAL_MACHINE_COUNT = "virtualmachinecount";
public static final String VIRTUAL_MACHINE_TYPE = "virtualmachinetype";
public static final String VIRTUAL_MACHINE_STATE = "vmstate";
public static final String VIRTUAL_MACHINES = "virtualmachines";
public static final String USAGE_ID = "usageid";
public static final String USAGE_TYPE = "usagetype";
@ -1143,6 +1146,9 @@ public class ApiConstants {
public static final String NFS_MOUNT_OPTIONS = "nfsmountopts";
public static final String SHAREDFSVM_MIN_CPU_COUNT = "sharedfsvmmincpucount";
public static final String SHAREDFSVM_MIN_RAM_SIZE = "sharedfsvmminramsize";
public static final String PARAMETER_DESCRIPTION_ACTIVATION_RULE = "Quota tariff's activation rule. It can receive a JS script that results in either " +
"a boolean or a numeric value: if it results in a boolean value, the tariff value will be applied according to the result; if it results in a numeric value, the " +
"numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " +

View File

@ -54,6 +54,7 @@ import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse;
import org.apache.cloudstack.api.response.EventResponse;
import org.apache.cloudstack.api.response.ExtractResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.FirewallResponse;
import org.apache.cloudstack.api.response.FirewallRuleResponse;
import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse;
@ -151,6 +152,7 @@ import org.apache.cloudstack.region.PortableIp;
import org.apache.cloudstack.region.PortableIpRange;
import org.apache.cloudstack.region.Region;
import org.apache.cloudstack.secstorage.heuristics.Heuristic;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.object.ObjectStore;
import org.apache.cloudstack.usage.Usage;
@ -551,4 +553,6 @@ public interface ResponseGenerator {
ObjectStoreResponse createObjectStoreResponse(ObjectStore os);
BucketResponse createBucketResponse(Bucket bucket);
SharedFSResponse createSharedFSResponse(ResponseView view, SharedFS sharedFS);
}

View File

@ -70,6 +70,8 @@ public class ListCapabilitiesCmd extends BaseCmd {
response.setInstancesStatsUserOnly((Boolean) capabilities.get(ApiConstants.INSTANCES_STATS_USER_ONLY));
response.setInstancesDisksStatsRetentionEnabled((Boolean) capabilities.get(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_ENABLED));
response.setInstancesDisksStatsRetentionTime((Integer) capabilities.get(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME));
response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT));
response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE));
response.setObjectName("capability");
response.setResponseName(getCommandName());
this.setResponseObject(response);

View File

@ -0,0 +1,145 @@
// 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.storage.sharedfs;
import javax.inject.Inject;
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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
import com.cloud.event.EventTypes;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.user.Account;
@APICommand(name = "changeSharedFileSystemDiskOffering",
responseObject= SharedFSResponse.class,
description = "Change Disk offering of a Shared FileSystem",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ChangeSharedFSDiskOfferingCmd extends BaseAsyncCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
required = true,
entityType = SharedFSResponse.class,
description = "the ID of the shared filesystem")
private Long id;
@Parameter(name = ApiConstants.DISK_OFFERING_ID,
type = CommandType.UUID,
entityType = DiskOfferingResponse.class,
description = "the disk offering to use for the underlying storage")
private Long diskOfferingId;
@Parameter(name = ApiConstants.SIZE,
type = CommandType.LONG,
description = "the size of the shared filesystem in GiB")
private Long size;
@Parameter(name = ApiConstants.MIN_IOPS,
type = CommandType.LONG,
description = "min iops")
private Long minIops;
@Parameter(name = ApiConstants.MAX_IOPS,
type = CommandType.LONG,
description = "max iops")
private Long maxIops;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public Long getSize() {
return size;
}
public Long getDiskOfferingId() {
return diskOfferingId;
}
public Long getMinIops() {
return minIops;
}
public Long getMaxIops() {
return maxIops;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public String getEventType() {
return EventTypes.EVENT_SHAREDFS_CHANGE_DISK_OFFERING;
}
@Override
public String getEventDescription() {
return "Changing disk offering for the Shared FileSystem " + id;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public void execute() throws ResourceAllocationException {
SharedFS sharedFS = sharedFSService.changeSharedFSDiskOffering(this);
if (sharedFS != null) {
ResponseObject.ResponseView respView = getResponseView();
Account caller = CallContext.current().getCallingAccount();
if (_accountService.isRootAdmin(caller.getId())) {
respView = ResponseObject.ResponseView.Full;
}
SharedFSResponse response = _responseGenerator.createSharedFSResponse(respView, sharedFS);
response.setObjectName(SharedFS.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to change disk offering for the Shared FileSystem");
}
}
}

View File

@ -0,0 +1,147 @@
// 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.storage.sharedfs;
import javax.inject.Inject;
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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
import com.cloud.event.EventTypes;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import com.cloud.user.Account;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "changeSharedFileSystemServiceOffering",
responseObject= SharedFSResponse.class,
description = "Change Service offering of a Shared FileSystem",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ChangeSharedFSServiceOfferingCmd extends BaseAsyncCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
required = true,
entityType = SharedFSResponse.class,
description = "the ID of the shared filesystem")
private Long id;
@Parameter(name = ApiConstants.SERVICE_OFFERING_ID,
type = CommandType.UUID,
entityType = ServiceOfferingResponse.class,
required = true,
description = "the offering to use for the shared filesystem vm")
private Long serviceOfferingId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public Long getServiceOfferingId() {
return serviceOfferingId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public String getEventType() {
return EventTypes.EVENT_SHAREDFS_CHANGE_SERVICE_OFFERING;
}
@Override
public String getEventDescription() {
return "Changing service offering for the Shared FileSystem " + id;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
private String getExceptionMsg(Exception ex) {
return "Shared FileSystem restart failed with exception" + ex.getMessage();
}
@Override
public void execute() {
SharedFS sharedFS;
try {
sharedFS = sharedFSService.changeSharedFSServiceOffering(this);
} catch (ResourceUnavailableException ex) {
logger.warn("Shared FileSystem change service offering exception: ", ex);
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, getExceptionMsg(ex));
} catch (InsufficientCapacityException ex) {
logger.warn("Shared FileSystem change service offering exception: ", ex);
throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, getExceptionMsg(ex));
} catch (OperationTimedoutException ex) {
logger.warn("Shared FileSystem change service offering exception: ", ex);
throw new CloudRuntimeException("Shared FileSystem change service offering timed out due to " + ex.getMessage());
} catch (ManagementServerException ex) {
logger.warn("Shared FileSystem change service offering exception: ", ex);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
} catch (VirtualMachineMigrationException ex) {
logger.warn("Shared FileSystem change service offering exception: ", ex);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
if (sharedFS != null) {
ResponseObject.ResponseView respView = getResponseView();
Account caller = CallContext.current().getCallingAccount();
if (_accountService.isRootAdmin(caller.getId())) {
respView = ResponseObject.ResponseView.Full;
}
SharedFSResponse response = _responseGenerator.createSharedFSResponse(respView, sharedFS);
response.setObjectName(SharedFS.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to change the service offering for the Shared FileSystem");
}
}
}

View File

@ -0,0 +1,304 @@
// 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.storage.sharedfs;
import javax.inject.Inject;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.user.Account;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.NetworkResponse;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSProvider;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
@APICommand(name = "createSharedFileSystem",
responseObject= SharedFSResponse.class,
description = "Create a new Shared File System of specified size and disk offering, attached to the given network",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class CreateSharedFSCmd extends BaseAsyncCreateCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.NAME,
type = CommandType.STRING,
required = true,
description = "the name of the shared filesystem.")
private String name;
@Parameter(name = ApiConstants.ACCOUNT,
type = BaseCmd.CommandType.STRING,
description = "the account associated with the shared filesystem. Must be used with the domainId parameter.")
private String accountName;
@Parameter(name = ApiConstants.DOMAIN_ID,
type = CommandType.UUID,
entityType = DomainResponse.class,
description = "the domain ID associated with the shared filesystem. If used with the account parameter"
+ " returns the shared filesystem associated with the account for the specified domain." +
"If account is NOT provided then the shared filesystem will be assigned to the caller account and domain.")
private Long domainId;
@Parameter(name = ApiConstants.PROJECT_ID,
type = CommandType.UUID,
entityType = ProjectResponse.class,
description = "the project associated with the shared filesystem. Mutually exclusive with account parameter")
private Long projectId;
@Parameter(name = ApiConstants.DESCRIPTION,
type = CommandType.STRING,
description = "the description for the shared filesystem.")
private String description;
@Parameter(name = ApiConstants.SIZE,
type = CommandType.LONG,
description = "the size of the shared filesystem in GiB")
private Long size;
@Parameter(name = ApiConstants.ZONE_ID,
type = CommandType.UUID,
required = true,
entityType = ZoneResponse.class,
description = "the zone id.")
private Long zoneId;
@Parameter(name = ApiConstants.DISK_OFFERING_ID,
type = CommandType.UUID,
required = true,
entityType = DiskOfferingResponse.class,
description = "the disk offering to use for the underlying storage. This will define the size and other specifications like encryption and qos for the shared filesystem.")
private Long diskOfferingId;
@Parameter(name = ApiConstants.MIN_IOPS,
type = CommandType.LONG,
description = "min iops")
private Long minIops;
@Parameter(name = ApiConstants.MAX_IOPS,
type = CommandType.LONG,
description = "max iops")
private Long maxIops;
@Parameter(name = ApiConstants.SERVICE_OFFERING_ID,
type = CommandType.UUID,
required = true,
entityType = ServiceOfferingResponse.class,
description = "the service offering to use for the shared filesystem VM hosting the data. The offering should be HA enabled and the cpu count and memory size should be greater than equal to sharedfsvm.min.cpu.count and sharedfsvm.min.ram.size respectively")
private Long serviceOfferingId;
@Parameter(name = ApiConstants.FILESYSTEM,
type = CommandType.STRING,
required = true,
description = "the filesystem format (XFS / EXT4) which will be installed on the shared filesystem.")
private String fsFormat;
@Parameter(name = ApiConstants.PROVIDER,
type = CommandType.STRING,
description = "the provider to be used for the shared filesystem. The list of providers can be fetched by using the listSharedFileSystemProviders API.")
private String sharedFSProviderName;
@Parameter(name = ApiConstants.NETWORK_ID,
type = CommandType.UUID,
required = true,
entityType = NetworkResponse.class,
description = "network to attach the shared filesystem to")
private Long networkId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public String getName() {
return name;
}
public Long getProjectId() {
return projectId;
}
public Long getDomainId() {
return domainId;
}
public String getAccountName() {
return accountName;
}
public String getDescription() {
return description;
}
public Long getSize() {
return size;
}
public Long getZoneId() {
return zoneId;
}
public Long getDiskOfferingId() {
return diskOfferingId;
}
public Long getServiceOfferingId() {
return serviceOfferingId;
}
public Long getMaxIops() {
return maxIops;
}
public Long getMinIops() {
return minIops;
}
public String getFsFormat() {
return fsFormat;
}
public Long getNetworkId() {
return networkId;
}
public String getSharedFSProviderName() {
if (sharedFSProviderName != null) {
return sharedFSProviderName;
} else {
return SharedFSProvider.SharedFSProviderType.SHAREDFSVM.toString();
}
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.SharedFS;
}
@Override
public Long getApiResourceId() {
return this.getEntityId();
}
@Override
public long getEntityOwnerId() {
Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true);
if (accountId == null) {
return CallContext.current().getCallingAccount().getId();
}
return accountId;
}
@Override
public String getEventType() {
return EventTypes.EVENT_SHAREDFS_CREATE;
}
@Override
public String getEventDescription() {
return "Creating shared filesystem " + name;
}
private String getCreateExceptionMsg(Exception ex) {
return "Shared FileSystem create failed with exception" + ex.getMessage();
}
private String getStartExceptionMsg(Exception ex) {
return "Shared FileSystem start failed with exception: " + ex.getMessage();
}
public void create() {
SharedFS sharedFS;
sharedFS = sharedFSService.allocSharedFS(this);
if (sharedFS != null) {
setEntityId(sharedFS.getId());
setEntityUuid(sharedFS.getUuid());
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Shared FileSystem");
}
}
@Override
public void execute() {
SharedFS sharedFS;
try {
sharedFS = sharedFSService.deploySharedFS(this);
} catch (ResourceUnavailableException ex) {
logger.warn("Shared FileSystem start exception: ", ex);
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, getStartExceptionMsg(ex));
} catch (ConcurrentOperationException ex) {
logger.warn("Shared FileSystem start exception: ", ex);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, getStartExceptionMsg(ex));
} catch (InsufficientCapacityException ex) {
logger.warn("Shared FileSystem start exception: ", ex);
throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, getStartExceptionMsg(ex));
} catch (ResourceAllocationException ex) {
logger.warn("Shared FileSystem start exception: ", ex);
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage());
} catch (OperationTimedoutException ex) {
throw new CloudRuntimeException("Shared FileSystem start timed out due to " + ex.getMessage());
}
if (sharedFS != null) {
ResponseObject.ResponseView respView = getResponseView();
Account caller = CallContext.current().getCallingAccount();
if (_accountService.isRootAdmin(caller.getId())) {
respView = ResponseObject.ResponseView.Full;
}
SharedFSResponse response = _responseGenerator.createSharedFSResponse(respView, sharedFS);
response.setObjectName(SharedFS.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to start Shared FileSystem");
}
}
}

View File

@ -0,0 +1,116 @@
// 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.storage.sharedfs;
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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
import javax.inject.Inject;
import com.cloud.event.EventTypes;
@APICommand(name = "destroySharedFileSystem",
responseObject= SuccessResponse.class,
description = "Destroy a Shared FileSystem by id",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class DestroySharedFSCmd extends BaseAsyncCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = SharedFSResponse.class,
description = "the ID of the shared filesystem to delete")
private Long id;
@Parameter(name = ApiConstants.EXPUNGE,
type = CommandType.BOOLEAN,
description = "If true is passed, the shared filesystem is expunged immediately. False by default.")
private Boolean expunge;
@Parameter(name = ApiConstants.FORCED,
type = CommandType.BOOLEAN,
description = "If true is passed, the shared filesystem can be destroyed without stopping it first.")
private Boolean forced;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public boolean isExpunge() {
return (expunge != null) ? expunge : false;
}
public boolean isForced() {
return (forced != null) ? forced : false;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public String getEventType() {
return EventTypes.EVENT_SHAREDFS_DESTROY;
}
@Override
public String getEventDescription() {
return "Destroying Shared FileSystem " + id;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public void execute() {
Boolean result = sharedFSService.destroySharedFS(this);
if (result) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to destroy Shared FileSystem");
}
}
}

View File

@ -0,0 +1,96 @@
// 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.storage.sharedfs;
import javax.inject.Inject;
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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
import com.cloud.event.EventTypes;
@APICommand(name = "expungeSharedFileSystem",
responseObject= SuccessResponse.class,
description = "Expunge a Shared FileSystem by id",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ExpungeSharedFSCmd extends BaseAsyncCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = SharedFSResponse.class, description = "the ID of the shared filesystem to expunge")
private Long id;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public String getEventType() {
return EventTypes.EVENT_SHAREDFS_EXPUNGE;
}
@Override
public String getEventDescription() {
return "Expunging Shared FileSystem " + id;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public void execute() {
try {
sharedFSService.deleteSharedFS(id);
} catch (Exception ex) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to expunge Shared FileSystem");
} finally {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
}
}
}

View File

@ -0,0 +1,114 @@
// 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.storage.sharedfs;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListRetrieveOnlyResourceCountCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.NetworkResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
import javax.inject.Inject;
@APICommand(name = "listSharedFileSystems",
responseObject= SharedFSResponse.class,
description = "List Shared FileSystems",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ListSharedFSCmd extends BaseListRetrieveOnlyResourceCountCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = SharedFSResponse.class, description = "the ID of the shared filesystem")
private Long id;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the shared filesystem")
private String name;
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the ID of the availability zone")
private Long zoneId;
@Parameter(name = ApiConstants.NETWORK_ID, type = CommandType.UUID, entityType = NetworkResponse.class, description = "the ID of the network")
private Long networkId;
@Parameter(name = ApiConstants.DISK_OFFERING_ID, type = CommandType.UUID, entityType = DiskOfferingResponse.class, description = "the disk offering of the shared filesystem")
private Long diskOfferingId;
@Parameter(name = ApiConstants.SERVICE_OFFERING_ID, type = CommandType.UUID, entityType = ServiceOfferingResponse.class, description = "the service offering of the shared filesystem")
private Long serviceOfferingId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Long getZoneId() {
return zoneId;
}
public Long getNetworkId() {
return networkId;
}
public Long getDiskOfferingId() {
return diskOfferingId;
}
public Long getServiceOfferingId() {
return serviceOfferingId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
public long getEntityOwnerId() {
return 0;
}
@Override
public void execute() {
ListResponse<SharedFSResponse> response = sharedFSService.searchForSharedFS(getResponseView(), this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,60 @@
// 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.storage.sharedfs;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.response.SharedFSProviderResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.storage.sharedfs.SharedFSProvider;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
@APICommand(name = "listSharedFileSystemProviders",
responseObject = SharedFSProviderResponse.class,
description = "Lists all available shared filesystem providers.",
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ListSharedFSProvidersCmd extends BaseListCmd {
@Inject
public SharedFSService sharedFSService;
@Override
public void execute() {
List<SharedFSProvider> sharedFSProviders = sharedFSService.getSharedFSProviders();
final ListResponse<SharedFSProviderResponse> response = new ListResponse<>();
final List<SharedFSProviderResponse> responses = new ArrayList<>();
for (SharedFSProvider sharedFSProvider : sharedFSProviders) {
SharedFSProviderResponse sharedFSProviderResponse = new SharedFSProviderResponse();
sharedFSProviderResponse.setName(sharedFSProvider.getName());
sharedFSProviderResponse.setObjectName("sharedfilesystemprovider");
responses.add(sharedFSProviderResponse);
}
response.setResponses(responses, responses.size());
response.setResponseName(this.getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,83 @@
// 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.storage.sharedfs;
import javax.inject.Inject;
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;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
@APICommand(name = "recoverSharedFileSystem",
responseObject= SuccessResponse.class,
description = "Recover a Shared FileSystem by id",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class RecoverSharedFSCmd extends BaseCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = SharedFSResponse.class, description = "the ID of the shared filesystem to recover")
private Long id;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public void execute() {
SharedFS sharedFS = sharedFSService.recoverSharedFS(id);
if (sharedFS != null) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to recover Shared FileSystem");
}
}
}

View File

@ -0,0 +1,145 @@
// 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.storage.sharedfs;
import javax.inject.Inject;
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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.user.Account;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "restartSharedFileSystem",
responseObject= SuccessResponse.class,
description = "Restart a Shared FileSystem",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class RestartSharedFSCmd extends BaseAsyncCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
required = true,
entityType = SharedFSResponse.class,
description = "the ID of the shared filesystem")
private Long id;
@Parameter(name = ApiConstants.CLEANUP,
type = CommandType.BOOLEAN,
description = "is cleanup required")
private boolean cleanup;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public Boolean getCleanup() {
return cleanup;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public String getEventType() {
return EventTypes.EVENT_SHAREDFS_RESTART;
}
@Override
public String getEventDescription() {
return "Restarting Shared FileSystem " + id;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
private String getRestartExceptionMsg(Exception ex) {
return "Shared FileSystem restart failed with exception" + ex.getMessage();
}
@Override
public void execute() {
SharedFS sharedFS;
try {
sharedFS = sharedFSService.restartSharedFS(this.getId(), this.getCleanup());
} catch (ResourceUnavailableException ex) {
logger.warn("Shared FileSystem restart exception: ", ex);
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, getRestartExceptionMsg(ex));
} catch (ConcurrentOperationException ex) {
logger.warn("Shared FileSystem restart exception: ", ex);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, getRestartExceptionMsg(ex));
} catch (InsufficientCapacityException ex) {
logger.warn("Shared FileSystem restart exception: ", ex);
throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, getRestartExceptionMsg(ex));
} catch (ResourceAllocationException ex) {
logger.warn("Shared FileSystem restart exception: ", ex);
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage());
} catch (OperationTimedoutException ex) {
logger.warn("Shared FileSystem restart exception: ", ex);
throw new CloudRuntimeException("Shared FileSystem start timed out due to " + ex.getMessage());
}
if (sharedFS != null) {
ResponseObject.ResponseView respView = getResponseView();
Account caller = CallContext.current().getCallingAccount();
if (_accountService.isRootAdmin(caller.getId())) {
respView = ResponseObject.ResponseView.Full;
}
SharedFSResponse response = _responseGenerator.createSharedFSResponse(respView, sharedFS);
response.setObjectName(SharedFS.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to restart Shared FileSystem");
}
}
}

View File

@ -0,0 +1,135 @@
// 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.storage.sharedfs;
import javax.inject.Inject;
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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.user.Account;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "startSharedFileSystem",
responseObject= SharedFSResponse.class,
description = "Start a Shared FileSystem",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class StartSharedFSCmd extends BaseAsyncCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
required = true,
entityType = SharedFSResponse.class,
description = "the ID of the shared filesystem")
private Long id;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public String getEventDescription() {
return "Starting Shared FileSystem " + id;
}
@Override
public String getEventType() {
return EventTypes.EVENT_SHAREDFS_START;
}
private String getStartExceptionMsg(Exception ex) {
return "Shared FileSystem start failed with exception: " + ex.getMessage();
}
@Override
public void execute() {
SharedFS sharedFS;
try {
sharedFS = sharedFSService.startSharedFS(this.getId());
} catch (ResourceUnavailableException ex) {
logger.warn("Shared FileSystem start exception: ", ex);
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, getStartExceptionMsg(ex));
} catch (ConcurrentOperationException ex) {
logger.warn("Shared FileSystem start exception: ", ex);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, getStartExceptionMsg(ex));
} catch (InsufficientCapacityException ex) {
logger.warn("Shared FileSystem start exception: ", ex);
throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, getStartExceptionMsg(ex));
} catch (ResourceAllocationException ex) {
logger.warn("Shared FileSystem start exception: ", ex);
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage());
} catch (OperationTimedoutException ex) {
logger.warn("Shared FileSystem start exception: ", ex);
throw new CloudRuntimeException("Shared FileSystem start timed out due to " + ex.getMessage());
}
if (sharedFS != null) {
ResponseObject.ResponseView respView = getResponseView();
Account caller = CallContext.current().getCallingAccount();
if (_accountService.isRootAdmin(caller.getId())) {
respView = ResponseObject.ResponseView.Full;
}
SharedFSResponse response = _responseGenerator.createSharedFSResponse(respView, sharedFS);
response.setObjectName(SharedFS.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to start Shared FileSystem");
}
}
}

View File

@ -0,0 +1,115 @@
// 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.storage.sharedfs;
import javax.inject.Inject;
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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
@APICommand(name = "stopSharedFileSystem",
responseObject= SharedFSResponse.class,
description = "Stop a Shared FileSystem",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class StopSharedFSCmd extends BaseAsyncCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
required = true,
entityType = SharedFSResponse.class,
description = "the ID of the shared filesystem")
private Long id;
@Parameter(name = ApiConstants.FORCED,
type = CommandType.BOOLEAN,
description = "Force stop the shared filesystem.")
private Boolean forced;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public boolean isForced() {
return (forced != null) ? forced : false;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public String getEventType() {
return EventTypes.EVENT_SHAREDFS_STOP;
}
@Override
public String getEventDescription() {
return "Stopping Shared FileSystem " + id;
}
@Override
public void execute() {
SharedFS sharedFS = sharedFSService.stopSharedFS(this.getId(), this.isForced());
if (sharedFS != null) {
ResponseObject.ResponseView respView = getResponseView();
Account caller = CallContext.current().getCallingAccount();
if (_accountService.isRootAdmin(caller.getId())) {
respView = ResponseObject.ResponseView.Full;
}
SharedFSResponse response = _responseGenerator.createSharedFSResponse(respView, sharedFS);
response.setObjectName(SharedFS.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to stop Shared FileSystem");
}
}
}

View File

@ -0,0 +1,113 @@
// 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.storage.sharedfs;
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;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSService;
import javax.inject.Inject;
import com.cloud.user.Account;
@APICommand(name = "updateSharedFileSystem",
responseObject= SharedFSResponse.class,
description = "Update a Shared FileSystem",
responseView = ResponseObject.ResponseView.Restricted,
entityType = SharedFS.class,
requestHasSensitiveInfo = false,
since = "4.20.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class UpdateSharedFSCmd extends BaseCmd implements UserCmd {
@Inject
SharedFSService sharedFSService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
required = true,
entityType = SharedFSResponse.class,
description = "the ID of the shared filesystem")
private Long id;
@Parameter(name = ApiConstants.NAME,
type = CommandType.STRING,
description = "the name of the shared filesystem.")
private String name;
@Parameter(name = ApiConstants.DESCRIPTION,
type = CommandType.STRING,
description = "the description for the shared filesystem.")
private String description;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public void execute() {
SharedFS sharedFS = sharedFSService.updateSharedFS(this);
if (sharedFS != null) {
ResponseObject.ResponseView respView = getResponseView();
Account caller = CallContext.current().getCallingAccount();
if (_accountService.isRootAdmin(caller.getId())) {
respView = ResponseObject.ResponseView.Full;
}
SharedFSResponse response = _responseGenerator.createSharedFSResponse(respView, sharedFS);
response.setObjectName(SharedFS.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update Shared FileSystem");
}
}
}

View File

@ -128,6 +128,14 @@ public class CapabilitiesResponse extends BaseResponse {
@Param(description = "the retention time for Instances disks stats", since = "4.18.0")
private Integer instancesDisksStatsRetentionTime;
@SerializedName(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT)
@Param(description = "the min CPU count for the service offering used by the shared filesystem VM", since = "4.20.0")
private Integer sharedFsVmMinCpuCount;
@SerializedName(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE)
@Param(description = "the min Ram size for the service offering used by the shared filesystem VM", since = "4.20.0")
private Integer sharedFsVmMinRamSize;
public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
this.securityGroupsEnabled = securityGroupsEnabled;
}
@ -231,4 +239,12 @@ public class CapabilitiesResponse extends BaseResponse {
public void setCustomHypervisorDisplayName(String customHypervisorDisplayName) {
this.customHypervisorDisplayName = customHypervisorDisplayName;
}
public void setSharedFsVmMinCpuCount(Integer sharedFsVmMinCpuCount) {
this.sharedFsVmMinCpuCount = sharedFsVmMinCpuCount;
}
public void setSharedFsVmMinRamSize(Integer sharedFsVmMinRamSize) {
this.sharedFsVmMinRamSize = sharedFsVmMinRamSize;
}
}

View File

@ -0,0 +1,38 @@
// 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.response;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
public class SharedFSProviderResponse extends BaseResponse {
@SerializedName(ApiConstants.NAME)
@Param(description = "the name of the shared filesystem provider")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,369 @@
// 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.response;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponseWithTagInformation;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.List;
@EntityReference(value = SharedFS.class)
public class SharedFSResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "ID of the shared filesystem")
private String id;
@SerializedName(ApiConstants.NAME)
@Param(description = "name of the shared filesystem")
private String name;
@SerializedName(ApiConstants.DESCRIPTION)
@Param(description = "description of the shared filesystem")
private String description;
@SerializedName(ApiConstants.ZONE_ID)
@Param(description = "ID of the availability zone")
private String zoneId;
@SerializedName(ApiConstants.ZONE_NAME)
@Param(description = "Name of the availability zone")
private String zoneName;
@SerializedName(ApiConstants.VIRTUAL_MACHINE_ID)
@Param(description = "ID of the storage fs vm")
private String virtualMachineId;
@SerializedName(ApiConstants.VIRTUAL_MACHINE_STATE)
@Param(description = "ID of the storage fs vm")
private String virtualMachineState;
@SerializedName(ApiConstants.VOLUME_NAME)
@Param(description = "name of the storage fs data volume")
private String volumeName;
@SerializedName(ApiConstants.VOLUME_ID)
@Param(description = "ID of the storage fs data volume")
private String volumeId;
@SerializedName(ApiConstants.STORAGE)
@Param(description = "name of the storage pool hosting the data volume")
private String storagePoolName;
@SerializedName(ApiConstants.STORAGE_ID)
@Param(description = "ID of the storage pool hosting the data volume")
private String storagePoolId;
@SerializedName(ApiConstants.SIZE)
@Param(description = "size of the shared filesystem")
private Long size;
@SerializedName(ApiConstants.SIZEGB)
@Param(description = "size of the shared filesystem in GiB")
private String sizeGB;
@SerializedName(ApiConstants.DISK_OFFERING_ID)
@Param(description = "disk offering ID for the shared filesystem")
private String diskOfferingId;
@SerializedName("diskofferingname")
@Param(description = "disk offering for the shared filesystem")
private String diskOfferingName;
@SerializedName("iscustomdiskoffering")
@Param(description = "disk offering for the shared filesystem has custom size")
private Boolean isCustomDiskOffering;
@SerializedName("diskofferingdisplaytext")
@Param(description = "disk offering display text for the shared filesystem")
private String diskOfferingDisplayText;
@SerializedName(ApiConstants.SERVICE_OFFERING_ID)
@Param(description = "service offering ID for the shared filesystem")
private String serviceOfferingId;
@SerializedName("serviceofferingname")
@Param(description = "service offering for the shared filesystem")
private String serviceOfferingName;
@SerializedName(ApiConstants.NETWORK_ID)
@Param(description = "Network ID of the shared filesystem")
private String networkId;
@SerializedName(ApiConstants.NETWORK_NAME)
@Param(description = "Network name of the shared filesystem")
private String networkName;
@SerializedName(ApiConstants.NIC)
@Param(description = "the list of nics associated with the shared filesystem", responseObject = NicResponse.class)
private List<NicResponse> nics;
@SerializedName(ApiConstants.PATH)
@Param(description = "path to mount the shared filesystem")
private String path;
@SerializedName(ApiConstants.STATE)
@Param(description = "the state of the shared filesystem")
private String state;
@SerializedName(ApiConstants.PROVIDER)
@Param(description = "the shared filesystem provider")
private String provider;
@SerializedName(ApiConstants.FILESYSTEM)
@Param(description = "the filesystem format")
private String filesystem;
@SerializedName(ApiConstants.ACCOUNT)
@Param(description = "the account associated with the shared filesystem")
private String accountName;
@SerializedName(ApiConstants.PROJECT_ID)
@Param(description = "the project ID of the shared filesystem")
private String projectId;
@SerializedName(ApiConstants.PROJECT)
@Param(description = "the project name of the shared filesystem")
private String projectName;
@SerializedName(ApiConstants.DOMAIN_ID)
@Param(description = "the ID of the domain associated with the shared filesystem")
private String domainId;
@SerializedName(ApiConstants.DOMAIN)
@Param(description = "the domain associated with the shared filesystem")
private String domainName;
@SerializedName(ApiConstants.DOMAIN_PATH)
@Param(description = "path of the domain to which the shared filesystem")
private String domainPath;
@SerializedName(ApiConstants.PROVISIONINGTYPE)
@Param(description = "provisioning type used in the shared filesystem")
private String provisioningType;
@SerializedName(ApiConstants.DISK_IO_READ)
@Param(description = "the read (IO) of disk on the shared filesystem")
private Long diskIORead;
@SerializedName(ApiConstants.DISK_IO_WRITE)
@Param(description = "the write (IO) of disk on the shared filesystem")
private Long diskIOWrite;
@SerializedName(ApiConstants.DISK_KBS_READ)
@Param(description = "the shared filesystem's disk read in KiB")
private Long diskKbsRead;
@SerializedName(ApiConstants.DISK_KBS_WRITE)
@Param(description = "the shared filesystem's disk write in KiB")
private Long diskKbsWrite;
@SerializedName(ApiConstants.VIRTUAL_SIZE)
@Param(description = "the bytes allocated")
private Long virtualSize;
@SerializedName(ApiConstants.PHYSICAL_SIZE)
@Param(description = "the bytes actually consumed on disk")
private Long physicalSize;
@SerializedName(ApiConstants.UTILIZATION)
@Param(description = "the disk utilization")
private String utilization;
@Override
public void setAccountName(String accountName) {
this.accountName = accountName;
}
@Override
public void setProjectId(String projectId) {
this.projectId = projectId;
}
@Override
public void setProjectName(String projectName) {
this.projectName = projectName;
}
@Override
public void setDomainId(String domainId) {
this.domainId = domainId;
}
@Override
public void setDomainName(String domainName) {
this.domainName = domainName;
}
@Override
public void setDomainPath(String domainPath) {
this.domainPath = domainPath;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setZoneId(String zoneId) {
this.zoneId = zoneId;
}
public void setZoneName(String zoneName) {
this.zoneName = zoneName;
}
public void setVirtualMachineId(String virtualMachineId) {
this.virtualMachineId = virtualMachineId;
}
public void setState(String state) {
this.state = state;
}
public void setVolumeId(String volumeId) {
this.volumeId = volumeId;
}
public void setNetworkId(String networkId) {
this.networkId = networkId;
}
public void setNetworkName(String networkName) {
this.networkName = networkName;
}
public List<NicResponse> getNics() {
return nics;
}
public void addNic(NicResponse nic) {
if (this.nics == null) {
this.nics = new ArrayList<>();
}
this.nics.add(nic);
}
public void setSize(Long size) {
this.size = size;
}
public void setDescription(String description) {
this.description = description;
}
public void setPath(String path) {
this.path = path;
}
public void setVolumeName(String volumeName) {
this.volumeName = volumeName;
}
public void setStoragePoolName(String storagePoolName) {
this.storagePoolName = storagePoolName;
}
public void setStoragePoolId(String storagePoolId) {
this.storagePoolId = storagePoolId;
}
public void setProvider(String provider) {
this.provider = provider;
}
public void setFilesystem(String filesystem) {
this.filesystem = filesystem;
}
public void setSizeGB(Long size) {
if (size != null) {
this.sizeGB = String.format("%.2f GiB", size / (1024.0 * 1024.0 * 1024.0));
}
}
public void setDiskOfferingId(String diskOfferingId) {
this.diskOfferingId = diskOfferingId;
}
public void setDiskOfferingName(String diskOfferingName) {
this.diskOfferingName = diskOfferingName;
}
public void setDiskOfferingDisplayText(String diskOfferingDisplayText) {
this.diskOfferingDisplayText = diskOfferingDisplayText;
}
public void setServiceOfferingId(String serviceOfferingId) {
this.serviceOfferingId = serviceOfferingId;
}
public void setServiceOfferingName(String serviceOfferingName) {
this.serviceOfferingName = serviceOfferingName;
}
public void setProvisioningType(String provisioningType) {
this.provisioningType = provisioningType;
}
public void setDiskIORead(Long diskIORead) {
this.diskIORead = diskIORead;
}
public void setDiskIOWrite(Long diskIOWrite) {
this.diskIOWrite = diskIOWrite;
}
public void setDiskKbsRead(Long diskKbsRead) {
this.diskKbsRead = diskKbsRead;
}
public void setDiskKbsWrite(Long diskKbsWrite) {
this.diskKbsWrite = diskKbsWrite;
}
public void setVirtualSize(Long virtualSize) {
this.virtualSize = virtualSize;
}
public void setPhysicalSize(Long physicalSize) {
this.physicalSize = physicalSize;
}
public void setUtilization(String utilization) {
this.utilization = utilization;
}
public void setIsCustomDiskOffering(Boolean isCustomDiskOffering) {
this.isCustomDiskOffering = isCustomDiskOffering;
}
public void setVirtualMachineState(String virtualMachineState) {
this.virtualMachineState = virtualMachineState;
}
}

View File

@ -388,6 +388,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
@Param(description = "VNF details", since = "4.19.0")
private Map<String, String> vnfDetails;
@SerializedName((ApiConstants.VM_TYPE))
@Param(description = "User VM type", since = "4.20.0")
private String vmType;
public UserVmResponse() {
securityGroupList = new LinkedHashSet<>();
nics = new TreeSet<>(Comparator.comparingInt(x -> Integer.parseInt(x.getDeviceId())));
@ -1142,6 +1146,14 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
this.vnfDetails.put(key,value);
}
public void setVmType(String vmType) {
this.vmType = vmType;
}
public String getVmType() {
return vmType;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}

View File

@ -93,7 +93,7 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
@Param(description = "display name of the virtual machine")
private String virtualMachineDisplayName;
@SerializedName("vmstate")
@SerializedName(ApiConstants.VIRTUAL_MACHINE_STATE)
@Param(description = "state of the virtual machine")
private String virtualMachineState;
@ -262,11 +262,11 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
private boolean supportsStorageSnapshot;
@SerializedName(ApiConstants.PHYSICAL_SIZE)
@Param(description = "the bytes allocated")
@Param(description = "the bytes actually consumed on disk")
private Long physicalsize;
@SerializedName(ApiConstants.VIRTUAL_SIZE)
@Param(description = "the bytes actually consumed on disk")
@Param(description = "the bytes allocated")
private Long virtualsize;
@SerializedName(ApiConstants.UTILIZATION)

View File

@ -0,0 +1,189 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.utils.fsm.StateObject;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.framework.config.ConfigKey;
import java.util.Date;
public interface SharedFS extends ControlledEntity, Identity, InternalIdentity, StateObject<SharedFS.State> {
static final ConfigKey<Boolean> SharedFSFeatureEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class,
"sharedfs.feature.enabled",
"true",
" Indicates whether the Shared FileSystem feature is enabled or not. Management server restart needed on change",
false);
ConfigKey<Integer> SharedFSCleanupInterval = new ConfigKey<>(Integer.class,
"sharedfs.cleanup.interval",
"Advanced",
"14400",
"The interval (in seconds) to wait before running the shared filesystem cleanup thread.",
false,
ConfigKey.Scope.Global,
null,
SharedFSFeatureEnabled.key());
ConfigKey<Integer> SharedFSCleanupDelay = new ConfigKey<>(Integer.class,
"sharedfs.cleanup.delay",
"Advanced",
"86400",
"Determines how long (in seconds) to wait before actually expunging destroyed shared filesystem.",
false,
ConfigKey.Scope.Global,
null,
SharedFSFeatureEnabled.key());
ConfigKey<Integer> SharedFSExpungeWorkers = new ConfigKey<>(Integer.class,
"sharedfs.expunge.workers",
"Advanced",
"2",
"Determines how many threads are created to do the work of expunging destroyed shared filesystem.",
false,
ConfigKey.Scope.Global,
null,
SharedFSFeatureEnabled.key());
String SharedFSVmNamePrefix = "fsvm";
String SharedFSPath = "/export";
enum FileSystemType {
EXT4, XFS
}
enum Protocol {
NFS
}
enum State {
Allocated(false, "The shared filesystem is allocated in db but hasn't been created or started yet."),
Ready(false, "The shared filesystem is ready to use."),
Stopping(true, "The shared filesystem is being stopped"),
Stopped(false, "The shared filesystem is in stopped state. It can not be used but the data is still there."),
Starting(true, "The shared filesystem is being started."),
Destroyed(false, "The shared filesystem is destroyed."),
Expunging(true, "The shared filesystem is being expunged."),
Expunged(false, "The shared filesystem has been expunged."),
Error(false, "The shared filesystem is in error state.");
boolean _transitional;
String _description;
/**
* SharedFS State
*
* @param transitional true for transition/non-final state, otherwise false
* @param description description of the state
*/
State(boolean transitional, String description) {
_transitional = transitional;
_description = description;
}
public boolean isTransitional() {
return _transitional;
}
public String getDescription() {
return _description;
}
private final static StateMachine2<State, Event, SharedFS> s_fsm = new StateMachine2<State, Event, SharedFS>();
public static StateMachine2<SharedFS.State, SharedFS.Event, SharedFS> getStateMachine() {
return s_fsm;
}
static {
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Allocated, Event.OperationFailed, Error, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Allocated, Event.OperationSucceeded, Ready, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Error, Event.DestroyRequested, Destroyed, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Stopped, Event.StartRequested, Starting, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Starting, Event.OperationSucceeded, Ready, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Starting, Event.OperationFailed, Stopped, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Ready, Event.StopRequested, Stopping, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Stopping, Event.OperationSucceeded, Stopped, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Stopping, Event.OperationFailed, Ready, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Stopped, Event.DestroyRequested, Destroyed, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Destroyed, Event.RecoveryRequested, Stopped, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Destroyed, Event.ExpungeOperation, Expunging, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Error, Event.ExpungeOperation, Expunging, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Expunging, Event.ExpungeOperation, Expunging, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Expunging, Event.OperationSucceeded, Expunged, null));
}
}
enum Event {
StopRequested,
StartRequested,
DestroyRequested,
OperationSucceeded,
OperationFailed,
ExpungeOperation,
RecoveryRequested,
}
static String getSharedFSPath() {
return SharedFSPath;
}
long getId();
String getName();
void setName(String name);
String getUuid();
String getDescription();
void setDescription(String description);
Long getDataCenterId();
State getState();
String getFsProviderName();
Protocol getProtocol();
Long getVolumeId();
void setVolumeId(Long volumeId);
Long getVmId();
void setVmId(Long vmId);
FileSystemType getFsType();
Long getServiceOfferingId();
void setServiceOfferingId(Long serviceOfferingId);
Date getUpdated();
public long getUpdatedCount();
public void incrUpdatedCount();
}

View File

@ -0,0 +1,43 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs;
import com.cloud.dc.DataCenter;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import com.cloud.utils.Pair;
public interface SharedFSLifeCycle {
void checkPrerequisites(DataCenter zone, Long serviceOfferingId);
Pair<Long, Long> deploySharedFS(SharedFS sharedFS, Long networkId, Long diskOfferingId, Long size, Long minIops, Long maxIops) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, OperationTimedoutException;
void startSharedFS(SharedFS sharedFS) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException;
boolean stopSharedFS(SharedFS sharedFS, Boolean forced);
boolean deleteSharedFS(SharedFS sharedFS);
boolean reDeploySharedFS(SharedFS sharedFS) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, OperationTimedoutException;
boolean changeSharedFSServiceOffering(SharedFS sharedFS, Long serviceOfferingId) throws ManagementServerException, ResourceUnavailableException, VirtualMachineMigrationException;
}

View File

@ -0,0 +1,30 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs;
import com.cloud.utils.component.Adapter;
public interface SharedFSProvider extends Adapter {
enum SharedFSProviderType {
SHAREDFSVM
}
void configure();
SharedFSLifeCycle getSharedFSLifeCycle();
}

View File

@ -0,0 +1,72 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs;
import java.util.List;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSDiskOfferingCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSServiceOfferingCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.CreateSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.DestroySharedFSCmd;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.UpdateSharedFSCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.ListResponse;
public interface SharedFSService {
List<SharedFSProvider> getSharedFSProviders();
boolean stateTransitTo(SharedFS sharedFS, SharedFS.Event event);
void setSharedFSProviders(List<SharedFSProvider> sharedFSProviders);
SharedFSProvider getSharedFSProvider(String sharedFSProviderName);
SharedFS allocSharedFS(CreateSharedFSCmd cmd);
SharedFS deploySharedFS(CreateSharedFSCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, OperationTimedoutException;
SharedFS startSharedFS(Long sharedFSId) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException;
SharedFS stopSharedFS(Long sharedFSId, Boolean forced);
SharedFS restartSharedFS(Long sharedFSId, boolean cleanup) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException;
ListResponse<SharedFSResponse> searchForSharedFS(ResponseObject.ResponseView respView, ListSharedFSCmd cmd);
SharedFS updateSharedFS(UpdateSharedFSCmd cmd);
SharedFS changeSharedFSDiskOffering(ChangeSharedFSDiskOfferingCmd cmd) throws ResourceAllocationException;
SharedFS changeSharedFSServiceOffering(ChangeSharedFSServiceOfferingCmd cmd) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ManagementServerException, VirtualMachineMigrationException;
Boolean destroySharedFS(DestroySharedFSCmd cmd);
SharedFS recoverSharedFS(Long sharedFSId);
void deleteSharedFS(Long sharedFSId);
}

View File

@ -653,6 +653,11 @@
<artifactId>cloud-plugin-storage-object-simulator</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-sharedfs-provider-storagevm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-usage</artifactId>

View File

@ -363,4 +363,7 @@
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
<property name="excludeKey" value="event.buses.exclude" />
</bean>
<bean id="sharedFSProvidersRegistry" class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
</bean>
</beans>

View File

@ -77,6 +77,10 @@
value="org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy" />
</bean>
<bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
<property name="registry" ref="sharedFSProvidersRegistry" />
<property name="typeClass"
value="org.apache.cloudstack.storage.sharedfs.SharedFSProvider" />
</bean>
</beans>

View File

@ -0,0 +1,238 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.storage.sharedfs;
import java.util.Date;
import java.util.UUID;
import com.cloud.utils.db.GenericDao;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name = "shared_filesystem")
public class SharedFSVO implements SharedFS {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "name")
private String name;
@Column(name = "uuid")
private String uuid;
@Column(name = "description")
private String description;
@Column(name = "domain_id")
private long domainId;
@Column(name = "account_id")
private long accountId;
@Column(name = "data_center_id")
private long dataCenterId;
@Column(name = "state")
@Enumerated(value = EnumType.STRING)
private State state;
@Column(name = "fs_provider_name")
private String fsProviderName;
@Column(name = "protocol")
@Enumerated(value = EnumType.STRING)
private Protocol protocol;
@Column(name = "volume_id")
private Long volumeId;
@Column(name = "vm_id")
private Long vmId;
@Column(name = "fs_type")
@Enumerated(value = EnumType.STRING)
private FileSystemType fsType;
@Column(name = "service_offering_id")
private Long serviceOfferingId;
@Column(name = "updated")
@Temporal(value = TemporalType.TIMESTAMP)
Date updated;
@Column(name = "update_count", updatable = true, nullable = false)
protected long updatedCount; // This field should be updated everytime the
// state is updated. There's no set method in
// the vo object because it is done with in the
// dao code.
@Column(name = GenericDao.CREATED_COLUMN)
protected Date created;
@Column(name = GenericDao.REMOVED_COLUMN)
protected Date removed;
public SharedFSVO() {
}
public SharedFSVO(String name, String description, long domainId, long accountId, long dataCenterId,
String fsProviderName, Protocol protocol, FileSystemType fsType, Long serviceOfferingId) {
this.name = name;
this.description = description;
this.domainId = domainId;
this.accountId = accountId;
this.dataCenterId = dataCenterId;
this.fsProviderName = fsProviderName;
this.protocol = protocol;
this.state = State.Allocated;
this.fsType = fsType;
this.serviceOfferingId = serviceOfferingId;
this.uuid = UUID.randomUUID().toString();
}
@Override
public Class<?> getEntityType() {
return SharedFS.class;
}
@Override
public long getId() {
return id;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public String getDescription() {
return description;
}
@Override
public void setDescription(String description) {
this.description = description;
}
@Override
public long getDomainId() {
return domainId;
}
@Override
public long getAccountId() {
return accountId;
}
@Override
public Long getDataCenterId() {
return dataCenterId;
}
@Override
public State getState() {
return state;
}
@Override
public String getFsProviderName() {
return fsProviderName;
}
@Override
public Protocol getProtocol() {
return protocol;
}
@Override
public Long getVolumeId() {
return volumeId;
}
@Override
public void setVolumeId(Long volumeId) {
this.volumeId = volumeId;
}
@Override
public Long getVmId() {
return vmId;
}
@Override
public void setVmId(Long vmId) {
this.vmId = vmId;
}
@Override
public FileSystemType getFsType() {
return fsType;
}
@Override
public Long getServiceOfferingId() {
return serviceOfferingId;
}
@Override
public void setServiceOfferingId(Long serviceOfferingId) {
this.serviceOfferingId = serviceOfferingId;
}
@Override
public Date getUpdated() {
return updated;
}
@Override
public long getUpdatedCount() {
return updatedCount;
}
@Override
public void incrUpdatedCount() {
updatedCount++;
}
}

View File

@ -0,0 +1,32 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs.dao;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSVO;
import com.cloud.utils.db.GenericDao;
import com.cloud.utils.fsm.StateDao;
import java.util.Date;
import java.util.List;
public interface SharedFSDao extends GenericDao<SharedFSVO, Long>, StateDao<SharedFS.State, SharedFS.Event, SharedFS> {
List<SharedFSVO> listSharedFSToBeDestroyed(Date date);
SharedFSVO findSharedFSByNameAccountDomain(String name, Long accountId, Long domainId);
}

View File

@ -0,0 +1,117 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs.dao;
import com.cloud.network.dao.NetworkDao;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.UpdateBuilder;
import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMNetworkMapDao;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSVO;
import javax.inject.Inject;
import java.util.Date;
import java.util.List;
public class SharedFSDaoImpl extends GenericDaoBase<SharedFSVO, Long> implements SharedFSDao {
@Inject
VMNetworkMapDao vmNetworkMapDao;
@Inject
NetworkDao networkDao;
protected final SearchBuilder<SharedFSVO> StateUpdateCountSearch;
protected final SearchBuilder<SharedFSVO> DestroyedByTimeSearch;
protected final SearchBuilder<SharedFSVO> NameAccountDomainSearch;
public SharedFSDaoImpl() {
StateUpdateCountSearch = createSearchBuilder();
StateUpdateCountSearch.and("id", StateUpdateCountSearch.entity().getId(), SearchCriteria.Op.EQ);
StateUpdateCountSearch.and("state", StateUpdateCountSearch.entity().getState(), SearchCriteria.Op.EQ);
StateUpdateCountSearch.and("updatedCount", StateUpdateCountSearch.entity().getUpdatedCount(), SearchCriteria.Op.EQ);
StateUpdateCountSearch.done();
DestroyedByTimeSearch = createSearchBuilder();
DestroyedByTimeSearch.and("state", DestroyedByTimeSearch.entity().getState(), SearchCriteria.Op.IN);
DestroyedByTimeSearch.and("accountId", DestroyedByTimeSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
DestroyedByTimeSearch.done();
NameAccountDomainSearch = createSearchBuilder();
NameAccountDomainSearch.and("name", NameAccountDomainSearch.entity().getName(), SearchCriteria.Op.EQ);
NameAccountDomainSearch.and("accountId", NameAccountDomainSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
NameAccountDomainSearch.and("domainId", NameAccountDomainSearch.entity().getDomainId(), SearchCriteria.Op.EQ);
NameAccountDomainSearch.done();
}
@Override
public boolean updateState(SharedFS.State currentState, SharedFS.Event event, SharedFS.State nextState, SharedFS vo, Object data) {
Long oldUpdated = vo.getUpdatedCount();
Date oldUpdatedTime = vo.getUpdated();
SearchCriteria<SharedFSVO> sc = StateUpdateCountSearch.create();
sc.setParameters("id", vo.getId());
sc.setParameters("state", currentState);
sc.setParameters("updatedCount", vo.getUpdatedCount());
vo.incrUpdatedCount();
UpdateBuilder builder = getUpdateBuilder(vo);
builder.set(vo, "state", nextState);
builder.set(vo, "updated", new Date());
int rows = update((SharedFSVO) vo, sc);
if (rows == 0 && logger.isDebugEnabled()) {
SharedFSVO dbSharedFS = findByIdIncludingRemoved(vo.getId());
if (dbSharedFS != null) {
StringBuilder str = new StringBuilder("Unable to update ").append(vo.toString());
str.append(": DB Data={id=").append(dbSharedFS.getId()).append("; state=").append(dbSharedFS.getState()).append("; updatecount=").append(dbSharedFS.getUpdatedCount()).append(";updatedTime=")
.append(dbSharedFS.getUpdated());
str.append(": New Data={id=").append(vo.getId()).append("; state=").append(nextState).append("; event=").append(event).append("; updatecount=").append(vo.getUpdatedCount())
.append("; updatedTime=").append(vo.getUpdated());
str.append(": stale Data={id=").append(vo.getId()).append("; state=").append(currentState).append("; event=").append(event).append("; updatecount=").append(oldUpdated)
.append("; updatedTime=").append(oldUpdatedTime);
} else {
logger.debug("Unable to update sharedfs: id=" + vo.getId() + ", as it is not present in the database anymore");
}
}
return rows > 0;
}
@Override
public List<SharedFSVO> listSharedFSToBeDestroyed(Date date) {
SearchCriteria<SharedFSVO> sc = DestroyedByTimeSearch.create();
sc.setParameters("state", SharedFS.State.Destroyed, SharedFS.State.Expunging, SharedFS.State.Error);
sc.setParameters("updateTime", date);
return listBy(sc);
}
@Override
public SharedFSVO findSharedFSByNameAccountDomain(String name, Long accountId, Long domainId) {
SearchCriteria<SharedFSVO> sc = NameAccountDomainSearch.create();
sc.setParameters("name", name);
sc.setParameters("accountId", accountId);
sc.setParameters("domainId", domainId);
return findOneBy(sc);
}
}

View File

@ -289,4 +289,6 @@
<bean id="ClusterDrsPlanDetailsDaoImpl" class="org.apache.cloudstack.cluster.dao.ClusterDrsPlanMigrationDaoImpl" />
<bean id="objectStoreDetailsDaoImpl" class="org.apache.cloudstack.storage.datastore.db.ObjectStoreDetailsDaoImpl" />
<bean id="bucketStatisticsDaoImpl" class="com.cloud.usage.dao.BucketStatisticsDaoImpl" />
<bean id="SharedFSDaoImpl" class="org.apache.cloudstack.storage.sharedfs.dao.SharedFSDaoImpl" />
<bean id="SharedFSJoinDaoImpl" class="org.apache.cloudstack.storage.sharedfs.query.dao.SharedFSJoinDaoImpl" />
</beans>

View File

@ -158,6 +158,229 @@ WHERE
name IN ("quota.usage.smtp.useStartTLS", "quota.usage.smtp.useAuth", "alert.smtp.useAuth", "project.smtp.useAuth")
AND value NOT IN ("true", "y", "t", "1", "on", "yes");
CREATE TABLE `cloud`.`shared_filesystem`(
`id` bigint unsigned NOT NULL auto_increment COMMENT 'ID',
`uuid` varchar(40) COMMENT 'UUID',
`name` varchar(255) NOT NULL COMMENT 'Name of the shared filesystem',
`description` varchar(1024) COMMENT 'Description',
`domain_id` bigint unsigned NOT NULL COMMENT 'Domain ID',
`account_id` bigint unsigned NOT NULL COMMENT 'Account ID',
`data_center_id` bigint unsigned NOT NULL COMMENT 'Data center ID',
`state` varchar(12) NOT NULL COMMENT 'State of the shared filesystem in the FSM',
`fs_provider_name` varchar(255) COMMENT 'Name of the shared filesystem provider',
`protocol` varchar(10) COMMENT 'Protocol supported by the shared filesystem',
`volume_id` bigint unsigned COMMENT 'Volume which the shared filesystem is using as storage',
`vm_id` bigint unsigned COMMENT 'vm on which the shared filesystem is hosted',
`fs_type` varchar(10) NOT NULL COMMENT 'The filesystem format to be used for the shared filesystem',
`service_offering_id` bigint unsigned COMMENT 'Service offering for the vm',
`update_count` bigint unsigned COMMENT 'Update count for state change',
`updated` datetime COMMENT 'date updated',
`created` datetime NOT NULL COMMENT 'date created',
`removed` datetime COMMENT 'date removed if not null',
PRIMARY KEY (`id`),
CONSTRAINT `uc_shared_filesystem__uuid` UNIQUE (`uuid`),
INDEX `i_shared_filesystem__account_id`(`account_id`),
INDEX `i_shared_filesystem__domain_id`(`domain_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP VIEW IF EXISTS `cloud`.`user_vm_view`;
CREATE VIEW `user_vm_view` AS
SELECT
`vm_instance`.`id` AS `id`,
`vm_instance`.`name` AS `name`,
`user_vm`.`display_name` AS `display_name`,
`user_vm`.`user_data` AS `user_data`,
`user_vm`.`user_vm_type` AS `user_vm_type`,
`account`.`id` AS `account_id`,
`account`.`uuid` AS `account_uuid`,
`account`.`account_name` AS `account_name`,
`account`.`type` AS `account_type`,
`domain`.`id` AS `domain_id`,
`domain`.`uuid` AS `domain_uuid`,
`domain`.`name` AS `domain_name`,
`domain`.`path` AS `domain_path`,
`projects`.`id` AS `project_id`,
`projects`.`uuid` AS `project_uuid`,
`projects`.`name` AS `project_name`,
`instance_group`.`id` AS `instance_group_id`,
`instance_group`.`uuid` AS `instance_group_uuid`,
`instance_group`.`name` AS `instance_group_name`,
`vm_instance`.`uuid` AS `uuid`,
`vm_instance`.`user_id` AS `user_id`,
`vm_instance`.`last_host_id` AS `last_host_id`,
`vm_instance`.`vm_type` AS `type`,
`vm_instance`.`limit_cpu_use` AS `limit_cpu_use`,
`vm_instance`.`created` AS `created`,
`vm_instance`.`state` AS `state`,
`vm_instance`.`update_time` AS `update_time`,
`vm_instance`.`removed` AS `removed`,
`vm_instance`.`ha_enabled` AS `ha_enabled`,
`vm_instance`.`hypervisor_type` AS `hypervisor_type`,
`vm_instance`.`instance_name` AS `instance_name`,
`vm_instance`.`guest_os_id` AS `guest_os_id`,
`vm_instance`.`display_vm` AS `display_vm`,
`guest_os`.`uuid` AS `guest_os_uuid`,
`vm_instance`.`pod_id` AS `pod_id`,
`host_pod_ref`.`uuid` AS `pod_uuid`,
`vm_instance`.`private_ip_address` AS `private_ip_address`,
`vm_instance`.`private_mac_address` AS `private_mac_address`,
`vm_instance`.`vm_type` AS `vm_type`,
`data_center`.`id` AS `data_center_id`,
`data_center`.`uuid` AS `data_center_uuid`,
`data_center`.`name` AS `data_center_name`,
`data_center`.`is_security_group_enabled` AS `security_group_enabled`,
`data_center`.`networktype` AS `data_center_network_type`,
`host`.`id` AS `host_id`,
`host`.`uuid` AS `host_uuid`,
`host`.`name` AS `host_name`,
`host`.`cluster_id` AS `cluster_id`,
`host`.`status` AS `host_status`,
`host`.`resource_state` AS `host_resource_state`,
`vm_template`.`id` AS `template_id`,
`vm_template`.`uuid` AS `template_uuid`,
`vm_template`.`name` AS `template_name`,
`vm_template`.`type` AS `template_type`,
`vm_template`.`format` AS `template_format`,
`vm_template`.`display_text` AS `template_display_text`,
`vm_template`.`enable_password` AS `password_enabled`,
`iso`.`id` AS `iso_id`,
`iso`.`uuid` AS `iso_uuid`,
`iso`.`name` AS `iso_name`,
`iso`.`display_text` AS `iso_display_text`,
`service_offering`.`id` AS `service_offering_id`,
`service_offering`.`uuid` AS `service_offering_uuid`,
`disk_offering`.`uuid` AS `disk_offering_uuid`,
`disk_offering`.`id` AS `disk_offering_id`,
(CASE
WHEN ISNULL(`service_offering`.`cpu`) THEN `custom_cpu`.`value`
ELSE `service_offering`.`cpu`
END) AS `cpu`,
(CASE
WHEN ISNULL(`service_offering`.`speed`) THEN `custom_speed`.`value`
ELSE `service_offering`.`speed`
END) AS `speed`,
(CASE
WHEN ISNULL(`service_offering`.`ram_size`) THEN `custom_ram_size`.`value`
ELSE `service_offering`.`ram_size`
END) AS `ram_size`,
`backup_offering`.`uuid` AS `backup_offering_uuid`,
`backup_offering`.`id` AS `backup_offering_id`,
`service_offering`.`name` AS `service_offering_name`,
`disk_offering`.`name` AS `disk_offering_name`,
`backup_offering`.`name` AS `backup_offering_name`,
`storage_pool`.`id` AS `pool_id`,
`storage_pool`.`uuid` AS `pool_uuid`,
`storage_pool`.`pool_type` AS `pool_type`,
`volumes`.`id` AS `volume_id`,
`volumes`.`uuid` AS `volume_uuid`,
`volumes`.`device_id` AS `volume_device_id`,
`volumes`.`volume_type` AS `volume_type`,
`security_group`.`id` AS `security_group_id`,
`security_group`.`uuid` AS `security_group_uuid`,
`security_group`.`name` AS `security_group_name`,
`security_group`.`description` AS `security_group_description`,
`nics`.`id` AS `nic_id`,
`nics`.`uuid` AS `nic_uuid`,
`nics`.`device_id` AS `nic_device_id`,
`nics`.`network_id` AS `network_id`,
`nics`.`ip4_address` AS `ip_address`,
`nics`.`ip6_address` AS `ip6_address`,
`nics`.`ip6_gateway` AS `ip6_gateway`,
`nics`.`ip6_cidr` AS `ip6_cidr`,
`nics`.`default_nic` AS `is_default_nic`,
`nics`.`gateway` AS `gateway`,
`nics`.`netmask` AS `netmask`,
`nics`.`mac_address` AS `mac_address`,
`nics`.`broadcast_uri` AS `broadcast_uri`,
`nics`.`isolation_uri` AS `isolation_uri`,
`vpc`.`id` AS `vpc_id`,
`vpc`.`uuid` AS `vpc_uuid`,
`networks`.`uuid` AS `network_uuid`,
`networks`.`name` AS `network_name`,
`networks`.`traffic_type` AS `traffic_type`,
`networks`.`guest_type` AS `guest_type`,
`user_ip_address`.`id` AS `public_ip_id`,
`user_ip_address`.`uuid` AS `public_ip_uuid`,
`user_ip_address`.`public_ip_address` AS `public_ip_address`,
`ssh_details`.`value` AS `keypair_names`,
`resource_tags`.`id` AS `tag_id`,
`resource_tags`.`uuid` AS `tag_uuid`,
`resource_tags`.`key` AS `tag_key`,
`resource_tags`.`value` AS `tag_value`,
`resource_tags`.`domain_id` AS `tag_domain_id`,
`domain`.`uuid` AS `tag_domain_uuid`,
`domain`.`name` AS `tag_domain_name`,
`resource_tags`.`account_id` AS `tag_account_id`,
`account`.`account_name` AS `tag_account_name`,
`resource_tags`.`resource_id` AS `tag_resource_id`,
`resource_tags`.`resource_uuid` AS `tag_resource_uuid`,
`resource_tags`.`resource_type` AS `tag_resource_type`,
`resource_tags`.`customer` AS `tag_customer`,
`async_job`.`id` AS `job_id`,
`async_job`.`uuid` AS `job_uuid`,
`async_job`.`job_status` AS `job_status`,
`async_job`.`account_id` AS `job_account_id`,
`affinity_group`.`id` AS `affinity_group_id`,
`affinity_group`.`uuid` AS `affinity_group_uuid`,
`affinity_group`.`name` AS `affinity_group_name`,
`affinity_group`.`description` AS `affinity_group_description`,
`autoscale_vmgroups`.`id` AS `autoscale_vmgroup_id`,
`autoscale_vmgroups`.`uuid` AS `autoscale_vmgroup_uuid`,
`autoscale_vmgroups`.`name` AS `autoscale_vmgroup_name`,
`vm_instance`.`dynamically_scalable` AS `dynamically_scalable`,
`user_data`.`id` AS `user_data_id`,
`user_data`.`uuid` AS `user_data_uuid`,
`user_data`.`name` AS `user_data_name`,
`user_vm`.`user_data_details` AS `user_data_details`,
`vm_template`.`user_data_link_policy` AS `user_data_policy`
FROM
(((((((((((((((((((((((((((((((((((`user_vm`
JOIN `vm_instance` ON (((`vm_instance`.`id` = `user_vm`.`id`)
AND ISNULL(`vm_instance`.`removed`))))
JOIN `account` ON ((`vm_instance`.`account_id` = `account`.`id`)))
JOIN `domain` ON ((`vm_instance`.`domain_id` = `domain`.`id`)))
LEFT JOIN `guest_os` ON ((`vm_instance`.`guest_os_id` = `guest_os`.`id`)))
LEFT JOIN `host_pod_ref` ON ((`vm_instance`.`pod_id` = `host_pod_ref`.`id`)))
LEFT JOIN `projects` ON ((`projects`.`project_account_id` = `account`.`id`)))
LEFT JOIN `instance_group_vm_map` ON ((`vm_instance`.`id` = `instance_group_vm_map`.`instance_id`)))
LEFT JOIN `instance_group` ON ((`instance_group_vm_map`.`group_id` = `instance_group`.`id`)))
LEFT JOIN `data_center` ON ((`vm_instance`.`data_center_id` = `data_center`.`id`)))
LEFT JOIN `host` ON ((`vm_instance`.`host_id` = `host`.`id`)))
LEFT JOIN `vm_template` ON ((`vm_instance`.`vm_template_id` = `vm_template`.`id`)))
LEFT JOIN `vm_template` `iso` ON ((`iso`.`id` = `user_vm`.`iso_id`)))
LEFT JOIN `volumes` ON ((`vm_instance`.`id` = `volumes`.`instance_id`)))
LEFT JOIN `service_offering` ON ((`vm_instance`.`service_offering_id` = `service_offering`.`id`)))
LEFT JOIN `disk_offering` `svc_disk_offering` ON ((`volumes`.`disk_offering_id` = `svc_disk_offering`.`id`)))
LEFT JOIN `disk_offering` ON ((`volumes`.`disk_offering_id` = `disk_offering`.`id`)))
LEFT JOIN `backup_offering` ON ((`vm_instance`.`backup_offering_id` = `backup_offering`.`id`)))
LEFT JOIN `storage_pool` ON ((`volumes`.`pool_id` = `storage_pool`.`id`)))
LEFT JOIN `security_group_vm_map` ON ((`vm_instance`.`id` = `security_group_vm_map`.`instance_id`)))
LEFT JOIN `security_group` ON ((`security_group_vm_map`.`security_group_id` = `security_group`.`id`)))
LEFT JOIN `user_data` ON ((`user_data`.`id` = `user_vm`.`user_data_id`)))
LEFT JOIN `nics` ON (((`vm_instance`.`id` = `nics`.`instance_id`)
AND ISNULL(`nics`.`removed`))))
LEFT JOIN `networks` ON ((`nics`.`network_id` = `networks`.`id`)))
LEFT JOIN `vpc` ON (((`networks`.`vpc_id` = `vpc`.`id`)
AND ISNULL(`vpc`.`removed`))))
LEFT JOIN `user_ip_address` FORCE INDEX(`fk_user_ip_address__vm_id`) ON ((`user_ip_address`.`vm_id` = `vm_instance`.`id`)))
LEFT JOIN `user_vm_details` `ssh_details` ON (((`ssh_details`.`vm_id` = `vm_instance`.`id`)
AND (`ssh_details`.`name` = 'SSH.KeyPairNames'))))
LEFT JOIN `resource_tags` ON (((`resource_tags`.`resource_id` = `vm_instance`.`id`)
AND (`resource_tags`.`resource_type` = 'UserVm'))))
LEFT JOIN `async_job` ON (((`async_job`.`instance_id` = `vm_instance`.`id`)
AND (`async_job`.`instance_type` = 'VirtualMachine')
AND (`async_job`.`job_status` = 0))))
LEFT JOIN `affinity_group_vm_map` ON ((`vm_instance`.`id` = `affinity_group_vm_map`.`instance_id`)))
LEFT JOIN `affinity_group` ON ((`affinity_group_vm_map`.`affinity_group_id` = `affinity_group`.`id`)))
LEFT JOIN `autoscale_vmgroup_vm_map` ON ((`autoscale_vmgroup_vm_map`.`instance_id` = `vm_instance`.`id`)))
LEFT JOIN `autoscale_vmgroups` ON ((`autoscale_vmgroup_vm_map`.`vmgroup_id` = `autoscale_vmgroups`.`id`)))
LEFT JOIN `user_vm_details` `custom_cpu` ON (((`custom_cpu`.`vm_id` = `vm_instance`.`id`)
AND (`custom_cpu`.`name` = 'CpuNumber'))))
LEFT JOIN `user_vm_details` `custom_speed` ON (((`custom_speed`.`vm_id` = `vm_instance`.`id`)
AND (`custom_speed`.`name` = 'CpuSpeed'))))
LEFT JOIN `user_vm_details` `custom_ram_size` ON (((`custom_ram_size`.`vm_id` = `vm_instance`.`id`)
AND (`custom_ram_size`.`name` = 'memory'))));
-- Quota inject tariff result into subsequent ones
CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.quota_tariff', 'position', 'bigint(20) NOT NULL DEFAULT 1 COMMENT "Position in the execution sequence for tariffs of the same type"');

View File

@ -0,0 +1,83 @@
-- 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.
-- VIEW `cloud`.`shared_filesystem_view`;
DROP VIEW IF EXISTS `cloud`.`shared_filesystem_view`;
CREATE VIEW `cloud`.`shared_filesystem_view` AS
SELECT
`shared_filesystem`.`id` AS `id`,
`shared_filesystem`.`uuid` AS `uuid`,
`shared_filesystem`.`name` AS `name`,
`shared_filesystem`.`description` AS `description`,
`shared_filesystem`.`state` AS `state`,
`shared_filesystem`.`fs_provider_name` AS `provider`,
`shared_filesystem`.`fs_type` AS `fs_type`,
`shared_filesystem`.`volume_id` AS `volume_id`,
`shared_filesystem`.`account_id` AS `account_id`,
`shared_filesystem`.`data_center_id` AS `zone_id`,
`zone`.`uuid` AS `zone_uuid`,
`zone`.`name` AS `zone_name`,
`instance`.`id` AS `instance_id`,
`instance`.`uuid` AS `instance_uuid`,
`instance`.`name` AS `instance_name`,
`instance`.`state` AS `instance_state`,
`volumes`.`size` AS `size`,
`volumes`.`uuid` AS `volume_uuid`,
`volumes`.`name` AS `volume_name`,
`volumes`.`provisioning_type` AS `provisioning_type`,
`volumes`.`format` AS `volume_format`,
`volumes`.`path` AS `volume_path`,
`volumes`.`chain_info` AS `volume_chain_info`,
`storage_pool`.`uuid` AS `pool_uuid`,
`storage_pool`.`name` AS `pool_name`,
`account`.`account_name` AS `account_name`,
`project`.`uuid` AS `project_uuid`,
`project`.`name` AS `project_name`,
`domain`.`uuid` AS `domain_uuid`,
`domain`.`name` AS `domain_name`,
`domain`.`path` AS `domain_path`,
`service_offering`.`uuid` AS `service_offering_uuid`,
`service_offering`.`name` AS `service_offering_name`,
`disk_offering`.`uuid` AS `disk_offering_uuid`,
`disk_offering`.`name` AS `disk_offering_name`,
`disk_offering`.`display_text` AS `disk_offering_display_text`,
`disk_offering`.`disk_size` AS `disk_offering_size`,
`disk_offering`.`customized` AS `disk_offering_custom`
FROM
`cloud`.`shared_filesystem`
LEFT JOIN
`cloud`.`data_center` AS `zone` ON `shared_filesystem`.`data_center_id` = `zone`.`id`
LEFT JOIN
`cloud`.`vm_instance` AS `instance` ON `shared_filesystem`.`vm_id` = `instance`.`id`
LEFT JOIN
`cloud`.`volumes` AS `volumes` ON `shared_filesystem`.`volume_id` = `volumes`.`id`
LEFT JOIN
`cloud`.`storage_pool` AS `storage_pool` ON `volumes`.`pool_id` = `storage_pool`.`id`
LEFT JOIN
`cloud`.`account` AS `account` ON `shared_filesystem`.`account_id` = `account`.`id`
LEFT JOIN
`cloud`.`projects` AS `project` ON `project`.`project_account_id` = `account`.`id`
LEFT JOIN
`cloud`.`domain` AS `domain` ON `shared_filesystem`.`domain_id` = `domain`.`id`
LEFT JOIN
`cloud`.`service_offering` AS `service_offering` ON `shared_filesystem`.`service_offering_id` = `service_offering`.`id`
LEFT JOIN
`cloud`.`disk_offering` AS `disk_offering` ON `volumes`.`disk_offering_id` = `disk_offering`.`id`
GROUP BY
`shared_filesystem`.`id`;

View File

@ -25,6 +25,7 @@ SELECT
`vm_instance`.`name` AS `name`,
`user_vm`.`display_name` AS `display_name`,
`user_vm`.`user_data` AS `user_data`,
`user_vm`.`user_vm_type` AS `user_vm_type`,
`account`.`id` AS `account_id`,
`account`.`uuid` AS `account_uuid`,
`account`.`account_name` AS `account_name`,

View File

@ -90,7 +90,7 @@ public final class LibvirtStartCommandWrapper extends CommandWrapper<StartComman
libvirtComputingResource.applyDefaultNetworkRules(conn, vmSpec, false);
// pass cmdline info to system vms
if (vmSpec.getType() != VirtualMachine.Type.User || (vmSpec.getBootArgs() != null && vmSpec.getBootArgs().contains(UserVmManager.CKS_NODE))) {
if (vmSpec.getType() != VirtualMachine.Type.User || (vmSpec.getBootArgs() != null && (vmSpec.getBootArgs().contains(UserVmManager.CKS_NODE) || vmSpec.getBootArgs().contains(UserVmManager.SHAREDFSVM)))) {
// try to patch and SSH into the systemvm for up to 5 minutes
for (int count = 0; count < 10; count++) {
// wait and try passCmdLine for 30 seconds at most for CLOUDSTACK-2823

View File

@ -25,7 +25,7 @@ import org.apache.commons.lang3.StringUtils;
import com.google.gson.annotations.SerializedName;
public class VolumeMetricsResponse extends VolumeResponse {
@SerializedName("sizegb")
@SerializedName(ApiConstants.SIZEGB)
@Param(description = "disk size in GiB")
private String diskSizeGB;

View File

@ -120,6 +120,7 @@
<module>shutdown</module>
<module>storage/sharedfs/storagevm</module>
<module>storage/image/default</module>
<module>storage/image/s3</module>
<module>storage/image/sample</module>

View File

@ -0,0 +1,30 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-plugin-sharedfs-provider-storagevm</artifactId>
<name>Apache CloudStack Plugin - StorageVm shared filesystem provider</name>
<parent>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloudstack-plugins</artifactId>
<version>4.20.0.0-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
</project>

View File

@ -0,0 +1,303 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs.lifecycle;
import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSPath;
import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSVmNamePrefix;
import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_CPU_COUNT;
import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_RAM_SIZE;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.VirtualMachineMigrationException;
import com.cloud.offering.ServiceOffering;
import com.cloud.storage.LaunchPermissionVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.LaunchPermissionDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.FileUtil;
import com.cloud.utils.Pair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmService;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSLifeCycle;
import org.apache.commons.codec.binary.Base64;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.cloud.dc.DataCenter;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.network.Network;
import com.cloud.resource.ResourceManager;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachineManager;
public class StorageVmSharedFSLifeCycle implements SharedFSLifeCycle {
protected Logger logger = LogManager.getLogger(getClass());
@Inject
private AccountManager accountMgr;
@Inject
protected ResourceManager resourceMgr;
@Inject
private VirtualMachineManager virtualMachineManager;
@Inject
private VolumeApiService volumeApiService;
@Inject
protected UserVmService userVmService;
@Inject
protected UserVmManager userVmManager;
@Inject
private DataCenterDao dataCenterDao;
@Inject
private VMTemplateDao templateDao;
@Inject
VolumeDao volumeDao;
@Inject
private UserVmDao userVmDao;
@Inject
NicDao nicDao;
@Inject
ServiceOfferingDao serviceOfferingDao;
@Inject
protected LaunchPermissionDao launchPermissionDao;
private String readResourceFile(String resource) {
try {
return FileUtil.readResourceFile(resource);
} catch (IOException e) {
throw new CloudRuntimeException("Unable to read the user data resource file due to exception " + e.getMessage());
}
}
private String getStorageVmConfig(final String fileSystem, final String hypervisorType, final String exportPath) {
String fsVmConfig = readResourceFile("/conf/fsvm-init.yml");
final String filesystem = "{{ fsvm.filesystem }}";
final String hypervisor = "{{ fsvm.hypervisor }}";
final String exportpath = "{{ fsvm.exportpath }}";
fsVmConfig = fsVmConfig.replace(filesystem, fileSystem);
fsVmConfig = fsVmConfig.replace(hypervisor, hypervisorType);
fsVmConfig = fsVmConfig.replace(exportpath, exportPath);
return fsVmConfig;
}
private String getStorageVmName(String fileShareName) {
String prefix = String.format("%s-%s", SharedFSVmNamePrefix, fileShareName);
String suffix = Long.toHexString(System.currentTimeMillis());
if (!NetUtils.verifyDomainNameLabel(prefix, true)) {
prefix = prefix.replaceAll("[^a-zA-Z0-9-]", "");
}
int nameLength = prefix.length() + suffix.length() + SharedFSVmNamePrefix.length();
if (nameLength > 63) {
int prefixLength = prefix.length() - (nameLength - 63);
prefix = prefix.substring(0, prefixLength);
}
return (String.format("%s-%s", prefix, suffix));
}
private UserVm deploySharedFSVM(Long zoneId, Account owner, List<Long> networkIds, String name, Long serviceOfferingId, Long diskOfferingId, SharedFS.FileSystemType fileSystem, Long size, Long minIops, Long maxIops) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
ServiceOffering serviceOffering = serviceOfferingDao.findById(serviceOfferingId);
DataCenter zone = dataCenterDao.findById(zoneId);
List<Hypervisor.HypervisorType> hypervisors = resourceMgr.getSupportedHypervisorTypes(zoneId, false, null);
if (hypervisors.size() > 0) {
Collections.shuffle(hypervisors);
} else {
throw new CloudRuntimeException(String.format("No supported hypervisor found for zone %s.", zone.toString()));
}
String hostName = getStorageVmName(name);
Network.IpAddresses addrs = new Network.IpAddresses(null, null);
Map<String, String> customParameterMap = new HashMap<String, String>();
if (minIops != null) {
customParameterMap.put("minIopsDo", minIops.toString());
customParameterMap.put("maxIopsDo", maxIops.toString());
}
List<String> keypairs = new ArrayList<String>();
for (final Iterator<Hypervisor.HypervisorType> iter = hypervisors.iterator(); iter.hasNext();) {
final Hypervisor.HypervisorType hypervisor = iter.next();
VMTemplateVO template = templateDao.findSystemVMReadyTemplate(zoneId, hypervisor);
if (template == null && !iter.hasNext()) {
throw new CloudRuntimeException(String.format("Unable to find the systemvm template for %s or it was not downloaded in %s.", hypervisor.toString(), zone.toString()));
}
LaunchPermissionVO existingPermission = launchPermissionDao.findByTemplateAndAccount(template.getId(), owner.getId());
if (existingPermission == null) {
LaunchPermissionVO launchPermission = new LaunchPermissionVO(template.getId(), owner.getId());
launchPermissionDao.persist(launchPermission);
}
UserVm vm = null;
String fsVmConfig = getStorageVmConfig(fileSystem.toString().toLowerCase(), hypervisor.toString().toLowerCase(), SharedFSPath);
String base64UserData = Base64.encodeBase64String(fsVmConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset()));
CallContext vmContext = CallContext.register(CallContext.current(), ApiCommandResourceType.VirtualMachine);
try {
vm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, hostName, hostName,
diskOfferingId, size, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData,
null, null, keypairs, null, addrs, null, null, null,
customParameterMap, null, null, null, null,
true, UserVmManager.SHAREDFSVM, null);
vmContext.setEventResourceId(vm.getId());
userVmService.startVirtualMachine(vm);
} catch (InsufficientCapacityException ex) {
if (vm != null) {
expungeVm(vm.getId());
}
if (iter.hasNext()) {
continue;
} else {
throw ex;
}
} finally {
CallContext.unregister();
}
return vm;
}
return null;
}
@Override
public void checkPrerequisites(DataCenter zone, Long serviceOfferingId) {
ServiceOffering serviceOffering = serviceOfferingDao.findById(serviceOfferingId);
if (serviceOffering.getCpu() < SHAREDFSVM_MIN_CPU_COUNT.valueIn(zone.getId())) {
throw new InvalidParameterValueException("Service offering's number of cpu should be greater than or equal to " + SHAREDFSVM_MIN_CPU_COUNT.key());
}
if (serviceOffering.getRamSize() < SHAREDFSVM_MIN_RAM_SIZE.valueIn(zone.getId())) {
throw new InvalidParameterValueException("Service offering's ram size should be greater than or equal to " + SHAREDFSVM_MIN_RAM_SIZE.key());
}
if (!serviceOffering.isOfferHA()) {
throw new InvalidParameterValueException("Service offering's should be HA enabled");
}
}
@Override
public Pair<Long, Long> deploySharedFS(SharedFS sharedFS, Long networkId, Long diskOfferingId, Long size, Long minIops, Long maxIops) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, OperationTimedoutException {
Account owner = accountMgr.getActiveAccountById(sharedFS.getAccountId());
UserVm vm = deploySharedFSVM(sharedFS.getDataCenterId(), owner, List.of(networkId), sharedFS.getName(), sharedFS.getServiceOfferingId(), diskOfferingId, sharedFS.getFsType(), size, minIops, maxIops);
List<VolumeVO> volumes = volumeDao.findByInstanceAndType(vm.getId(), Volume.Type.DATADISK);
return new Pair<>(volumes.get(0).getId(), vm.getId());
}
@Override
public void startSharedFS(SharedFS sharedFS) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException {
UserVmVO vm = userVmDao.findById(sharedFS.getVmId());
userVmService.startVirtualMachine(vm);
}
@Override
public boolean stopSharedFS(SharedFS sharedFS, Boolean forced) {
userVmManager.stopVirtualMachine(sharedFS.getVmId(), false);
return true;
}
private void expungeVm(Long vmId) {
UserVmVO userVM = userVmDao.findById(vmId);
if (userVM == null) {
return;
}
try {
UserVm vm = userVmService.destroyVm(userVM.getId(), true);
if (!userVmManager.expunge(userVM)) {
throw new CloudRuntimeException("Failed to expunge VM " + userVM.toString());
}
} catch (ResourceUnavailableException e) {
throw new CloudRuntimeException("Failed to expunge VM " + userVM.toString());
}
userVmDao.remove(vmId);
}
@Override
public boolean deleteSharedFS(SharedFS sharedFS) {
Long vmId = sharedFS.getVmId();
Long volumeId = sharedFS.getVolumeId();
if (vmId != null) {
expungeVm(vmId);
}
if (volumeId == null) {
return true;
}
VolumeVO volume = volumeDao.findById(volumeId);
Boolean expunge = false;
Boolean forceExpunge = false;
if (volume.getState() == Volume.State.Allocated) {
expunge = true;
forceExpunge = true;
}
volumeApiService.destroyVolume(volume.getId(), CallContext.current().getCallingAccount(), expunge, forceExpunge);
return true;
}
@Override
public boolean reDeploySharedFS(SharedFS sharedFS) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, OperationTimedoutException {
UserVm vm = virtualMachineManager.restoreVirtualMachine(sharedFS.getVmId(), null, null, true, null);
return (vm != null);
}
@Override
public boolean changeSharedFSServiceOffering(SharedFS sharedFS, Long serviceOfferingId) throws ManagementServerException, ResourceUnavailableException, VirtualMachineMigrationException {
return userVmManager.upgradeVirtualMachine(sharedFS.getVmId(), serviceOfferingId, new HashMap<String, String>());
}
}

View File

@ -0,0 +1,79 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs.provider;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSProvider;
import org.apache.cloudstack.storage.sharedfs.lifecycle.StorageVmSharedFSLifeCycle;
import com.cloud.utils.component.AdapterBase;
import com.cloud.utils.component.ComponentContext;
public class StorageVmSharedFSProvider extends AdapterBase implements SharedFSProvider, Configurable {
protected String name = String.valueOf(SharedFSProviderType.SHAREDFSVM);
public static final ConfigKey<Integer> SHAREDFSVM_MIN_RAM_SIZE = new ConfigKey<Integer>("Advanced",
Integer.class,
"sharedfsvm.min.ram.size",
"1024",
"minimum ram size allowed for the compute offering to be used to create the sharedfs vm.",
true,
ConfigKey.Scope.Zone,
SharedFS.SharedFSFeatureEnabled.key());
public static final ConfigKey<Integer> SHAREDFSVM_MIN_CPU_COUNT = new ConfigKey<Integer>("Advanced",
Integer.class,
"sharedfsvm.min.cpu.count",
"2",
"minimum cpu count allowed for the compute offering to be used to create the sharedfs vm.",
true,
ConfigKey.Scope.Zone,
SharedFS.SharedFSFeatureEnabled.key());
protected StorageVmSharedFSLifeCycle lifecycle;
@Override
public String getName() {
return name;
}
@Override
public void configure() {
lifecycle = ComponentContext.inject(StorageVmSharedFSLifeCycle.class);
}
@Override
public StorageVmSharedFSLifeCycle getSharedFSLifeCycle() {
return lifecycle;
}
@Override
public String getConfigComponentName() {
return StorageVmSharedFSProvider.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey[] {
SHAREDFSVM_MIN_CPU_COUNT,
SHAREDFSVM_MIN_RAM_SIZE
};
}
}

View File

@ -0,0 +1,18 @@
# 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.
name=sharedfs-provider-storagevm
parent=storage

View File

@ -0,0 +1,35 @@
<!--
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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
>
<bean id="storageVmSharedFSProvider"
class="org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider">
<property name="name" value="StorageVmSharedFSProvider" />
</bean>
</beans>

View File

@ -0,0 +1,220 @@
#cloud-config
# 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.
---
write_files:
- path: /usr/local/bin/sharedfs/common
permissions: '0700'
owner: root:root
content: |
#!/bin/bash -e
LOG_FILE="/var/log/userdata.log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE
}
get_block_device() {
if [ "$HYPERVISOR" == "kvm" ]; then
BLOCK_DEVICE="vdb"
elif [ "$HYPERVISOR" == "xenserver" ]; then
BLOCK_DEVICE="xvdb"
elif [ "$HYPERVISOR" == "vmware" ]; then
BLOCK_DEVICE="sdb"
else
log "Unknown hypervisor"
exit 1
fi
echo "$BLOCK_DEVICE"
}
- path: /usr/local/bin/sharedfs/create-shared-filesystem
permissions: '0700'
owner: root:root
content: |
#!/bin/bash -e
source /usr/local/bin/sharedfs/common
log "Running add script"
DISK_SIZE_DIR="/usr/local/var/sharedfs"
DISK_SIZE_FILE="$DISK_SIZE_DIR/previous_disk_size"
PARTITION="$1"
EXPORT_DIR={{ fsvm.exportpath }}
PERMISSIONS="rw,sync,no_subtree_check,no_root_squash"
mkdir -p "$EXPORT_DIR"
FS_TYPE=$(lsblk -no FSTYPE "$PARTITION")
if [ -z "$FS_TYPE" ]; then
FILESYSTEM={{ fsvm.filesystem }}
if [ "$FILESYSTEM" == "xfs" ]; then
mkfs.xfs "$PARTITION"
log "Formatted Partition $PARTITION with XFS Filesystem."
elif [ "$FILESYSTEM" == "ext4" ]; then
mkfs.ext4 "$PARTITION"
log "Formatted Partition $PARTITION with EXT4 Filesystem."
else
log "Invalid filesystem type specified. Use 'xfs' or 'ext4'."
exit 1
fi
else
log "Partition $PARTITION already has a filesystem of type $FS_TYPE. Skipping format."
fi
FS_INFO=$(blkid "$PARTITION")
UUID=$(echo "$FS_INFO" | grep -oP "UUID=\"\K[^\"]+")
TYPE=$(echo "$FS_INFO" | grep -oP "TYPE=\"\K[^\"]+")
if [ -z "$UUID" ] || [ -z "$TYPE" ]; then
log "Failed to retrieve UUID or TYPE for $PARTITION"
exit 1
fi
echo "UUID=$UUID $EXPORT_DIR $TYPE defaults 0 2" >> /etc/fstab
log "Added fstab entry."
mount -a
if mountpoint -q "$EXPORT_DIR"; then
log "$PARTITION is successfully mounted on $EXPORT_DIR"
else
log "Failed to mount $PARTITION on $EXPORT_DIR"
exit 1
fi
log "Configuring NFS export..."
EXPORT_ENTRY="$EXPORT_DIR *($PERMISSIONS)"
if ! grep -qF "$EXPORT_ENTRY" /etc/exports; then
echo "$EXPORT_ENTRY" | tee -a /etc/exports > /dev/null
fi
exportfs -ra
log "Enable and restart NFS server..."
systemctl enable nfs-kernel-server
systemctl restart nfs-kernel-server
CURRENT_DISK_SIZE=$(blockdev --getsz "$PARTITION")
mkdir -p "$DISK_SIZE_DIR"
echo "$CURRENT_DISK_SIZE" > "$DISK_SIZE_FILE"
log "NFS share created successfully."
log "Finished running add script."
- path: /usr/local/bin/sharedfs/resize-filesystem
permissions: '0700'
owner: root:root
content: |
#!/bin/bash -e
source /usr/local/bin/sharedfs/common
log "Running resize script."
FILESYSTEM={{ fsvm.filesystem }}
if [ "$FILESYSTEM" == "xfs" ]; then
EXPORT_DIR={{ fsvm.exportpath }}
xfs_growfs "$EXPORT_DIR"
elif [ "$FILESYSTEM" == "ext4" ]; then
PARTITION="/dev/$1"
resize2fs "$PARTITION"
else
log "Invalid filesystem type specified. Use 'xfs' or 'ext4'."
exit 1
fi
log "Finished running resize script."
- path: /etc/systemd/system/resize-sharedfs@.service
permissions: '0700'
owner: root:root
content: |
[Unit]
Description=Resize Shared FileSystem
[Install]
WantedBy=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/sharedfs/resize-filesystem %I
- path: /etc/systemd/system/cloud-dhclient@.service
permissions: '0700'
owner: root:root
content: |
[Unit]
Description=CloudStack service to start dhclient
[Install]
WantedBy=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/dhclient %I
Restart=on-failure
- path: /usr/local/bin/sharedfs/fsvm-setup
permissions: '0700'
owner: root:root
content: |
#!/bin/bash -e
source /usr/local/bin/sharedfs/common
HYPERVISOR={{ fsvm.hypervisor }}
BLOCK_DEVICE=$(get_block_device "$HYPERVISOR")
PARTITION="/dev/$BLOCK_DEVICE"
ADD_PARTITION_FILE="/usr/local/bin/sharedfs/create-shared-filesystem"
RESIZE_PARTITION_FILE="/usr/local/bin/sharedfs/resize-filesystem"
for interface in $(ls /sys/class/net/ | grep '^eth'); do
echo "Starting cloud-dhclient service for interface: $interface"
sudo systemctl start cloud-dhclient@$interface.service
done
log "Existing network interfaces configured"
UDEV_RESIZE_RULE="KERNEL==\"$BLOCK_DEVICE\", ACTION==\"add|change\", SUBSYSTEM==\"block\", ENV{SYSTEMD_WANTS}=\"resize-sharedfs@%k.service\""
UDEV_ADD_NIC_RULE="ACTION==\"add\", SUBSYSTEM==\"net\", DRIVERS==\"?*\", ENV{SYSTEMD_WANTS}=\"cloud-dhclient@%k.service\""
# Add udev rules
echo "$UDEV_RESIZE_RULE" > /etc/udev/rules.d/99-resize-filesystem.rules
echo "$UDEV_ADD_NIC_RULE" > /etc/udev/rules.d/99-add-nic.rules
if [ -b "$PARTITION" ]; then
log "Partition $PARTITION exists. Running the $ADD_PARTITION_FILE script"
$ADD_PARTITION_FILE "$PARTITION"
fi
log "Udev rules added."
# Reload udev rules
udevadm control --reload-rules
udevadm trigger
log "Script execution finished successfully."
sudo touch /home/cloud/success
echo "true" > /home/cloud/success
runcmd:
- /usr/local/bin/sharedfs/fsvm-setup

View File

@ -0,0 +1,335 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs.lifecycle;
import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_CPU_COUNT;
import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_RAM_SIZE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.network.Network;
import com.cloud.offering.ServiceOffering;
import com.cloud.resource.ResourceManager;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
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.DiskOfferingDao;
import com.cloud.storage.dao.LaunchPermissionDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.uservm.UserVm;
import com.cloud.utils.FileUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmService;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
@RunWith(MockitoJUnitRunner.class)
public class StorageVmSharedFSLifeCycleTest {
@Mock
private AccountManager accountMgr;
@Mock
protected ResourceManager resourceMgr;
@Mock
private VirtualMachineManager virtualMachineManager;
@Mock
private VolumeApiService volumeApiService;
@Mock
protected UserVmService userVmService;
@Mock
protected UserVmManager userVmManager;
@Mock
private DataCenterDao dataCenterDao;
@Mock
private VMTemplateDao templateDao;
@Mock
VolumeDao volumeDao;
@Mock
private UserVmDao userVmDao;
@Mock
NicDao nicDao;
@Mock
ServiceOfferingDao serviceOfferingDao;
@Mock
private DiskOfferingDao diskOfferingDao;
@Mock
protected LaunchPermissionDao launchPermissionDao;
@Spy
@InjectMocks
StorageVmSharedFSLifeCycle lifeCycle;
private static final long s_ownerId = 1L;
private static final long s_zoneId = 2L;
private static final long s_diskOfferingId = 3L;
private static final long s_serviceOfferingId = 4L;
private static final long s_templateId = 5L;
private static final long s_volumeId = 6L;
private static final long s_vmId = 7L;
private static final long s_networkId = 8L;
private static final long s_size = 10L;
private static final long s_minIops = 1000L;
private static final long s_maxIops = 2000L;
private static final String s_fsFormat = "EXT4";
private static final String s_name = "TestSharedFS";
private MockedStatic<FileUtil> fileUtilMocked;
private MockedStatic<CallContext> callContextMocked;
private AutoCloseable closeable;
@Before
public void setUp() {
closeable = MockitoAnnotations.openMocks(this);
callContextMocked = mockStatic(CallContext.class);
CallContext callContextMock = mock(CallContext.class);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Account owner = mock(Account.class);
when(callContextMock.getCallingAccount()).thenReturn(owner);
CallContext vmContext = mock(CallContext.class);
when(callContextMock.register(CallContext.current(), ApiCommandResourceType.VirtualMachine)).thenReturn(vmContext);
fileUtilMocked = mockStatic(FileUtil.class);
fileUtilMocked.when(() -> FileUtil.readResourceFile("/conf/fsvm-init.yml")).thenReturn("");
}
@After
public void tearDown() throws Exception {
fileUtilMocked.close();
callContextMocked.close();
closeable.close();
}
@Test
public void testCheckPrerequisites() {
DataCenterVO zone = mock(DataCenterVO.class);
when(zone.getId()).thenReturn(s_zoneId);
ServiceOfferingVO serviceOfferingVO = mock(ServiceOfferingVO.class);
when(serviceOfferingVO.getCpu()).thenReturn(4);
when(serviceOfferingVO.getRamSize()).thenReturn(1024);
when(serviceOfferingVO.isOfferHA()).thenReturn(true);
when(serviceOfferingDao.findById(s_serviceOfferingId)).thenReturn(serviceOfferingVO);
lifeCycle.checkPrerequisites(zone, s_serviceOfferingId);
}
@Test
public void testCheckPrerequisitesMinCpuException() {
DataCenterVO zone = mock(DataCenterVO.class);
when(zone.getId()).thenReturn(s_zoneId);
ServiceOfferingVO serviceOfferingVO = mock(ServiceOfferingVO.class);
when(serviceOfferingDao.findById(s_serviceOfferingId)).thenReturn(serviceOfferingVO);
InvalidParameterValueException exception = Assert.assertThrows(InvalidParameterValueException.class, () -> lifeCycle.checkPrerequisites(zone, s_serviceOfferingId));
Assert.assertEquals(exception.getMessage(), "Service offering's number of cpu should be greater than or equal to " + SHAREDFSVM_MIN_CPU_COUNT.key());
}
@Test
public void testCheckPrerequisitesMinRamException() {
DataCenterVO zone = mock(DataCenterVO.class);
when(zone.getId()).thenReturn(s_zoneId);
ServiceOfferingVO serviceOfferingVO = mock(ServiceOfferingVO.class);
when(serviceOfferingDao.findById(s_serviceOfferingId)).thenReturn(serviceOfferingVO);
when(serviceOfferingVO.getCpu()).thenReturn(4);
InvalidParameterValueException exception = Assert.assertThrows(InvalidParameterValueException.class, () -> lifeCycle.checkPrerequisites(zone, s_serviceOfferingId));
Assert.assertEquals(exception.getMessage(), "Service offering's ram size should be greater than or equal to " + SHAREDFSVM_MIN_RAM_SIZE.key());
}
@Test
public void testCheckPrerequisitesHAException() {
DataCenterVO zone = mock(DataCenterVO.class);
when(zone.getId()).thenReturn(s_zoneId);
ServiceOfferingVO serviceOfferingVO = mock(ServiceOfferingVO.class);
when(serviceOfferingDao.findById(s_serviceOfferingId)).thenReturn(serviceOfferingVO);
when(serviceOfferingVO.getCpu()).thenReturn(4);
when(serviceOfferingVO.getRamSize()).thenReturn(1024);
InvalidParameterValueException exception = Assert.assertThrows(InvalidParameterValueException.class, () -> lifeCycle.checkPrerequisites(zone, s_serviceOfferingId));
Assert.assertEquals(exception.getMessage(), "Service offering's should be HA enabled");
}
private SharedFS prepareDeploySharedFS() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
SharedFS sharedFS = mock(SharedFS.class);
when(sharedFS.getDataCenterId()).thenReturn(s_zoneId);
when(sharedFS.getName()).thenReturn(s_name);
when(sharedFS.getServiceOfferingId()).thenReturn(s_serviceOfferingId);
when(sharedFS.getFsType()).thenReturn(SharedFS.FileSystemType.valueOf(s_fsFormat));
when(sharedFS.getAccountId()).thenReturn(s_ownerId);
DataCenterVO zone = mock(DataCenterVO.class);
when(dataCenterDao.findById(s_zoneId)).thenReturn(zone);
when(resourceMgr.getSupportedHypervisorTypes(s_zoneId, false, null)).thenReturn(List.of(Hypervisor.HypervisorType.KVM));
ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class);
when(serviceOfferingDao.findById(s_serviceOfferingId)).thenReturn(serviceOffering);
VMTemplateVO template = mock(VMTemplateVO.class);
when(templateDao.findSystemVMReadyTemplate(s_zoneId, Hypervisor.HypervisorType.KVM)).thenReturn(template);
when(template.getId()).thenReturn(s_templateId);
return sharedFS;
}
@Test
public void testDeploySharedFS() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, IOException, OperationTimedoutException {
SharedFS sharedFS = prepareDeploySharedFS();
when(sharedFS.getAccountId()).thenReturn(s_ownerId);
Account owner = mock(Account.class);
when(owner.getId()).thenReturn(s_ownerId);
when(accountMgr.getActiveAccountById(s_ownerId)).thenReturn(owner);
UserVm vm = mock(UserVm.class);
when(vm.getId()).thenReturn(s_vmId);
when(userVmService.createAdvancedVirtualMachine(
any(DataCenter.class), any(ServiceOffering.class), any(VirtualMachineTemplate.class), anyList(), any(Account.class), anyString(),
anyString(), anyLong(), anyLong(), isNull(), any(Hypervisor.HypervisorType.class), any(BaseCmd.HTTPMethod.class), anyString(),
isNull(), isNull(), anyList(), isNull(), any(Network.IpAddresses.class), isNull(), isNull(), isNull(),
anyMap(), isNull(), isNull(), isNull(), isNull(),
anyBoolean(), anyString(), isNull())).thenReturn(vm);
VolumeVO volume = mock(VolumeVO.class);
when(volume.getId()).thenReturn(s_volumeId);
when(volumeDao.findByInstanceAndType(s_vmId, Volume.Type.DATADISK)).thenReturn(List.of(volume));
Pair<Long, Long> result = lifeCycle.deploySharedFS(sharedFS, s_networkId, s_diskOfferingId, s_size, s_minIops, s_maxIops);
Assert.assertEquals(Optional.ofNullable(result.first()), Optional.ofNullable(s_volumeId));
Assert.assertEquals(Optional.ofNullable(result.second()), Optional.ofNullable(s_vmId));
}
@Test(expected = CloudRuntimeException.class)
public void testDeploySharedFSHypervisorNotFound() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, IOException, OperationTimedoutException {
SharedFS sharedFS = mock(SharedFS.class);
when(sharedFS.getDataCenterId()).thenReturn(s_zoneId);
when(sharedFS.getName()).thenReturn(s_name);
when(sharedFS.getServiceOfferingId()).thenReturn(s_serviceOfferingId);
when(sharedFS.getFsType()).thenReturn(SharedFS.FileSystemType.valueOf(s_fsFormat));
when(sharedFS.getAccountId()).thenReturn(s_ownerId);
when(accountMgr.getActiveAccountById(s_ownerId)).thenReturn(null);
DataCenterVO zone = mock(DataCenterVO.class);
when(dataCenterDao.findById(s_zoneId)).thenReturn(zone);
lifeCycle.deploySharedFS(sharedFS, s_networkId, s_diskOfferingId, s_size, s_minIops, s_maxIops);
}
@Test(expected = CloudRuntimeException.class)
public void testDeploySharedFSTemplateNotFound() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, IOException, OperationTimedoutException {
SharedFS sharedFS = mock(SharedFS.class);
when(sharedFS.getDataCenterId()).thenReturn(s_zoneId);
when(sharedFS.getName()).thenReturn(s_name);
when(sharedFS.getServiceOfferingId()).thenReturn(s_serviceOfferingId);
when(sharedFS.getFsType()).thenReturn(SharedFS.FileSystemType.valueOf(s_fsFormat));
when(sharedFS.getAccountId()).thenReturn(s_ownerId);
when(accountMgr.getActiveAccountById(s_ownerId)).thenReturn(null);
DataCenterVO zone = mock(DataCenterVO.class);
when(dataCenterDao.findById(s_zoneId)).thenReturn(zone);
when(resourceMgr.getSupportedHypervisorTypes(s_zoneId, false, null)).thenReturn(List.of(Hypervisor.HypervisorType.KVM));
when(templateDao.findSystemVMReadyTemplate(s_zoneId, Hypervisor.HypervisorType.KVM)).thenReturn(null);
lifeCycle.deploySharedFS(sharedFS, s_networkId, s_diskOfferingId, s_size, s_minIops, s_maxIops);
}
@Test
public void testDeleteSharedFS() throws ResourceUnavailableException {
SharedFS sharedFS = mock(SharedFS.class);
when(sharedFS.getVmId()).thenReturn(s_vmId);
when(sharedFS.getVolumeId()).thenReturn(s_volumeId);
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(s_vmId);
when(userVmDao.findById(s_vmId)).thenReturn(vm);
when(userVmService.destroyVm(s_vmId, true)).thenReturn(vm);
when(userVmManager.expunge(vm)).thenReturn(true);
VolumeVO volume = mock(VolumeVO.class);
when(volumeDao.findById(s_volumeId)).thenReturn(volume);
when(volume.getId()).thenReturn(s_volumeId);
when(volume.getState()).thenReturn(Volume.State.Allocated);
Assert.assertEquals(lifeCycle.deleteSharedFS(sharedFS), true);
}
@Test
public void testReDeploySharedFS() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, IOException, OperationTimedoutException {
SharedFS sharedFS = mock(SharedFS.class);
Long vmId = 1L;
when(sharedFS.getVmId()).thenReturn(vmId);
UserVm vm = mock(UserVm.class);
when(virtualMachineManager.restoreVirtualMachine(vmId, null, null, true, null)).thenReturn(vm);
boolean result = lifeCycle.reDeploySharedFS(sharedFS);
Assert.assertEquals(result, true);
}
}

View File

@ -50,6 +50,7 @@ import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse;
import org.apache.cloudstack.api.response.EventResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.HostForMigrationResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.HostTagResponse;
@ -93,6 +94,9 @@ import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.query.dao.SharedFSJoinDao;
import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO;
import com.cloud.agent.api.VgpuTypesInfo;
import com.cloud.api.query.dao.AccountJoinDao;
@ -491,6 +495,8 @@ public class ApiDBUtils {
static SnapshotPolicyDetailsDao s_snapshotPolicyDetailsDao;
static ObjectStoreDao s_objectStoreDao;
static SharedFSJoinDao s_sharedFSJoinDao;
static BucketDao s_bucketDao;
static VirtualMachineManager s_virtualMachineManager;
@ -758,6 +764,8 @@ public class ApiDBUtils {
private BucketDao bucketDao;
@Inject
private VirtualMachineManager virtualMachineManager;
@Inject
private SharedFSJoinDao sharedFSJoinDao;
@PostConstruct
void init() {
@ -893,6 +901,7 @@ public class ApiDBUtils {
s_objectStoreDao = objectStoreDao;
s_bucketDao = bucketDao;
s_virtualMachineManager = virtualMachineManager;
s_sharedFSJoinDao = sharedFSJoinDao;
}
// ///////////////////////////////////////////////////////////
@ -2269,4 +2278,12 @@ public class ApiDBUtils {
public static ObjectStoreResponse fillObjectStoreDetails(ObjectStoreResponse storeData, ObjectStoreVO store) {
return s_objectStoreDao.setObjectStoreResponse(storeData, store);
}
public static SharedFSResponse newSharedFSResponse(ResponseView view, SharedFSJoinVO sharedFSView) {
return s_sharedFSJoinDao.newSharedFSResponse(view, sharedFSView);
}
public static SharedFSJoinVO newSharedFSView(SharedFS sharedFS) {
return s_sharedFSJoinDao.newSharedFSView(sharedFS);
}
}

View File

@ -89,6 +89,7 @@ import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse;
import org.apache.cloudstack.api.response.EventResponse;
import org.apache.cloudstack.api.response.ExtractResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.FirewallResponse;
import org.apache.cloudstack.api.response.FirewallRuleResponse;
import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse;
@ -213,6 +214,8 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO;
import org.apache.cloudstack.storage.object.Bucket;
import org.apache.cloudstack.storage.object.ObjectStore;
import org.apache.cloudstack.usage.Usage;
@ -5280,4 +5283,10 @@ public class ApiResponseHelper implements ResponseGenerator {
populateAccount(bucketResponse, bucket.getAccountId());
return bucketResponse;
}
@Override
public SharedFSResponse createSharedFSResponse(ResponseView view, SharedFS sharedFS) {
SharedFSJoinVO sharedFSView = ApiDBUtils.newSharedFSView(sharedFS);
return ApiDBUtils.newSharedFSResponse(view, sharedFSView);
}
}

View File

@ -172,6 +172,7 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
userVmResponse.setCreated(userVm.getCreated());
userVmResponse.setLastUpdated(userVm.getLastUpdated());
userVmResponse.setDisplayVm(userVm.isDisplayVm());
userVmResponse.setVmType(userVm.getUserVmType());
if (userVm.getState() != null) {
userVmResponse.setState(userVm.getState().toString());

View File

@ -365,6 +365,9 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
@Column(name = "user_data", updatable = true, nullable = true, length = 2048)
private String userData;
@Column(name = "user_vm_type")
private String userVmType;
@Column(name = "project_id")
private long projectId;
@ -819,6 +822,10 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
return userData;
}
public String getUserVmType() {
return userVmType;
}
public long getGuestOsId() {
return guestOsId;
}

View File

@ -4041,6 +4041,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(UpdateBucketCmd.class);
cmdList.add(DeleteBucketCmd.class);
cmdList.add(ListBucketsCmd.class);
return cmdList;
}
@ -4475,6 +4476,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
regionSecondaryEnabled = true;
}
final Integer fsVmMinCpu = Integer.parseInt(_configDao.getValue("sharedfsvm.min.cpu.count"));
final Integer fsVmMinRam = Integer.parseInt(_configDao.getValue("sharedfsvm.min.ram.size"));
capabilities.put("securityGroupsEnabled", securityGroupsEnabled);
capabilities.put("userPublicTemplateEnabled", userPublicTemplateEnabled);
capabilities.put("cloudStackVersion", getVersion());
@ -4502,6 +4506,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
capabilities.put("apiLimitInterval", apiLimitInterval);
capabilities.put("apiLimitMax", apiLimitMax);
}
capabilities.put(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT, fsVmMinCpu);
capabilities.put(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE, fsVmMinRam);
return capabilities;
}

View File

@ -1035,7 +1035,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
// if VM Id is provided, attach the volume to the VM
if (cmd.getVirtualMachineId() != null) {
try {
attachVolumeToVM(cmd.getVirtualMachineId(), volume.getId(), volume.getDeviceId());
attachVolumeToVM(cmd.getVirtualMachineId(), volume.getId(), volume.getDeviceId(), false);
} catch (Exception ex) {
StringBuilder message = new StringBuilder("Volume: ");
message.append(volume.getUuid());
@ -1974,7 +1974,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
boolean shrinkOk = cmd.isShrinkOk();
boolean autoMigrateVolume = cmd.getAutoMigrate();
VolumeVO volume = _volsDao.findById(cmd.getId());
return changeDiskOfferingForVolumeInternal(cmd.getId(), newDiskOfferingId, newSize, newMinIops, newMaxIops, autoMigrateVolume, shrinkOk);
}
@Override
public Volume changeDiskOfferingForVolumeInternal(Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean autoMigrateVolume, boolean shrinkOk) throws ResourceAllocationException {
VolumeVO volume = _volsDao.findById(volumeId);
if (volume == null) {
throw new InvalidParameterValueException("No such volume");
}
@ -1982,10 +1988,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
/* Does the caller have authority to act on this volume? */
_accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume);
return changeDiskOfferingForVolumeInternal(volume, newDiskOfferingId, newSize, newMinIops, newMaxIops, autoMigrateVolume, shrinkOk);
}
private Volume changeDiskOfferingForVolumeInternal(VolumeVO volume, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean autoMigrateVolume, boolean shrinkOk) throws ResourceAllocationException {
long existingDiskOfferingId = volume.getDiskOfferingId();
DiskOfferingVO existingDiskOffering = _diskOfferingDao.findByIdIncludingRemoved(existingDiskOfferingId);
DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(newDiskOfferingId);
@ -2345,6 +2347,16 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
* the actual disk size.
*/
if (currentSize > newSize) {
if (shrinkOk) {
VMInstanceVO vm = _vmInstanceDao.findById(volume.getInstanceId());
if (vm != null && vm.getType().equals(VirtualMachine.Type.User)) {
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
if (userVm != null && UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Shrink volume cannot be done on a Shared FileSystem VM");
}
}
}
if (volume != null && ImageFormat.QCOW2.equals(volume.getFormat()) && !Volume.State.Allocated.equals(volume.getState()) && !StoragePoolType.StorPool.equals(volume.getPoolType())) {
String message = "Unable to shrink volumes of type QCOW2";
logger.warn(message);
@ -2365,7 +2377,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@Override
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true)
public Volume attachVolumeToVM(AttachVolumeCmd command) {
return attachVolumeToVM(command.getVirtualMachineId(), command.getId(), command.getDeviceId());
return attachVolumeToVM(command.getVirtualMachineId(), command.getId(), command.getDeviceId(), false);
}
private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
@ -2473,13 +2485,17 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
return newVol;
}
public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId, Boolean allowAttachForSharedFS) {
Account caller = CallContext.current().getCallingAccount();
VolumeInfo volumeToAttach = getAndCheckVolumeInfo(volumeId);
UserVmVO vm = getAndCheckUserVmVO(vmId, volumeToAttach);
if (!allowAttachForSharedFS && UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) {
throw new InvalidParameterValueException("Can't attach a volume to a Shared FileSystem VM");
}
checkDeviceId(deviceId, volumeToAttach, vm);
checkNumberOfAttachedVolumes(deviceId, vm);
@ -2894,6 +2910,11 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
// Check that the VM is in the correct state
UserVmVO vm = _userVmDao.findById(vmId);
if (UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) {
throw new InvalidParameterValueException("Can't detach a volume from a Shared FileSystem VM");
}
if (vm.getState() != State.Running && vm.getState() != State.Stopped && vm.getState() != State.Destroyed) {
throw new InvalidParameterValueException("Please specify a VM that is either running or stopped.");
}
@ -3760,7 +3781,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
private boolean isOperationSupported(VMTemplateVO template, UserVmVO userVm) {
if (template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM &&
(userVm == null || !UserVmManager.CKS_NODE.equals(userVm.getUserVmType()))) {
(userVm == null || !UserVmManager.CKS_NODE.equals(userVm.getUserVmType()) || !UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType()))) {
return false;
}
return true;

View File

@ -1055,7 +1055,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
if (instanceId != null) {
userVmVO = _vmDao.findById(instanceId);
}
if (template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM && (userVmVO == null || !UserVmManager.CKS_NODE.equals(userVmVO.getUserVmType()))) {
if (template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM && (userVmVO == null || !UserVmManager.CKS_NODE.equals(userVmVO.getUserVmType()) || !UserVmManager.SHAREDFSVM.equals(userVmVO.getUserVmType()))) {
throw new InvalidParameterValueException("VolumeId: " + volumeId + " is for System VM , Creating snapshot against System VM volumes is not supported");
}
}

View File

@ -207,6 +207,7 @@ import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.State;
@ -1185,6 +1186,9 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
if (vm == null) {
throw new InvalidParameterValueException("Unable to find a virtual machine with id " + vmId);
}
if (UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
VMTemplateVO iso = _tmpltDao.findById(isoId);
if (iso == null || iso.getRemoved() != null) {

View File

@ -86,6 +86,7 @@ public interface UserVmManager extends UserVmService {
static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
public static final String CKS_NODE = "cksnode";
public static final String SHAREDFSVM = "sharedfsvm";
/**
* @param hostId get all of the virtual machines that belong to one host.

View File

@ -957,6 +957,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (userVm == null) {
throw new InvalidParameterValueException("unable to find a virtual machine by id" + cmd.getId());
}
if (UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
_accountMgr.checkAccess(caller, null, true, userVm);
VMTemplateVO template = _templateDao.findByIdIncludingRemoved(userVm.getTemplateId());
@ -1001,6 +1004,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (userVm == null) {
throw new InvalidParameterValueException("unable to find a virtual machine by id" + cmd.getId());
}
if (UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
VMTemplateVO template = _templateDao.findByIdIncludingRemoved(userVm.getTemplateId());
@ -1440,6 +1446,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new InvalidParameterValueException("unable to find a network with id " + networkId);
}
if (UserVmManager.SHAREDFSVM.equals(vmInstance.getUserVmType()) && network.getGuestType() == Network.GuestType.Shared) {
if ((network.getAclType() != ControlledEntity.ACLType.Account) ||
(network.getDomainId() != vmInstance.getDomainId()) ||
(network.getAccountId() != vmInstance.getAccountId())) {
throw new InvalidParameterValueException("Shared network which is not Account scoped and not belonging to the same account can not be added to a Shared FileSystem VM");
}
}
Account vmOwner = _accountMgr.getAccount(vmInstance.getAccountId());
_networkModel.checkNetworkPermissions(vmOwner, network);
@ -1950,6 +1964,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException {
VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId);
Account caller = CallContext.current().getCallingAccount();
_accountMgr.checkAccess(caller, null, true, vmInstance);
if (vmInstance == null) {
@ -2276,6 +2291,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (vm == null) {
throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId);
}
if (UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
// When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVm is false for the caller.
if (!_accountMgr.isAdmin(userId) && !AllowUserExpungeRecoverVm.valueIn(userId)) {
@ -2811,6 +2829,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new CloudRuntimeException("Detail settings are read from OVA, it cannot be changed by API call.");
}
}
UserVmVO userVm = _vmDao.findById(cmd.getId());
if (userVm != null && UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
String userData = cmd.getUserData();
Long userDataId = cmd.getUserdataId();
String userDataDetails = null;
@ -3379,6 +3402,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (vm == null || vm.getRemoved() != null) {
throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId);
}
if (UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
if (Arrays.asList(State.Destroyed, State.Expunging).contains(vm.getState()) && !expunge) {
logger.debug("Vm id=" + vmId + " is already destroyed");
@ -3820,7 +3846,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
// Verify that owner can use the service offering
_accountMgr.checkAccess(owner, serviceOffering, zone);
_accountMgr.checkAccess(owner, _diskOfferingDao.findById(diskOfferingId), zone);
DiskOffering diskOffering =_diskOfferingDao.findById(diskOfferingId);
_accountMgr.checkAccess(owner, diskOffering, zone);
List<HypervisorType> vpcSupportedHTypes = _vpcMgr.getSupportedVpcHypervisors();
if (networkIdList == null || networkIdList.isEmpty()) {
@ -4237,7 +4265,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
}
if (template.getTemplateType().equals(TemplateType.SYSTEM) && !CKS_NODE.equals(vmType)) {
if (template.getTemplateType().equals(TemplateType.SYSTEM) && !CKS_NODE.equals(vmType) && !SHAREDFSVM.equals(vmType)) {
throw new InvalidParameterValueException("Unable to use system template " + template.getId() + " to deploy a user vm");
}
List<VMTemplateZoneVO> listZoneTemplate = _templateZoneDao.listByZoneTemplate(zone.getId(), template.getId());
@ -5084,10 +5112,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
private void addUserVMCmdlineArgs(Long vmId, VirtualMachineProfile profile, DeployDestination dest, StringBuilder buf) {
UserVmVO k8sVM = _vmDao.findById(vmId);
UserVmVO vm = _vmDao.findById(vmId);
buf.append(" template=domP");
buf.append(" name=").append(profile.getHostName());
buf.append(" type=").append(k8sVM.getUserVmType());
buf.append(" type=").append(vm.getUserVmType());
for (NicProfile nic : profile.getNics()) {
int deviceId = nic.getDeviceId();
if (nic.getIPv4Address() == null) {
@ -5128,7 +5156,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
Map<String, String> details = userVmDetailsDao.listDetailsKeyPairs(vm.getId());
vm.setDetails(details);
StringBuilder buf = profile.getBootArgsBuilder();
if (CKS_NODE.equals(vm.getUserVmType())) {
if (CKS_NODE.equals(vm.getUserVmType()) || SHAREDFSVM.equals(vm.getUserVmType())) {
addUserVMCmdlineArgs(vm.getId(), profile, dest, buf);
}
// add userdata info into vm profile
@ -5909,6 +5937,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
ex.addProxyObject(String.valueOf(vmId), "vmId");
throw ex;
}
if (UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
if (vm.getRemoved() != null) {
logger.trace("Vm id=" + vmId + " is already expunged");
@ -7368,6 +7399,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
ex.addProxyObject(vm.getUuid(), "vmId");
throw ex;
}
if (UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
final Account oldAccount = _accountService.getActiveAccountById(vm.getAccountId());
if (oldAccount == null) {
@ -7888,6 +7922,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
ex.addProxyObject(String.valueOf(vmId), "vmId");
throw ex;
}
if (UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
_accountMgr.checkAccess(caller, null, true, vm);
VMTemplateVO template;

View File

@ -508,6 +508,9 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
if (userVm == null) {
throw new InvalidParameterValueException("Create vm to snapshot failed due to vm: " + vmId + " is not found");
}
if (UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(vmSnapshotId);
if (vmSnapshot == null) {
throw new CloudRuntimeException("VM snapshot id: " + vmSnapshotId + " can not be found");

View File

@ -60,6 +60,7 @@ import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.fsm.StateListener;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.Event;
@ -430,6 +431,9 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
if (vmInstance == null) {
throw new InvalidParameterValueException("Unable to find a virtual machine with id " + vmId);
}
if (UserVmManager.SHAREDFSVM.equals(vmInstance.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
// Check that the VM is stopped
if (!vmInstance.getState().equals(State.Stopped)) {

View File

@ -29,6 +29,8 @@ import java.util.TimerTask;
import com.cloud.storage.VolumeApiService;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachineManager;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
@ -115,6 +117,7 @@ import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.google.gson.Gson;
@ -147,6 +150,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
@Inject
private DiskOfferingDao diskOfferingDao;
@Inject
private UserVmDao userVmDao;
@Inject
private ApiDispatcher apiDispatcher;
@Inject
private AsyncJobManager asyncJobManager;
@ -279,6 +284,13 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
throw new CloudRuntimeException("VM is not in running or stopped state");
}
if (vm.getType().equals(VirtualMachine.Type.User)) {
UserVmVO userVm = userVmDao.findById(vmId);
if (userVm != null && UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
}
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
@ -406,6 +418,12 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
}
final VMInstanceVO vm = findVmById(vmId);
if (vm.getType().equals(VirtualMachine.Type.User)) {
UserVmVO userVm = userVmDao.findById(vmId);
if (userVm != null && UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
}
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
@ -473,6 +491,13 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
if (vm.getType().equals(VirtualMachine.Type.User)) {
UserVmVO userVm = userVmDao.findById(vmId);
if (userVm != null && UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
}
if (vm.getBackupOfferingId() == null) {
throw new CloudRuntimeException("VM has not backup offering configured, cannot create backup before assigning it to a backup offering");
}
@ -734,6 +759,12 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
validateForZone(backup.getZoneId());
final VMInstanceVO vm = findVmById(vmId);
if (vm.getType().equals(VirtualMachine.Type.User)) {
UserVmVO userVm = userVmDao.findById(vmId);
if (userVm != null && UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
}
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
if (vm.getBackupOfferingId() != null && !BackupEnableAttachDetachVolumes.value()) {

View File

@ -0,0 +1,720 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs;
import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSCleanupDelay;
import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSCleanupInterval;
import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSFeatureEnabled;
import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSExpungeWorkers;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.configuration.ConfigurationManager;
import com.cloud.dc.DataCenter;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import com.cloud.network.Network;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.org.Grouping;
import com.cloud.projects.Project;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.component.PluggableService;
import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.JoinBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.utils.fsm.StateMachine2;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSDiskOfferingCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSServiceOfferingCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.CreateSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ExpungeSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSProvidersCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.DestroySharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.RecoverSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.RestartSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.StartSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.StopSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.UpdateSharedFSCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.storage.sharedfs.dao.SharedFSDao;
import org.apache.cloudstack.storage.sharedfs.SharedFS.Event;
import org.apache.cloudstack.storage.sharedfs.SharedFS.State;
import org.apache.cloudstack.storage.sharedfs.query.dao.SharedFSJoinDao;
import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.NicVO;
import com.cloud.vm.dao.NicDao;
public class SharedFSServiceImpl extends ManagerBase implements SharedFSService, Configurable, PluggableService {
@Inject
private AccountManager accountMgr;
@Inject
private DataCenterDao dataCenterDao;
@Inject
private ConfigurationManager configMgr;
@Inject
private VolumeApiService volumeApiService;
@Inject
private SharedFSDao sharedFSDao;
@Inject
private SharedFSJoinDao sharedFSJoinDao;
@Inject
private DiskOfferingDao diskOfferingDao;
@Inject
ConfigurationDao configDao;
@Inject
VolumeDao volumeDao;
@Inject
NetworkDao networkDao;
@Inject
NicDao nicDao;
protected List<SharedFSProvider> sharedFSProviders;
private Map<String, SharedFSProvider> sharedFSProviderMap = new HashMap<>();
protected final StateMachine2<State, Event, SharedFS> sharedFSStateMachine;
ScheduledExecutorService _executor = null;
public SharedFSServiceImpl() {
this.sharedFSStateMachine = State.getStateMachine();
}
@Override
public boolean start() {
sharedFSProviderMap.clear();
for (final SharedFSProvider provider : sharedFSProviders) {
sharedFSProviderMap.put(provider.getName(), provider);
provider.configure();
}
_executor.scheduleWithFixedDelay(new SharedFSGarbageCollector(), SharedFSCleanupInterval.value(), SharedFSCleanupInterval.value(), TimeUnit.SECONDS);
return true;
}
public boolean stop() {
_executor.shutdown();
return true;
}
@Override
public List<SharedFSProvider> getSharedFSProviders() {
return sharedFSProviders;
}
@Override
public boolean stateTransitTo(SharedFS sharedFS, Event event) {
try {
return sharedFSStateMachine.transitTo(sharedFS, event, null, sharedFSDao);
} catch (NoTransitionException e) {
String message = String.format("State transit error for Shared FileSystem %s [%s] due to exception: %s.",
sharedFS.getName(), sharedFS.getId(), e.getMessage());
logger.error(message, e);
throw new CloudRuntimeException(message, e);
}
}
@Override
public void setSharedFSProviders(List<SharedFSProvider> sharedFSProviders) {
this.sharedFSProviders = sharedFSProviders;
}
@Override
public SharedFSProvider getSharedFSProvider(String sharedFSProviderName) {
if (sharedFSProviderMap.containsKey(sharedFSProviderName)) {
return sharedFSProviderMap.get(sharedFSProviderName);
}
throw new CloudRuntimeException("Invalid Shared FileSystem provider name!");
}
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
int wrks = SharedFSExpungeWorkers.value();
_executor = Executors.newScheduledThreadPool(wrks, new NamedThreadFactory("SharedFS-Scavenger"));
return true;
}
public List<Class<?>> getCommands() {
final List<Class<?>> cmdList = new ArrayList<>();
if (SharedFSFeatureEnabled.value()) {
cmdList.add(ListSharedFSProvidersCmd.class);
cmdList.add(CreateSharedFSCmd.class);
cmdList.add(ListSharedFSCmd.class);
cmdList.add(UpdateSharedFSCmd.class);
cmdList.add(DestroySharedFSCmd.class);
cmdList.add(RestartSharedFSCmd.class);
cmdList.add(StartSharedFSCmd.class);
cmdList.add(StopSharedFSCmd.class);
cmdList.add(ChangeSharedFSDiskOfferingCmd.class);
cmdList.add(ChangeSharedFSServiceOfferingCmd.class);
cmdList.add(RecoverSharedFSCmd.class);
cmdList.add(ExpungeSharedFSCmd.class);
}
return cmdList;
}
private DataCenter validateAndGetZone(Long zoneId) {
DataCenter zone = dataCenterDao.findById(zoneId);
if (zone == null) {
throw new InvalidParameterValueException("Unable to find zone by ID: " + zoneId);
}
if (zone.getAllocationState() == Grouping.AllocationState.Disabled) {
throw new PermissionDeniedException(String.format("Cannot perform this operation, zone ID: %s is currently disabled", zone.getUuid()));
}
if (zone.getNetworkType() == DataCenter.NetworkType.Basic ||
zone.isSecurityGroupEnabled()) {
throw new PermissionDeniedException("This feature is supported only on Advanced Zone without security groups");
}
return zone;
}
private void validateDiskOffering(Long diskOfferingId, Long size, Long minIops, Long maxIops, DataCenter zone) {
Account caller = CallContext.current().getCallingAccount();
DiskOfferingVO diskOffering = diskOfferingDao.findById(diskOfferingId);
configMgr.checkDiskOfferingAccess(caller, diskOffering, zone);
if (!diskOffering.isCustomized() && size != null) {
throw new InvalidParameterValueException("Size provided with a non-custom disk offering");
}
if ((diskOffering.isCustomizedIops() == null || diskOffering.isCustomizedIops() == false) && (minIops != null || maxIops != null)) {
throw new InvalidParameterValueException("Iops provided with a non-custom-iops disk offering");
}
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_CREATE, eventDescription = "Allocating Shared FileSystem", create = true)
public SharedFS allocSharedFS(CreateSharedFSCmd cmd) {
Account caller = CallContext.current().getCallingAccount();
long ownerId = cmd.getEntityOwnerId();
Account owner = accountMgr.getActiveAccountById(ownerId);
accountMgr.checkAccess(caller, null, true, owner);
DataCenter zone = validateAndGetZone(cmd.getZoneId());
Long diskOfferingId = cmd.getDiskOfferingId();
Long size = cmd.getSize();
Long minIops = cmd.getMinIops();
Long maxIops = cmd.getMaxIops();
validateDiskOffering(diskOfferingId, size, minIops, maxIops, zone);
SharedFSProvider provider = getSharedFSProvider(cmd.getSharedFSProviderName());
SharedFSLifeCycle lifeCycle = provider.getSharedFSLifeCycle();
lifeCycle.checkPrerequisites(zone, cmd.getServiceOfferingId());
NetworkVO networkVO = networkDao.findById(cmd.getNetworkId());
if (networkVO == null) {
throw new InvalidParameterValueException("Unable to find a network with Network ID " + cmd.getNetworkId());
}
if (networkVO.getGuestType() == Network.GuestType.Shared) {
if ((networkVO.getAclType() != ControlledEntity.ACLType.Account) ||
(cmd.getDomainId() != null && (networkVO.getDomainId() != cmd.getDomainId())) ||
(networkVO.getAccountId() != owner.getAccountId())) {
throw new InvalidParameterValueException("Shared network which is not Account scoped and not belonging to the same account can not be used to create a Shared FileSystem");
}
}
SharedFS.FileSystemType fsType;
try {
fsType = SharedFS.FileSystemType.valueOf(cmd.getFsFormat().toUpperCase());
} catch (IllegalArgumentException ex) {
throw new InvalidParameterValueException("Invalid File system format specified. Supported formats are EXT4 and XFS");
}
if (sharedFSDao.findSharedFSByNameAccountDomain(cmd.getName(), owner.getAccountId(), cmd.getDomainId()) != null) {
throw new InvalidParameterValueException("There already exists a Shared FileSystem with this name for the given account and domain.");
}
SharedFSVO sharedFS = new SharedFSVO(cmd.getName(), cmd.getDescription(), owner.getDomainId(),
ownerId, cmd.getZoneId(), cmd.getSharedFSProviderName(), SharedFS.Protocol.NFS,
fsType, cmd.getServiceOfferingId());
return sharedFSDao.persist(sharedFS);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_CREATE, eventDescription = "Deploying Shared FileSystem", async = true)
public SharedFS deploySharedFS(CreateSharedFSCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, OperationTimedoutException {
SharedFSVO sharedFS = sharedFSDao.findById(cmd.getEntityId());
Long diskOfferingId = cmd.getDiskOfferingId();
Long size = cmd.getSize();
Long minIops = cmd.getMinIops();
Long maxIops = cmd.getMaxIops();
SharedFSProvider provider = getSharedFSProvider(cmd.getSharedFSProviderName());
SharedFSLifeCycle lifeCycle = provider.getSharedFSLifeCycle();
Pair<Long, Long> result = null;
try {
result = lifeCycle.deploySharedFS(sharedFS, cmd.getNetworkId(), diskOfferingId, size, minIops, maxIops);
} catch (Exception ex) {
stateTransitTo(sharedFS, Event.OperationFailed);
throw ex;
}
sharedFS.setVolumeId(result.first());
sharedFS.setVmId(result.second());
sharedFSDao.update(sharedFS.getId(), sharedFS);
stateTransitTo(sharedFS, Event.OperationSucceeded);
return sharedFS;
}
private SharedFS startSharedFS(SharedFS sharedFS) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException {
SharedFSProvider provider = getSharedFSProvider(sharedFS.getFsProviderName());
SharedFSLifeCycle lifeCycle = provider.getSharedFSLifeCycle();
try {
stateTransitTo(sharedFS, Event.StartRequested);
lifeCycle.startSharedFS(sharedFS);
} catch (Exception ex) {
stateTransitTo(sharedFS, Event.OperationFailed);
throw ex;
}
stateTransitTo(sharedFS, Event.OperationSucceeded);
sharedFS = sharedFSDao.findById(sharedFS.getId());
return sharedFS;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_START, eventDescription = "Starting Shared FileSystem")
public SharedFS startSharedFS(Long sharedFSId) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
SharedFSVO sharedFS = sharedFSDao.findById(sharedFSId);
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, sharedFS);
Set<State> validStates = new HashSet<>(List.of(State.Stopped));
if (!validStates.contains(sharedFS.getState())) {
throw new InvalidParameterValueException("Shared FileSystem can be started only if it is in the " + validStates.toString() + " state");
}
return startSharedFS(sharedFS);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_STOP, eventDescription = "Stopping Shared FileSystem")
public SharedFS stopSharedFS(Long sharedFSId, Boolean forced) {
SharedFSVO sharedFS = sharedFSDao.findById(sharedFSId);
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, sharedFS);
Set<State> validStates = new HashSet<>(List.of(State.Ready));
if (!validStates.contains(sharedFS.getState())) {
throw new InvalidParameterValueException("Shared FileSystem can be stopped only if it is in the " + State.Ready + " state");
}
SharedFSProvider provider = getSharedFSProvider(sharedFS.getFsProviderName());
SharedFSLifeCycle lifeCycle = provider.getSharedFSLifeCycle();
try {
stateTransitTo(sharedFS, Event.StopRequested);
lifeCycle.stopSharedFS(sharedFS, forced);
} catch (Exception e) {
stateTransitTo(sharedFS, Event.OperationFailed);
throw e;
}
stateTransitTo(sharedFS, Event.OperationSucceeded);
return sharedFS;
}
private SharedFSVO reDeploySharedFS(SharedFSVO sharedFS) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
SharedFSProvider provider = getSharedFSProvider(sharedFS.getFsProviderName());
SharedFSLifeCycle lifeCycle = provider.getSharedFSLifeCycle();
boolean result = lifeCycle.reDeploySharedFS(sharedFS);
return (result ? sharedFS : null);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_RESTART, eventDescription = "Restarting Shared FileSystem", async = true)
public SharedFS restartSharedFS(Long sharedFSId, boolean cleanup) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
SharedFSVO sharedFS = sharedFSDao.findById(sharedFSId);
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, sharedFS);
Set<State> validStates = new HashSet<>(List.of(State.Ready, State.Stopped));
if (!validStates.contains(sharedFS.getState())) {
throw new InvalidParameterValueException("Restart Shared FileSystem can be done only if the shared filesystem is in " + validStates.toString() + " states");
}
if (!cleanup) {
if (!sharedFS.getState().equals(State.Stopped)) {
stopSharedFS(sharedFS.getId(), false);
}
return startSharedFS(sharedFS.getId());
} else {
return reDeploySharedFS(sharedFS);
}
}
private Pair<List<Long>, Integer> searchForSharedFSIdsAndCount(ListSharedFSCmd cmd) {
Account caller = CallContext.current().getCallingAccount();
List<Long> permittedAccounts = new ArrayList<>();
Long id = cmd.getId();
String name = cmd.getName();
Long networkId = cmd.getNetworkId();
Long diskOfferingId = cmd.getDiskOfferingId();
Long serviceOfferingId = cmd.getServiceOfferingId();
String keyword = cmd.getKeyword();
Long startIndex = cmd.getStartIndex();
Long pageSize = cmd.getPageSizeVal();
Long zoneId = cmd.getZoneId();
String accountName = cmd.getAccountName();
Long domainId = cmd.getDomainId();
Long projectId = cmd.getProjectId();
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(domainId, cmd.isRecursive(), null);
accountMgr.buildACLSearchParameters(caller, id, accountName, projectId, permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
domainId = domainIdRecursiveListProject.first();
Boolean isRecursive = domainIdRecursiveListProject.second();
Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
Filter searchFilter = new Filter(SharedFSVO.class, "created", false, startIndex, pageSize);
SearchBuilder<SharedFSVO> sharedFSSearchBuilder = sharedFSDao.createSearchBuilder();
sharedFSSearchBuilder.select(null, SearchCriteria.Func.DISTINCT, sharedFSSearchBuilder.entity().getId()); // select distinct
accountMgr.buildACLSearchBuilder(sharedFSSearchBuilder, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
sharedFSSearchBuilder.and("id", sharedFSSearchBuilder.entity().getId(), SearchCriteria.Op.EQ);
sharedFSSearchBuilder.and("name", sharedFSSearchBuilder.entity().getName(), SearchCriteria.Op.EQ);
sharedFSSearchBuilder.and("dataCenterId", sharedFSSearchBuilder.entity().getDataCenterId(), SearchCriteria.Op.EQ);
if (keyword != null) {
sharedFSSearchBuilder.and("keywordName", sharedFSSearchBuilder.entity().getName(), SearchCriteria.Op.LIKE);
}
sharedFSSearchBuilder.and("serviceOfferingId", sharedFSSearchBuilder.entity().getServiceOfferingId(), SearchCriteria.Op.EQ);
if (diskOfferingId != null) {
SearchBuilder<VolumeVO> volSearch = volumeDao.createSearchBuilder();
volSearch.and("diskOfferingId", volSearch.entity().getDiskOfferingId(), SearchCriteria.Op.EQ);
sharedFSSearchBuilder.join("volSearch", volSearch, volSearch.entity().getId(), sharedFSSearchBuilder.entity().getVolumeId(), JoinBuilder.JoinType.INNER);
}
if (networkId != null) {
SearchBuilder<NicVO> nicSearch = nicDao.createSearchBuilder();
nicSearch.and("networkId", nicSearch.entity().getNetworkId(), SearchCriteria.Op.EQ);
sharedFSSearchBuilder.join("nicSearch", nicSearch, nicSearch.entity().getInstanceId(), sharedFSSearchBuilder.entity().getVmId(), JoinBuilder.JoinType.INNER);
}
SearchCriteria<SharedFSVO> sc = sharedFSSearchBuilder.create();
accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
if (keyword != null) {
sc.setParameters("keywordName", "%" + keyword + "%");
}
if (name != null) {
sc.setParameters("name", name);
}
if (id != null) {
sc.setParameters("id", id);
}
if (zoneId != null) {
sc.setParameters("dataCenterId", zoneId);
}
if (serviceOfferingId != null) {
sc.setParameters("serviceOfferingId", serviceOfferingId);
}
if (diskOfferingId != null) {
sc.setJoinParameters("volSearch", "diskOfferingId", diskOfferingId);
}
if (networkId != null) {
sc.setJoinParameters("nicSearch", "networkId", networkId);
}
Pair<List<SharedFSVO>, Integer> result = sharedFSDao.searchAndCount(sc, searchFilter);
List<Long> idsArray = result.first().stream().map(SharedFSVO::getId).collect(Collectors.toList());
return new Pair<List<Long>, Integer>(idsArray, result.second());
}
private Pair<List<SharedFSJoinVO>, Integer> searchForSharedFSInternal(ListSharedFSCmd cmd) {
Pair<List<Long>, Integer> sharedFSIds = searchForSharedFSIdsAndCount(cmd);
if (sharedFSIds.second() == 0) {
return new Pair<List<SharedFSJoinVO>, Integer>(null, 0);
}
List<SharedFSJoinVO> sharedFSs = sharedFSJoinDao.searchByIds(sharedFSIds.first().toArray(new Long[0]));
return new Pair<List<SharedFSJoinVO>, Integer>(sharedFSs, sharedFSIds.second());
}
@Override
public ListResponse<SharedFSResponse> searchForSharedFS(ResponseObject.ResponseView respView, ListSharedFSCmd cmd) {
Pair<List<SharedFSJoinVO>, Integer> result = searchForSharedFSInternal(cmd);
ListResponse<SharedFSResponse> response = new ListResponse<>();
if (cmd.getRetrieveOnlyResourceCount()) {
response.setResponses(new ArrayList<>(), result.second());
return response;
}
Account caller = CallContext.current().getCallingAccount();
if (accountMgr.isRootAdmin(caller.getId())) {
respView = ResponseObject.ResponseView.Full;
}
List<SharedFSResponse> sharedFSRespons = null;
if (result.second() > 0) {
sharedFSRespons = sharedFSJoinDao.createSharedFSResponses(respView, result.first().toArray(new SharedFSJoinVO[result.first().size()]));
}
response.setResponses(sharedFSRespons, result.second());
return response;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_UPDATE, eventDescription = "Updating Shared FileSystem")
public SharedFS updateSharedFS(UpdateSharedFSCmd cmd) {
Long id = cmd.getId();
String name = cmd.getName();
String description = cmd.getDescription();
SharedFSVO sharedFS = sharedFSDao.findById(id);
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, sharedFS);
if (name != null) {
sharedFS.setName(name);
}
if (description != null) {
sharedFS.setDescription(description);
}
sharedFSDao.update(sharedFS.getId(), sharedFS);
return sharedFS;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_CHANGE_DISK_OFFERING, eventDescription = "Change Shared FileSystem disk offering")
public SharedFS changeSharedFSDiskOffering(ChangeSharedFSDiskOfferingCmd cmd) throws ResourceAllocationException {
SharedFSVO sharedFS = sharedFSDao.findById(cmd.getId());
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, sharedFS);
Set<State> validStates = new HashSet<>(List.of(State.Ready, State.Stopped));
if (!validStates.contains(sharedFS.getState())) {
throw new InvalidParameterValueException("Disk offering of the Shared FileSystem can be changed only if it is in " + validStates.toString() + " states");
}
Long diskOfferingId = cmd.getDiskOfferingId();
Long newSize = cmd.getSize();
Long newMinIops = cmd.getMinIops();
Long newMaxIops = cmd.getMaxIops();
DataCenter zone = validateAndGetZone(sharedFS.getDataCenterId());
validateDiskOffering(diskOfferingId, newSize, newMinIops, newMaxIops, zone);
volumeApiService.changeDiskOfferingForVolumeInternal(sharedFS.getVolumeId(), diskOfferingId, newSize, newMinIops, newMaxIops, true, false);
return sharedFS;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_CHANGE_SERVICE_OFFERING, eventDescription = "Change Shared FileSystem service offering")
public SharedFS changeSharedFSServiceOffering(ChangeSharedFSServiceOfferingCmd cmd) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ManagementServerException, VirtualMachineMigrationException {
SharedFSVO sharedFS = sharedFSDao.findById(cmd.getId());
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, sharedFS);
Set<State> validStates = new HashSet<>(List.of(State.Stopped));
if (!validStates.contains(sharedFS.getState())) {
throw new InvalidParameterValueException("Service offering of the Shared FileSystem can be changed only if it is in " + validStates.toString() + " state");
}
SharedFSProvider provider = getSharedFSProvider(sharedFS.getFsProviderName());
SharedFSLifeCycle lifeCycle = provider.getSharedFSLifeCycle();
DataCenter zone = validateAndGetZone(sharedFS.getDataCenterId());
lifeCycle.checkPrerequisites(zone, cmd.getServiceOfferingId());
sharedFS = sharedFSDao.findById(cmd.getId());
if (lifeCycle.changeSharedFSServiceOffering(sharedFS, cmd.getServiceOfferingId())) {
sharedFS.setServiceOfferingId(cmd.getServiceOfferingId());
sharedFSDao.update(sharedFS.getId(), sharedFS);
return sharedFS;
} else {
return null;
}
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_DESTROY, eventDescription = "Destroy Shared FileSystem")
public Boolean destroySharedFS(DestroySharedFSCmd cmd) {
Long sharedFSId = cmd.getId();
Boolean expunge = cmd.isExpunge();
SharedFSVO sharedFS = sharedFSDao.findById(sharedFSId);
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, sharedFS);
if (sharedFS.getState().equals(State.Ready) && cmd.isForced()) {
stopSharedFS(sharedFS.getId(), false);
}
sharedFS = sharedFSDao.findById(sharedFSId);
Set<State> validStates = new HashSet<>(List.of(State.Stopped, State.Error));
if (!validStates.contains(sharedFS.getState())) {
throw new InvalidParameterValueException("Shared FileSystem can be destroyed only if it is in the " + validStates.toString() + " states");
}
stateTransitTo(sharedFS, Event.DestroyRequested);
if (expunge || sharedFS.getState().equals(State.Error)) {
deleteSharedFS(sharedFSId);
}
return true;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_RECOVER, eventDescription = "Recover Shared FileSystem")
public SharedFS recoverSharedFS(Long sharedFSId) {
SharedFSVO sharedFS = sharedFSDao.findById(sharedFSId);
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, sharedFS);
if (!State.Destroyed.equals(sharedFS.getState())) {
throw new InvalidParameterValueException("The Shared FileSystem should be in the Destroyed state to be recovered");
}
stateTransitTo(sharedFS, Event.RecoveryRequested);
sharedFS = sharedFSDao.findById(sharedFSId);
return sharedFS;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SHAREDFS_EXPUNGE, eventDescription = "Expunge Shared FileSystem")
public void deleteSharedFS(Long sharedFSId) {
SharedFSVO sharedFS = sharedFSDao.findById(sharedFSId);
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, sharedFS);
Set<State> validStates = new HashSet<>(List.of(State.Destroyed, State.Expunging, State.Error));
if (!validStates.contains(sharedFS.getState())) {
throw new InvalidParameterValueException("Shared FileSystem can be expunged only if it is in the " + validStates.toString() + " states");
}
SharedFSProvider provider = getSharedFSProvider(sharedFS.getFsProviderName());
SharedFSLifeCycle lifeCycle = provider.getSharedFSLifeCycle();
stateTransitTo(sharedFS, Event.ExpungeOperation);
lifeCycle.deleteSharedFS(sharedFS);
stateTransitTo(sharedFS, Event.OperationSucceeded);
sharedFSDao.remove(sharedFS.getId());
}
@Override
public String getConfigComponentName() {
return SharedFSService.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[]{
SharedFSCleanupInterval,
SharedFSCleanupDelay,
SharedFSFeatureEnabled,
SharedFSExpungeWorkers
};
}
protected class SharedFSGarbageCollector extends ManagedContextRunnable {
public SharedFSGarbageCollector() {
}
@Override
protected void runInContext() {
try {
logger.trace("Shared FileSystem Garbage Collection Thread is running.");
cleanupSharedFS(true);
} catch (Exception e) {
logger.error("Caught the following Exception", e);
}
}
}
public void cleanupSharedFS(boolean recurring) {
GlobalLock scanLock = GlobalLock.getInternLock("sharedfsservice.cleanup");
try {
if (scanLock.lock(30)) {
try {
List<SharedFSVO> sharedFSs = sharedFSDao.listSharedFSToBeDestroyed(new Date(System.currentTimeMillis() - ((long)SharedFSCleanupDelay.value() << 10)));
for (SharedFSVO sharedFS : sharedFSs) {
try {
stateTransitTo(sharedFS, Event.ExpungeOperation);
deleteSharedFS(sharedFS.getId());
} catch (Exception e) {
stateTransitTo(sharedFS, Event.OperationFailed);
logger.error(String.format("Unable to expunge Shared FileSystem [%s] due to: [%s].", sharedFS.getUuid(), e.getMessage()));
}
}
} finally {
scanLock.unlock();
}
}
} finally {
scanLock.releaseRef();
}
}
}

View File

@ -0,0 +1,38 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs.query.dao;
import java.util.List;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO;
import com.cloud.utils.db.GenericDao;
public interface SharedFSJoinDao extends GenericDao<SharedFSJoinVO, Long> {
SharedFSJoinVO newSharedFSView(SharedFS sharedFS);
SharedFSResponse newSharedFSResponse(ResponseObject.ResponseView view, SharedFSJoinVO sharedFSView);
List<SharedFSResponse> createSharedFSResponses(ResponseObject.ResponseView view, SharedFSJoinVO... sharedFSs);
List<SharedFSJoinVO> searchByIds(Long...sharedFSIds);
}

View File

@ -0,0 +1,187 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs.query.dao;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.NicResponse;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO;
import com.cloud.api.ApiDBUtils;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.storage.Storage;
import com.cloud.storage.VolumeStats;
import com.cloud.user.VmDiskStatisticsVO;
import com.cloud.user.dao.VmDiskStatisticsDao;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.NicVO;
import com.cloud.vm.dao.NicDao;
public class SharedFSJoinDaoImpl extends GenericDaoBase<SharedFSJoinVO, Long> implements SharedFSJoinDao {
@Inject
NicDao nicDao;
@Inject
NetworkDao networkDao;
@Inject
private VmDiskStatisticsDao vmDiskStatsDao;
private final SearchBuilder<SharedFSJoinVO> fsSearch;
private final SearchBuilder<SharedFSJoinVO> fsIdInSearch;
protected SharedFSJoinDaoImpl() {
fsSearch = createSearchBuilder();
fsSearch.and("id", fsSearch.entity().getId(), SearchCriteria.Op.EQ);
fsSearch.done();
fsIdInSearch = createSearchBuilder();
fsIdInSearch.and("idIN", fsIdInSearch.entity().getId(), SearchCriteria.Op.IN);
fsIdInSearch.done();
}
@Override
public SharedFSJoinVO newSharedFSView(SharedFS sharedFS) {
SearchCriteria<SharedFSJoinVO> sc = fsSearch.create();
sc.setParameters("id", sharedFS.getId());
List<SharedFSJoinVO> sharedFSs = searchIncludingRemoved(sc, null, null, false);
assert sharedFSs != null && sharedFSs.size() == 1 : "No shared filesystem found for id " + sharedFS.getId();
return sharedFSs.get(0);
}
@Override
public SharedFSResponse newSharedFSResponse(ResponseObject.ResponseView view, SharedFSJoinVO sharedFS) {
SharedFSResponse response = new SharedFSResponse();
response.setId(sharedFS.getUuid());
response.setName(sharedFS.getName());
response.setDescription(sharedFS.getDescription());
response.setState(sharedFS.getState().toString());
response.setProvider(sharedFS.getProvider());
response.setFilesystem(sharedFS.getFsType().toString());
response.setPath(SharedFS.getSharedFSPath());
response.setObjectName(SharedFS.class.getSimpleName().toLowerCase());
response.setZoneId(sharedFS.getZoneUuid());
response.setZoneName(sharedFS.getZoneName());
response.setVirtualMachineId(sharedFS.getInstanceUuid());
if (sharedFS.getInstanceState() != null) {
response.setVirtualMachineState(sharedFS.getInstanceState().toString());
}
response.setVolumeId(sharedFS.getVolumeUuid());
response.setVolumeName(sharedFS.getVolumeName());
response.setStoragePoolId(sharedFS.getPoolUuid());
response.setStoragePoolName(sharedFS.getPoolName());
final List<NicVO> nics = nicDao.listByVmId(sharedFS.getInstanceId());
if (nics.size() > 0) {
for (NicVO nicVO : nics) {
final NetworkVO network = networkDao.findById(nicVO.getNetworkId());
NicResponse nicResponse = new NicResponse();
nicResponse.setId(nicVO.getUuid());
nicResponse.setNetworkid(network.getUuid());
nicResponse.setIpaddress(nicVO.getIPv4Address());
nicResponse.setNetworkName(network.getName());
nicResponse.setObjectName("nic");
response.addNic(nicResponse);
}
}
response.setAccountName(sharedFS.getAccountName());
response.setDomainId(sharedFS.getDomainUuid());
response.setDomainName(sharedFS.getDomainName());
response.setDomainName(sharedFS.getDomainPath());
response.setProjectId(sharedFS.getProjectUuid());
response.setProjectName(sharedFS.getProjectName());
response.setDiskOfferingId(sharedFS.getDiskOfferingUuid());
response.setDiskOfferingName(sharedFS.getDiskOfferingName());
response.setDiskOfferingDisplayText(sharedFS.getDiskOfferingDisplayText());
response.setIsCustomDiskOffering(sharedFS.isDiskOfferingCustom());
if (sharedFS.isDiskOfferingCustom() == true) {
response.setSize(sharedFS.getSize());
} else {
response.setSize(sharedFS.getDiskOfferingSize());
}
response.setSizeGB(sharedFS.getSize());
response.setServiceOfferingId(sharedFS.getServiceOfferingUuid());
response.setServiceOfferingName(sharedFS.getServiceOfferingName());
if (sharedFS.getProvisioningType() != null) {
response.setProvisioningType(sharedFS.getProvisioningType().toString());
}
VmDiskStatisticsVO diskStats = vmDiskStatsDao.findBy(sharedFS.getAccountId(), sharedFS.getZoneId(), sharedFS.getInstanceId(), sharedFS.getVolumeId());
if (diskStats != null) {
response.setDiskIORead(diskStats.getCurrentIORead());
response.setDiskIOWrite(diskStats.getCurrentIOWrite());
response.setDiskKbsRead((long) (diskStats.getCurrentBytesRead() / 1024.0));
response.setDiskKbsWrite((long) (diskStats.getCurrentBytesWrite() / 1024.0));
}
VolumeStats vs = null;
if (sharedFS.getVolumeFormat() == Storage.ImageFormat.VHD || sharedFS.getVolumeFormat() == Storage.ImageFormat.QCOW2 || sharedFS.getVolumeFormat() == Storage.ImageFormat.RAW) {
if (sharedFS.getVolumePath() != null) {
vs = ApiDBUtils.getVolumeStatistics(sharedFS.getVolumePath());
}
} else if (sharedFS.getVolumeFormat() == Storage.ImageFormat.OVA) {
if (sharedFS.getVolumeChainInfo() != null) {
vs = ApiDBUtils.getVolumeStatistics(sharedFS.getVolumeChainInfo());
}
}
if (vs != null) {
response.setVirtualSize(vs.getVirtualSize());
response.setPhysicalSize(vs.getPhysicalSize());
double util = (double) vs.getPhysicalSize() / vs.getVirtualSize();
DecimalFormat df = new DecimalFormat("0.0%");
response.setUtilization(df.format(util));
}
return response;
}
public List<SharedFSResponse> createSharedFSResponses(ResponseObject.ResponseView view, SharedFSJoinVO... sharedFSs) {
List<SharedFSResponse> sharedFSRespons = new ArrayList<>();
for (SharedFSJoinVO sharedFS : sharedFSs) {
sharedFSRespons.add(newSharedFSResponse(view, sharedFS));
}
return sharedFSRespons;
}
@Override
public List<SharedFSJoinVO> searchByIds(Long... sharedFSIds) {
SearchCriteria<SharedFSJoinVO> sc = fsIdInSearch.create();
sc.setParameters("idIN", sharedFSIds);
return search(sc, null, null, false);
}
}

View File

@ -0,0 +1,320 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.storage.sharedfs.query.vo;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFS.State;
import com.cloud.api.query.vo.BaseViewVO;
import com.cloud.storage.Storage;
import com.cloud.vm.VirtualMachine;
@Entity
@Table(name = "shared_filesystem_view")
public class SharedFSJoinVO extends BaseViewVO implements InternalIdentity, Identity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "name")
private String name;
@Column(name = "description")
private String description;
@Column(name = "state")
@Enumerated(value = EnumType.STRING)
private State state;
@Column(name = "provider")
private String provider;
@Column(name = "fs_type")
@Enumerated(EnumType.STRING)
SharedFS.FileSystemType fsType;
@Column(name = "size")
private Long size;
@Column(name = "zone_id")
private long zoneId;
@Column(name = "zone_uuid")
private String zoneUuid;
@Column(name = "zone_name")
private String zoneName;
@Column(name = "account_id")
private long accountId;
@Column(name = "instance_id")
private long instanceId;
@Column(name = "instance_uuid")
private String instanceUuid;
@Column(name = "instance_name")
private String instanceName;
@Column(name = "instance_state")
@Enumerated(value = EnumType.STRING)
private VirtualMachine.State instanceState;
@Column(name = "volume_id")
private long volumeId;
@Column(name = "volume_uuid")
private String volumeUuid;
@Column(name = "volume_name")
private String volumeName;
@Column(name = "provisioning_type")
@Enumerated(EnumType.STRING)
Storage.ProvisioningType provisioningType;
@Column(name = "volume_format")
@Enumerated(EnumType.STRING)
private Storage.ImageFormat volumeFormat;
@Column(name = "volume_path")
private String volumePath;
@Column(name = "volume_chain_info")
private String volumeChainInfo;
@Column(name = "pool_uuid")
private String poolUuid;
@Column(name = "pool_name")
private String poolName;
@Column(name = "account_name")
private String accountName;
@Column(name = "project_uuid")
private String projectUuid;
@Column(name = "project_name")
private String projectName;
@Column(name = "domain_uuid")
private String domainUuid;
@Column(name = "domain_name")
private String domainName;
@Column(name = "domain_path")
private String domainPath;
@Column(name = "service_offering_uuid")
private String serviceOfferingUuid;
@Column(name = "service_offering_name")
private String serviceOfferingName;
@Column(name = "disk_offering_uuid")
private String diskOfferingUuid;
@Column(name = "disk_offering_name")
private String diskOfferingName;
@Column(name = "disk_offering_display_text")
private String diskOfferingDisplayText;
@Column(name = "disk_offering_size")
private long diskOfferingSize;
@Column(name = "disk_offering_custom")
private boolean diskOfferingCustom;
public SharedFSJoinVO() {
}
@Override
public long getId() {
return id;
}
@Override
public String getUuid() {
return uuid;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public State getState() {
return state;
}
public String getProvider() {
return provider;
}
public SharedFS.FileSystemType getFsType() {
return fsType;
}
public Long getSize() {
return size;
}
public long getZoneId() {
return zoneId;
}
public long getAccountId() {
return accountId;
}
public String getZoneUuid() {
return zoneUuid;
}
public String getZoneName() {
return zoneName;
}
public long getInstanceId() {
return instanceId;
}
public String getInstanceUuid() {
return instanceUuid;
}
public String getInstanceName() {
return instanceName;
}
public VirtualMachine.State getInstanceState() {
return instanceState;
}
public long getVolumeId() {
return volumeId;
}
public String getVolumeUuid() {
return volumeUuid;
}
public String getVolumeName() {
return volumeName;
}
public Storage.ProvisioningType getProvisioningType() {
return provisioningType;
}
public Storage.ImageFormat getVolumeFormat() {
return volumeFormat;
}
public String getVolumePath() {
return volumePath;
}
public String getVolumeChainInfo() {
return volumeChainInfo;
}
public String getPoolUuid() {
return poolUuid;
}
public String getPoolName() {
return poolName;
}
public String getAccountName() {
return accountName;
}
public String getProjectUuid() {
return projectUuid;
}
public String getProjectName() {
return projectName;
}
public String getDomainUuid() {
return domainUuid;
}
public String getDomainName() {
return domainName;
}
public String getDomainPath() {
return domainPath;
}
public String getServiceOfferingUuid() {
return serviceOfferingUuid;
}
public String getServiceOfferingName() {
return serviceOfferingName;
}
public String getDiskOfferingUuid() {
return diskOfferingUuid;
}
public String getDiskOfferingName() {
return diskOfferingName;
}
public String getDiskOfferingDisplayText() {
return diskOfferingDisplayText;
}
public long getDiskOfferingSize() {
return diskOfferingSize;
}
public boolean isDiskOfferingCustom() {
return diskOfferingCustom;
}
}

View File

@ -2107,6 +2107,13 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
throw new UnsupportedServiceException("Unmanage VM is currently allowed for guest VMs only");
}
if (vmVO.getType().equals(VirtualMachine.Type.User)) {
UserVmVO userVm = userVmDao.findById(vmId);
if (UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem VM");
}
}
performUnmanageVMInstancePrechecks(vmVO);
Long hostId = findSuitableHostId(vmVO);

View File

@ -370,4 +370,8 @@
<bean id="vnfTemplateManager" class="org.apache.cloudstack.storage.template.VnfTemplateManagerImpl" />
<bean id="volumeImportUnmanageManager" class="org.apache.cloudstack.storage.volume.VolumeImportUnmanageManagerImpl" />
<bean id="sharedFSServiceImpl" class="org.apache.cloudstack.storage.sharedfs.SharedFSServiceImpl">
<property name="sharedFSProviders" value="#{sharedFSProvidersRegistry.registered}" />
</bean>
</beans>

View File

@ -474,44 +474,44 @@ public class VolumeApiServiceImplTest {
// Negative test - try to attach non-root non-datadisk volume
@Test(expected = InvalidParameterValueException.class)
public void attachIncorrectDiskType() throws NoSuchFieldException, IllegalAccessException {
volumeApiServiceImpl.attachVolumeToVM(1L, 5L, 0L);
volumeApiServiceImpl.attachVolumeToVM(1L, 5L, 0L, false);
}
// Negative test - attach root volume to running vm
@Test(expected = InvalidParameterValueException.class)
public void attachRootDiskToRunningVm() throws NoSuchFieldException, IllegalAccessException {
volumeApiServiceImpl.attachVolumeToVM(1L, 6L, 0L);
volumeApiServiceImpl.attachVolumeToVM(1L, 6L, 0L, false);
}
// Negative test - attach root volume to non-xen vm
@Test(expected = InvalidParameterValueException.class)
public void attachRootDiskToHyperVm() throws NoSuchFieldException, IllegalAccessException {
volumeApiServiceImpl.attachVolumeToVM(3L, 6L, 0L);
volumeApiServiceImpl.attachVolumeToVM(3L, 6L, 0L, false);
}
// Negative test - attach root volume from the managed data store
@Test(expected = InvalidParameterValueException.class)
public void attachRootDiskOfManagedDataStore() throws NoSuchFieldException, IllegalAccessException {
volumeApiServiceImpl.attachVolumeToVM(2L, 7L, 0L);
volumeApiServiceImpl.attachVolumeToVM(2L, 7L, 0L, false);
}
// Negative test - root volume can't be attached to the vm already having a root volume attached
@Test(expected = InvalidParameterValueException.class)
public void attachRootDiskToVmHavingRootDisk() throws NoSuchFieldException, IllegalAccessException {
volumeApiServiceImpl.attachVolumeToVM(4L, 6L, 0L);
volumeApiServiceImpl.attachVolumeToVM(4L, 6L, 0L, false);
}
// Negative test - root volume in uploaded state can't be attached
@Test(expected = InvalidParameterValueException.class)
public void attachRootInUploadedState() throws NoSuchFieldException, IllegalAccessException {
volumeApiServiceImpl.attachVolumeToVM(2L, 8L, 0L);
volumeApiServiceImpl.attachVolumeToVM(2L, 8L, 0L, false);
}
// Positive test - attach ROOT volume in correct state, to the vm not having root volume attached
@Test
public void attachRootVolumePositive() throws NoSuchFieldException, IllegalAccessException {
thrown.expect(NullPointerException.class);
volumeApiServiceImpl.attachVolumeToVM(2L, 6L, 0L);
volumeApiServiceImpl.attachVolumeToVM(2L, 6L, 0L, false);
}
// Negative test - attach data volume, to the vm on non-kvm hypervisor
@ -520,7 +520,7 @@ public class VolumeApiServiceImplTest {
DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class);
when(diskOffering.getEncrypt()).thenReturn(true);
when(_diskOfferingDao.findById(anyLong())).thenReturn(diskOffering);
volumeApiServiceImpl.attachVolumeToVM(2L, 10L, 1L);
volumeApiServiceImpl.attachVolumeToVM(2L, 10L, 1L, false);
}
// Positive test - attach data volume, to the vm on kvm hypervisor
@ -530,7 +530,7 @@ public class VolumeApiServiceImplTest {
DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class);
when(diskOffering.getEncrypt()).thenReturn(true);
when(_diskOfferingDao.findById(anyLong())).thenReturn(diskOffering);
volumeApiServiceImpl.attachVolumeToVM(4L, 10L, 1L);
volumeApiServiceImpl.attachVolumeToVM(4L, 10L, 1L, false);
}
// volume not Ready
@ -640,7 +640,7 @@ public class VolumeApiServiceImplTest {
when(_dcDao.findById(anyLong())).thenReturn(zoneWithDisabledLocalStorage);
when(zoneWithDisabledLocalStorage.isLocalStorageEnabled()).thenReturn(true);
try {
volumeApiServiceImpl.attachVolumeToVM(2L, 9L, null);
volumeApiServiceImpl.attachVolumeToVM(2L, 9L, null, false);
} catch (InvalidParameterValueException e) {
Assert.assertEquals(e.getMessage(), ("primary storage resource limit check failed"));
}

View File

@ -264,4 +264,5 @@ public class MockNetworkDaoImpl extends GenericDaoBase<NetworkVO, Long> implemen
public List<NetworkVO> getAllPersistentNetworksFromZone(long dataCenterId) {
return null;
}
}

View File

@ -0,0 +1,665 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSDiskOfferingCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSServiceOfferingCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.CreateSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.DestroySharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.UpdateSharedFSCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.storage.sharedfs.dao.SharedFSDao;
import org.apache.cloudstack.storage.sharedfs.query.dao.SharedFSJoinDao;
import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.configuration.ConfigurationManager;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import com.cloud.network.Network;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.org.Grouping;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.Pair;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.vm.NicVO;
import com.cloud.vm.dao.NicDao;
@RunWith(MockitoJUnitRunner.class)
public class SharedFSServiceImplTest {
@Mock
private AccountManager accountMgr;
@Mock
private SharedFSDao sharedFSDao;
@Mock
private SharedFSJoinDao sharedFSJoinDao;
@Mock
private DataCenterDao dataCenterDao;
@Mock
private DiskOfferingDao diskOfferingDao;
@Mock
VolumeDao volumeDao;
@Mock
NicDao nicDao;
@Mock
NetworkDao networkDao;
@Mock
private ConfigurationManager configMgr;
@Mock
private VolumeApiService volumeApiService;
@Mock
private SharedFSProvider provider;
@Mock
private SharedFSLifeCycle lifeCycle;
@Spy
@InjectMocks
private SharedFSServiceImpl sharedFSServiceImpl;
private static final long s_ownerId = 1L;
private static final long s_zoneId = 2L;
private static final long s_diskOfferingId = 3L;
private static final long s_serviceOfferingId = 4L;
private static final long s_domainId = 5L;
private static final long s_volumeId = 6L;
private static final long s_vmId = 7L;
private static final long s_networkId = 8L;
private static final long s_sharedFSId = 9L;
private static final long s_size = 10L;
private static final long s_minIops = 1000L;
private static final long s_maxIops = 2000L;
private static final String s_providerName = "SHAREDFSVM";
private static final String s_fsFormat = "EXT4";
private static final String s_name = "TestSharedFS";
private static final String s_description = "Test Description";
@Mock
Account owner;
@Mock
protected StateMachine2<SharedFS.State, SharedFS.Event, SharedFS> _stateMachine;
private MockedStatic<CallContext> callContextMocked;
private AutoCloseable closeable;
@Before
public void setUp() {
closeable = MockitoAnnotations.openMocks(this);
callContextMocked = mockStatic(CallContext.class);
CallContext callContextMock = mock(CallContext.class);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
when(callContextMock.getCallingAccount()).thenReturn(owner);
when(accountMgr.getActiveAccountById(s_ownerId)).thenReturn(owner);
Map<String, SharedFSProvider> mockProviderMap = new HashMap<>();
mockProviderMap.put(s_providerName, provider);
ReflectionTestUtils.setField(sharedFSServiceImpl, "sharedFSProviderMap", mockProviderMap);
when(sharedFSServiceImpl.getSharedFSProvider(s_providerName)).thenReturn(provider);
when(provider.getSharedFSLifeCycle()).thenReturn(lifeCycle);
ReflectionTestUtils.setField(sharedFSServiceImpl, "sharedFSStateMachine", _stateMachine);
}
@After
public void tearDown() throws Exception {
callContextMocked.close();
closeable.close();
}
private CreateSharedFSCmd getMockCreateSharedFSCmd() {
CreateSharedFSCmd cmd = mock(CreateSharedFSCmd.class);
when(cmd.getEntityOwnerId()).thenReturn(s_ownerId);
when(cmd.getZoneId()).thenReturn(s_zoneId);
when(cmd.getDiskOfferingId()).thenReturn(s_diskOfferingId);
when(cmd.getSize()).thenReturn(s_size);
when(cmd.getMinIops()).thenReturn(s_minIops);
when(cmd.getMaxIops()).thenReturn(s_maxIops);
when(cmd.getSharedFSProviderName()).thenReturn(s_providerName);
when(cmd.getServiceOfferingId()).thenReturn(s_serviceOfferingId);
when(cmd.getNetworkId()).thenReturn(s_networkId);
when(cmd.getFsFormat()).thenReturn(s_fsFormat);
return cmd;
}
private SharedFSVO getMockSharedFS() {
SharedFSVO sharedFS = new SharedFSVO(s_name, s_description, s_domainId, s_ownerId, s_zoneId,
s_providerName, SharedFS.Protocol.NFS, SharedFS.FileSystemType.valueOf(s_fsFormat), s_serviceOfferingId);
return sharedFS;
}
@Test
public void testDeploySharedFS() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, NoTransitionException, OperationTimedoutException {
CreateSharedFSCmd cmd = getMockCreateSharedFSCmd();
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(0L)).thenReturn(sharedFS);
Pair<Long, Long> result = new Pair<>(s_volumeId, s_vmId);
when(lifeCycle.deploySharedFS(sharedFS, s_networkId, s_diskOfferingId, s_size, s_minIops, s_maxIops)).thenReturn(result);
when(sharedFSDao.update(sharedFS.getId(), sharedFS)).thenReturn(true);
Assert.assertEquals(sharedFSServiceImpl.deploySharedFS(cmd), sharedFS);
Assert.assertEquals(Optional.ofNullable(sharedFS.getVmId()), Optional.ofNullable(s_vmId));
Assert.assertEquals(Optional.ofNullable(sharedFS.getVolumeId()), Optional.ofNullable(s_volumeId));
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.OperationSucceeded, null, sharedFSDao);
}
@Test
public void testDeploySharedFSException() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, NoTransitionException, OperationTimedoutException {
CreateSharedFSCmd cmd = getMockCreateSharedFSCmd();
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(0L)).thenReturn(sharedFS);
when(lifeCycle.deploySharedFS(sharedFS, s_networkId, s_diskOfferingId, s_size, s_minIops, s_maxIops)).thenThrow(new CloudRuntimeException(""));
Assert.assertThrows(CloudRuntimeException.class, () -> sharedFSServiceImpl.deploySharedFS(cmd));
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.OperationFailed, null, sharedFSDao);
verify(_stateMachine, never()).transitTo(sharedFS, SharedFS.Event.OperationSucceeded, null, sharedFSDao);
}
@Test
public void testAllocSharedFS() throws NoTransitionException {
CreateSharedFSCmd cmd = getMockCreateSharedFSCmd();
when(dataCenterDao.findById(s_zoneId)).thenReturn(null);
Assert.assertThrows(InvalidParameterValueException.class, () -> sharedFSServiceImpl.allocSharedFS(cmd));
DataCenterVO zone = mock(DataCenterVO.class);
when(dataCenterDao.findById(s_zoneId)).thenReturn(zone);
when(zone.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
DiskOfferingVO diskOfferingVO = mock(DiskOfferingVO.class);
when(diskOfferingDao.findById(s_diskOfferingId)).thenReturn(diskOfferingVO);
when(diskOfferingVO.isCustomized()).thenReturn(true);
when(diskOfferingVO.isCustomizedIops()).thenReturn(true);
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "id", s_sharedFSId);
when(cmd.getNetworkId()).thenReturn(s_networkId);
NetworkVO networkVO = mock(NetworkVO.class);
when(networkVO.getGuestType()).thenReturn(Network.GuestType.Isolated);
when(networkDao.findById(s_networkId)).thenReturn(networkVO);
sharedFSServiceImpl.allocSharedFS(cmd);
Assert.assertEquals(Optional.ofNullable(sharedFS.getAccountId()), Optional.ofNullable(s_ownerId));
Assert.assertEquals(Optional.ofNullable(sharedFS.getDataCenterId()), Optional.ofNullable(s_zoneId));
Assert.assertEquals(Optional.ofNullable(sharedFS.getServiceOfferingId()), Optional.ofNullable(s_serviceOfferingId));
}
@Test
public void testAllocSharedFSInvalidZone() {
CreateSharedFSCmd cmd = getMockCreateSharedFSCmd();
when(dataCenterDao.findById(s_zoneId)).thenReturn(null);
Assert.assertThrows(InvalidParameterValueException.class, () -> sharedFSServiceImpl.allocSharedFS(cmd));
DataCenterVO zone = mock(DataCenterVO.class);
when(dataCenterDao.findById(s_zoneId)).thenReturn(zone);
when(zone.getAllocationState()).thenReturn(Grouping.AllocationState.Disabled);
Assert.assertThrows(PermissionDeniedException.class, () -> sharedFSServiceImpl.allocSharedFS(cmd));
when(zone.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
when(zone.isSecurityGroupEnabled()).thenReturn(true);
Assert.assertThrows(PermissionDeniedException.class, () -> sharedFSServiceImpl.allocSharedFS(cmd));
}
@Test
public void tesAllocSharedFSInvalidDiskOffering() {
CreateSharedFSCmd cmd = getMockCreateSharedFSCmd();
DataCenterVO zone = mock(DataCenterVO.class);
when(dataCenterDao.findById(s_zoneId)).thenReturn(zone);
when(zone.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
DiskOfferingVO diskOfferingVO = mock(DiskOfferingVO.class);
when(diskOfferingDao.findById(s_diskOfferingId)).thenReturn(diskOfferingVO);
when(diskOfferingVO.isCustomized()).thenReturn(false);
Assert.assertThrows(InvalidParameterValueException.class, () -> sharedFSServiceImpl.allocSharedFS(cmd));
when(diskOfferingVO.isCustomized()).thenReturn(true);
when(diskOfferingVO.isCustomizedIops()).thenReturn(false);
Assert.assertThrows(InvalidParameterValueException.class, () -> sharedFSServiceImpl.allocSharedFS(cmd));
}
@Test
public void testAllocSharedFSInvalidFsFormat() {
CreateSharedFSCmd cmd = getMockCreateSharedFSCmd();
DataCenterVO zone = mock(DataCenterVO.class);
when(dataCenterDao.findById(s_zoneId)).thenReturn(zone);
when(zone.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
DiskOfferingVO diskOfferingVO = mock(DiskOfferingVO.class);
when(diskOfferingDao.findById(s_diskOfferingId)).thenReturn(diskOfferingVO);
when(diskOfferingVO.isCustomized()).thenReturn(true);
when(diskOfferingVO.isCustomizedIops()).thenReturn(true);
when(cmd.getNetworkId()).thenReturn(s_networkId);
NetworkVO networkVO = mock(NetworkVO.class);
when(networkVO.getGuestType()).thenReturn(Network.GuestType.Isolated);
when(networkDao.findById(s_networkId)).thenReturn(networkVO);
when(cmd.getFsFormat()).thenReturn("ext2");
Assert.assertThrows(InvalidParameterValueException.class, () -> sharedFSServiceImpl.allocSharedFS(cmd));
}
@Test
public void testStartSharedFS() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, OperationTimedoutException, NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "id", s_sharedFSId);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Stopped);
Assert.assertEquals(sharedFSServiceImpl.startSharedFS(s_sharedFSId), sharedFS);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.StartRequested, null, sharedFSDao);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.OperationSucceeded, null, sharedFSDao);
}
@Test
public void testStartSharedFSException() throws ResourceUnavailableException, InsufficientCapacityException, OperationTimedoutException, NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Stopped);
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
doThrow(CloudRuntimeException.class).when(lifeCycle).startSharedFS(sharedFS);
Assert.assertThrows(CloudRuntimeException.class, () -> sharedFSServiceImpl.startSharedFS(s_sharedFSId));
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.StartRequested, null, sharedFSDao);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.OperationFailed, null, sharedFSDao);
}
@Test(expected = InvalidParameterValueException.class)
public void testStartSharedFSInvalidState() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, OperationTimedoutException {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Ready);
sharedFSServiceImpl.startSharedFS(s_sharedFSId);
}
@Test
public void testStopSharedFS() throws NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Ready);
Assert.assertEquals(sharedFSServiceImpl.stopSharedFS(s_sharedFSId, false), sharedFS);
verify(lifeCycle, Mockito.times(1)).stopSharedFS(any(), any());
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.StopRequested, null, sharedFSDao);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.OperationSucceeded, null, sharedFSDao);
}
@Test
public void testStopSharedFSException() throws NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Ready);
doThrow(CloudRuntimeException.class).when(lifeCycle).stopSharedFS(sharedFS, false);
Assert.assertThrows(CloudRuntimeException.class, () -> sharedFSServiceImpl.stopSharedFS(s_sharedFSId, false));
verify(lifeCycle, Mockito.times(1)).stopSharedFS(any(), any());
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.StopRequested, null, sharedFSDao);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.OperationFailed, null, sharedFSDao);
}
@Test(expected = InvalidParameterValueException.class)
public void testStopSharedFSInvalidState() {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Stopped);
sharedFSServiceImpl.stopSharedFS(s_sharedFSId, false);
}
@Test
public void testRestartSharedFSWithoutCleanup() throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "id", s_sharedFSId);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Stopped);
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
sharedFSServiceImpl.restartSharedFS(s_sharedFSId, false);
verify(lifeCycle, never()).stopSharedFS(any(), any());
verify(lifeCycle, Mockito.times(1)).startSharedFS(any());
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.StartRequested, null, sharedFSDao);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.OperationSucceeded, null, sharedFSDao);
}
@Test
public void testRestartSharedFSWithCleanup() throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "id", s_sharedFSId);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Ready);
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
DataCenterVO zone = mock(DataCenterVO.class);
when(lifeCycle.reDeploySharedFS(sharedFS)).thenReturn(true);
sharedFSServiceImpl.restartSharedFS(s_sharedFSId, true);
verify(lifeCycle, never()).stopSharedFS(any(), any());
}
@Test
public void testUpdateSharedFS() {
String newName = "New SharedFS";
String newDescription = "New SharedFS Description";
UpdateSharedFSCmd cmd = mock(UpdateSharedFSCmd.class);
when(cmd.getId()).thenReturn(s_sharedFSId);
when(cmd.getName()).thenReturn(newName);
when(cmd.getDescription()).thenReturn(newDescription);
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
sharedFSServiceImpl.updateSharedFS(cmd);
Assert.assertEquals(sharedFS.getName(), newName);
Assert.assertEquals(sharedFS.getDescription(), newDescription);
}
@Test
public void testChangeSharedFSDiskOffering() throws ResourceAllocationException {
Long newSize = 200L;
Long newMinIops = 2000L;
Long newMaxIops = 4000L;
Long newDiskOfferingId = 10L;
ChangeSharedFSDiskOfferingCmd cmd = mock(ChangeSharedFSDiskOfferingCmd.class);
when(cmd.getId()).thenReturn(s_sharedFSId);
when(cmd.getDiskOfferingId()).thenReturn(newDiskOfferingId);
when(cmd.getSize()).thenReturn(newSize);
when(cmd.getMinIops()).thenReturn(newMinIops);
when(cmd.getMaxIops()).thenReturn(newMaxIops);
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Ready);
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
DataCenterVO zone = mock(DataCenterVO.class);
when(dataCenterDao.findById(s_zoneId)).thenReturn(zone);
when(zone.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
DiskOfferingVO diskOfferingVO = mock(DiskOfferingVO.class);
when(diskOfferingDao.findById(newDiskOfferingId)).thenReturn(diskOfferingVO);
when(diskOfferingVO.isCustomized()).thenReturn(true);
when(diskOfferingVO.isCustomizedIops()).thenReturn(true);
sharedFSServiceImpl.changeSharedFSDiskOffering(cmd);
}
@Test(expected = InvalidParameterValueException.class)
public void testChangeSharedFSDiskOfferingInvalidState() throws ResourceAllocationException {
ChangeSharedFSDiskOfferingCmd cmd = mock(ChangeSharedFSDiskOfferingCmd.class);
when(cmd.getId()).thenReturn(s_sharedFSId);
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Destroyed);
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
sharedFSServiceImpl.changeSharedFSDiskOffering(cmd);
}
@Test
public void testChangeSharedFSServiceOffering() throws ResourceUnavailableException, InsufficientCapacityException, ManagementServerException, OperationTimedoutException, NoTransitionException, VirtualMachineMigrationException {
ChangeSharedFSServiceOfferingCmd cmd = mock(ChangeSharedFSServiceOfferingCmd.class);
Long newServiceOfferingId = 100L;
when(cmd.getServiceOfferingId()).thenReturn(newServiceOfferingId);
when(cmd.getId()).thenReturn(s_sharedFSId);
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "id", s_sharedFSId);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Stopped);
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
DataCenterVO zone = mock(DataCenterVO.class);
when(dataCenterDao.findById(s_zoneId)).thenReturn(zone);
when(zone.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
when(lifeCycle.changeSharedFSServiceOffering(sharedFS, newServiceOfferingId)).thenReturn(true);
sharedFSServiceImpl.changeSharedFSServiceOffering(cmd);
Assert.assertEquals(Optional.ofNullable(sharedFS.getServiceOfferingId()), Optional.ofNullable(newServiceOfferingId));
}
@Test(expected = InvalidParameterValueException.class)
public void testChangeSharedFSServiceOfferingInvalidState() throws ResourceUnavailableException, InsufficientCapacityException, ManagementServerException, OperationTimedoutException, VirtualMachineMigrationException {
ChangeSharedFSServiceOfferingCmd cmd = mock(ChangeSharedFSServiceOfferingCmd.class);
Long newServiceOfferingId = 100L;
when(cmd.getId()).thenReturn(s_sharedFSId);
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "id", s_sharedFSId);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Starting);
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
sharedFSServiceImpl.changeSharedFSServiceOffering(cmd);
}
@Test
public void testDestroySharedFS() throws NoTransitionException {
DestroySharedFSCmd cmd = mock(DestroySharedFSCmd.class);
when(cmd.getId()).thenReturn(s_sharedFSId);
when(cmd.isExpunge()).thenReturn(false);
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Stopped);
Assert.assertEquals(sharedFSServiceImpl.destroySharedFS(cmd), true);
verify(lifeCycle, never()).deleteSharedFS(any());
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.DestroyRequested, null, sharedFSDao);
}
@Test(expected = InvalidParameterValueException.class)
public void testDestroySharedFSInvalidState() {
DestroySharedFSCmd cmd = mock(DestroySharedFSCmd.class);
when(cmd.getId()).thenReturn(s_sharedFSId);
when(cmd.isExpunge()).thenReturn(false);
when(cmd.isForced()).thenReturn(false);
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Ready);
sharedFSServiceImpl.destroySharedFS(cmd);
}
@Test
public void testRecoverSharedFS() throws NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Destroyed);
Assert.assertEquals(sharedFSServiceImpl.recoverSharedFS(s_sharedFSId), sharedFS);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.RecoveryRequested, null, sharedFSDao);
}
@Test(expected = InvalidParameterValueException.class)
public void testRecoverSharedFSInvalidState() {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Expunged);
sharedFSServiceImpl.recoverSharedFS(s_sharedFSId);
}
@Test
public void testDeleteSharedFS() throws NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Destroyed);
sharedFSServiceImpl.deleteSharedFS(s_sharedFSId);
verify(lifeCycle, Mockito.times(1)).deleteSharedFS(any());
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.ExpungeOperation, null, sharedFSDao);
}
@Test (expected = CloudRuntimeException.class)
public void testDeleteSharedFSTransitionException() throws NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Destroyed);
when(_stateMachine.transitTo(sharedFS, SharedFS.Event.ExpungeOperation, null, sharedFSDao)).thenThrow(new NoTransitionException(""));
sharedFSServiceImpl.deleteSharedFS(s_sharedFSId);
}
@Test(expected = InvalidParameterValueException.class)
public void testDeleteSharedFSInvalidState() {
SharedFSVO sharedFS = getMockSharedFS();
when(sharedFSDao.findById(s_sharedFSId)).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Stopped);
sharedFSServiceImpl.deleteSharedFS(s_sharedFSId);
}
private ListSharedFSCmd getMockListSharedFSCmd() {
ListSharedFSCmd cmd = mock(ListSharedFSCmd.class);
when(cmd.getId()).thenReturn(s_sharedFSId);
when(cmd.getName()).thenReturn(s_name);
when(cmd.getZoneId()).thenReturn(s_zoneId);
when(cmd.getDiskOfferingId()).thenReturn(s_diskOfferingId);
when(cmd.getServiceOfferingId()).thenReturn(s_serviceOfferingId);
when(cmd.getAccountName()).thenReturn("account");
when(cmd.getDomainId()).thenReturn(s_domainId);
when(cmd.getNetworkId()).thenReturn(s_networkId);
return cmd;
}
@Test
public void testSearchForSharedFS() {
SearchBuilder<SharedFSVO> sb = mock(SearchBuilder.class);
when(sharedFSDao.createSearchBuilder()).thenReturn(sb);
SharedFSVO sharedFS = getMockSharedFS();
when(sb.entity()).thenReturn(sharedFS);
ReflectionTestUtils.setField(sharedFS, "id", s_sharedFSId);
VolumeVO volume = mock(VolumeVO.class);
SearchBuilder<VolumeVO> volumeSb = mock(SearchBuilder.class);
when(volumeSb.entity()).thenReturn(volume);
when(volumeDao.createSearchBuilder()).thenReturn(volumeSb);
NicVO nic = mock(NicVO.class);
SearchBuilder<NicVO> nicSb = mock(SearchBuilder.class);
when(nicSb.entity()).thenReturn(nic);
when(nicDao.createSearchBuilder()).thenReturn(nicSb);
SearchCriteria<SharedFSVO> sc = mock(SearchCriteria.class);
Mockito.when(sb.create()).thenReturn(sc);
Pair<List<SharedFSVO>, Integer> result = new Pair<>(List.of(sharedFS), 1);
when(sharedFSDao.searchAndCount(any(), any())).thenReturn(result);
SharedFSJoinVO sharedFSJoinVO = mock(SharedFSJoinVO.class);
when(sharedFSJoinDao.searchByIds(List.of(s_sharedFSId).toArray(new Long[0]))).thenReturn(List.of(sharedFSJoinVO));
when(owner.getId()).thenReturn(s_ownerId);
when(accountMgr.isRootAdmin(any())).thenReturn(true);
when(sharedFSJoinDao.createSharedFSResponses(any(), any())).thenReturn(null);
ListSharedFSCmd cmd = getMockListSharedFSCmd();
sharedFSServiceImpl.searchForSharedFS(ResponseObject.ResponseView.Restricted, cmd);
verify(sc, times(1)).setParameters("id", s_sharedFSId);
verify(sc, times(1)).setParameters("name", s_name);
verify(sc, times(1)).setParameters("dataCenterId", s_zoneId);
verify(sc, times(1)).setParameters("serviceOfferingId", s_serviceOfferingId);
verify(sc, times(1)).setJoinParameters("volSearch", "diskOfferingId", s_diskOfferingId);
verify(sc, times(1)).setJoinParameters("nicSearch", "networkId", s_networkId);
verify(sharedFSDao, times(1)).searchAndCount(any(), any());
}
@Test
public void testCleanupSharedFS() throws NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Destroyed);
when(sharedFSDao.listSharedFSToBeDestroyed(any(Date.class))).thenReturn(List.of(sharedFS));
try (MockedStatic<GlobalLock> globalLockMocked = Mockito.mockStatic(GlobalLock.class)) {
GlobalLock scanlock = mock(GlobalLock.class);
when(GlobalLock.getInternLock("sharedfsservice.cleanup")).thenReturn(scanlock);
when(scanlock.lock(30)).thenReturn(true);
sharedFSServiceImpl.cleanupSharedFS(true);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.ExpungeOperation, null, sharedFSDao);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.OperationFailed, null, sharedFSDao);
}
}
@Test
public void testCleanupSharedFSInvalidState() throws NoTransitionException {
SharedFSVO sharedFS = getMockSharedFS();
ReflectionTestUtils.setField(sharedFS, "state", SharedFS.State.Stopped);
when(sharedFSDao.listSharedFSToBeDestroyed(any(Date.class))).thenReturn(List.of(sharedFS));
try (MockedStatic<GlobalLock> globalLockMocked = Mockito.mockStatic(GlobalLock.class)) {
GlobalLock scanlock = mock(GlobalLock.class);
when(GlobalLock.getInternLock("sharedfsservice.cleanup")).thenReturn(scanlock);
when(scanlock.lock(30)).thenReturn(true);
sharedFSServiceImpl.cleanupSharedFS(true);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.ExpungeOperation, null, sharedFSDao);
verify(_stateMachine, times(1)).transitTo(sharedFS, SharedFS.Event.OperationFailed, null, sharedFSDao);
}
}
}

View File

@ -0,0 +1,152 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.sharedfs.query.dao;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.SharedFSVO;
import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.api.ApiDBUtils;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.storage.Storage;
import com.cloud.storage.VolumeStats;
import com.cloud.user.VmDiskStatisticsVO;
import com.cloud.user.dao.VmDiskStatisticsDao;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.NicVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.NicDao;
@RunWith(MockitoJUnitRunner.class)
public class SharedFSJoinDaoImplTest {
@Mock
NicDao nicDao;
@Mock
NetworkDao networkDao;
@Mock
private VmDiskStatisticsDao vmDiskStatsDao;
@Spy
@InjectMocks
SharedFSJoinDaoImpl sharedFSJoinDao;
private AutoCloseable closeable;
@Before
public void setUp() throws Exception {
closeable = MockitoAnnotations.openMocks(this);
}
@After
public void tearDown() throws Exception {
closeable.close();
}
@Test
public void testNewSharedFSView() {
SharedFSVO sharedfs = mock(SharedFSVO.class);
Long id = 1L;
when(sharedfs.getId()).thenReturn(id);
SharedFSJoinVO sharedFSJoinVO = mock(SharedFSJoinVO.class);
SearchBuilder<SharedFSVO> sb = Mockito.mock(SearchBuilder.class);
ReflectionTestUtils.setField(sharedFSJoinDao, "fsSearch", sb);
SearchCriteria<SharedFSVO> sc = Mockito.mock(SearchCriteria.class);
Mockito.when(sb.create()).thenReturn(sc);
Mockito.doReturn(List.of(sharedFSJoinVO)).when(sharedFSJoinDao).searchIncludingRemoved(
Mockito.any(SearchCriteria.class), Mockito.eq(null), Mockito.eq(null),
Mockito.eq(false));
sharedFSJoinDao.newSharedFSView(sharedfs);
Mockito.verify(sc).setParameters("id", id);
Mockito.verify(sharedFSJoinDao, Mockito.times(1)).searchIncludingRemoved(
Mockito.any(SearchCriteria.class), Mockito.eq(null), Mockito.eq(null),
Mockito.eq(false));
}
@Test
public void newSharedFSResponse() {
Long s_ownerId = 1L;
Long s_zoneId = 2L;
Long s_volumeId = 3L;
Long s_vmId = 4L;
Long s_networkId = 5L;
String s_fsFormat = "EXT4";
SharedFS.State state = SharedFS.State.Ready;
VirtualMachine.State vmState = VirtualMachine.State.Running;
Storage.ProvisioningType provisioningType = Storage.ProvisioningType.THIN;
SharedFSJoinVO sharedFSJoinVO = mock(SharedFSJoinVO.class);
when(sharedFSJoinVO.getAccountId()).thenReturn(s_ownerId);
when(sharedFSJoinVO.getZoneId()).thenReturn(s_zoneId);
when(sharedFSJoinVO.getVolumeId()).thenReturn(s_volumeId);
when(sharedFSJoinVO.getInstanceId()).thenReturn(s_vmId);
when(sharedFSJoinVO.getState()).thenReturn(state);
when(sharedFSJoinVO.getFsType()).thenReturn(SharedFS.FileSystemType.valueOf(s_fsFormat));
when(sharedFSJoinVO.getInstanceState()).thenReturn(vmState);
when(sharedFSJoinVO.getProvisioningType()).thenReturn(provisioningType);
NicVO nic = mock(NicVO.class);
NetworkVO network = mock(NetworkVO.class);
when(nic.getNetworkId()).thenReturn(s_networkId);
when(nicDao.listByVmId(s_vmId)).thenReturn(List.of(nic));
when(networkDao.findById(s_networkId)).thenReturn(network);
VmDiskStatisticsVO diskStats = mock(VmDiskStatisticsVO.class);
when(vmDiskStatsDao.findBy(s_ownerId, s_zoneId, s_vmId, s_volumeId)).thenReturn(diskStats);
VolumeStats vs = mock(VolumeStats.class);
String path = "volumepath";
when(sharedFSJoinVO.getVolumeFormat()).thenReturn(Storage.ImageFormat.QCOW2);
when(sharedFSJoinVO.getVolumePath()).thenReturn(path);
try (MockedStatic<ApiDBUtils> apiDBUtilsMocked = Mockito.mockStatic(ApiDBUtils.class)) {
when(ApiDBUtils.getVolumeStatistics(path)).thenReturn(vs);
SharedFSResponse response = sharedFSJoinDao.newSharedFSResponse(ResponseObject.ResponseView.Restricted, sharedFSJoinVO);
Assert.assertEquals(ReflectionTestUtils.getField(response, "state"), state.toString());
Assert.assertEquals(ReflectionTestUtils.getField(response, "virtualMachineState"), vmState.toString());
Assert.assertEquals(ReflectionTestUtils.getField(response, "provisioningType"), provisioningType.toString());
}
}
}

View File

@ -63,7 +63,7 @@ patch() {
export TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE)
retry=60
local patched=false
if [ "$TYPE" != "cksnode" ]; then
if [ "$TYPE" != "cksnode" ] && [ "$TYPE" != "sharedfsvm" ]; then
while [ $retry -gt 0 ]
do
if tar tf $patchfile &> /dev/null; then

View File

@ -326,7 +326,10 @@ setup_common() {
then
setup_interface "0" $ETH0_IP $ETH0_MASK $GW
fi
setup_interface "1" $ETH1_IP $ETH1_MASK $GW
if [ -n "$ETH1_IP" ]
then
setup_interface "1" $ETH1_IP $ETH1_MASK $GW
fi
if [ -n "$ETH2_IP" ]
then
setup_interface "2" $ETH2_IP $ETH2_MASK $GW

View File

@ -151,7 +151,7 @@ config_guest() {
setup_interface_sshd() {
if [ "$TYPE" != "cksnode" ]; then
if [ "$TYPE" != "cksnode" ] && [ "$TYPE" != "sharedfsvm" ]; then
log_it "Applying iptables rules"
if [ "$TYPE" != "dhcpsrvr" ]; then
cp /etc/iptables/iptables-$TYPE /etc/iptables/rules.v4
@ -206,8 +206,12 @@ setup_interface_sshd() {
else
setup_sshd $ETH1_IP "eth1"
fi
elif [ "$TYPE" == "cksnode" ]; then
setup_common eth0
elif [ "$TYPE" == "sharedfsvm" ]; then
setup_common eth0
fi
systemctl restart systemd-journald

View File

@ -49,7 +49,7 @@ then
fi
fi
if [ "$TYPE" == "cksnode" ]; then
if [ "$TYPE" == "cksnode" ] || [ "$TYPE" == "sharedfsvm" ]; then
pkill -9 dhclient
fi

View File

@ -0,0 +1,64 @@
#!/bin/bash
# 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.
. /opt/cloud/bin/setup/common.sh
setup_sharedfsvm() {
log_it "Setting up sharedfsvm"
update-alternatives --set iptables /usr/sbin/iptables-legacy
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
update-alternatives --set arptables /usr/sbin/arptables-legacy
update-alternatives --set ebtables /usr/sbin/ebtables-legacy
log_it "Setting up entry in hosts"
sed -i /$NAME/d /etc/hosts
echo "$ETH0_IP $NAME" >> /etc/hosts
# set default ssh port and restart sshd service
sed -i 's/3922/22/g' /etc/ssh/sshd_config
systemctl restart ssh
> /root/.ssh/authorized_keys
swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
log_it "Swap disabled"
echo "export PATH='$PATH:/opt/bin/'">> ~/.bashrc
disable_rpfilter
enable_fwding 0
enable_irqbalance 0
setup_ntp
dhclient -1
rm -f /etc/logrotate.d/cloud
log_it "Starting cloud-init services"
if [ -f /home/cloud/success ]; then
systemctl stop cloud-init cloud-config cloud-final
systemctl disable cloud-init cloud-config cloud-final
else
systemctl start --no-block cloud-init
systemctl start --no-block cloud-config
systemctl start --no-block cloud-final
fi
}
setup_sharedfsvm
. /opt/cloud/bin/setup/patch.sh && patch_sshd_config

View File

@ -0,0 +1,277 @@
# 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 Shared FileSystem
"""
import time
# Import Local Modules
from nose.plugins.attrib import attr
from marvin.cloudstackAPI import (createFirewallRule,
createPortForwardingRule)
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.lib.utils import (cleanup_resources,
wait_until)
from marvin.lib.base import (Account,
VirtualMachine,
Network,
SharedFS,
ServiceOffering,
NetworkOffering,
DiskOffering,
PublicIPAddress,
)
from marvin.lib.common import (get_domain,
get_zone,
get_template)
from marvin.codes import FAILED
from marvin.lib.decoratorGenerators import skipTestIf
class TestSharedFSLifecycle(cloudstackTestCase):
@classmethod
def setUpClass(cls):
cls.testClient = super(TestSharedFSLifecycle, cls).getClsTestClient()
cls.apiclient = cls.testClient.getApiClient()
cls.services = cls.testClient.getParsedTestDataConfig()
cls.domain = get_domain(cls.apiclient)
cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
cls._cleanup = []
cls.hypervisor = cls.testClient.getHypervisorInfo()
cls.hypervisorNotSupported = False
if cls.hypervisor.lower() not in ["kvm", "vmware"]:
cls.hypervisorNotSupported = True
return
cls.services["service_offering"]["name"] = 'FSVM offering';
cls.services["service_offering"]["offerha"] = True;
cls.services["service_offering"]["cpunumber"] = 2;
cls.services["service_offering"]["cpuspeed"] = 500;
cls.services["service_offering"]["memory"] = 1024;
cls.service_offering = ServiceOffering.create(
cls.apiclient,
cls.services["service_offering"]
)
cls._cleanup.append(cls.service_offering)
cls.services["disk_offering"]["disksize"] = 1;
cls.disk_offering = DiskOffering.create(
cls.apiclient,
cls.services["disk_offering"],
custom=True
)
cls._cleanup.append(cls.disk_offering)
cls.useraccount = Account.create(
cls.apiclient,
cls.services["account"],
domainid=cls.domain.id
)
cls._cleanup.append(cls.useraccount)
cls.adminaccount = Account.create(
cls.apiclient,
cls.services["account"],
domainid=cls.domain.id,
admin=True
)
cls._cleanup.append(cls.adminaccount)
cls.network_offering_isolated = NetworkOffering.create(
cls.apiclient,
cls.services["isolated_network_offering"]
)
cls.network_offering_isolated.update(cls.apiclient, state='Enabled')
cls._cleanup.append(cls.network_offering_isolated)
cls.services["network"]["name"] = "Test Network Isolated"
cls.user_network = Network.create(
cls.apiclient,
cls.services["network"],
networkofferingid=cls.network_offering_isolated.id,
domainid=cls.domain.id,
accountid=cls.adminaccount.name,
zoneid=cls.zone.id
)
cls._cleanup.insert(0, cls.user_network)
cls.public_ipaddress = None
cls.sshpublicport = 1000
cls.template = get_template(
cls.apiclient,
cls.zone.id,
cls.services["ostype"])
if cls.template == FAILED:
assert False, "get_template() failed to return template with description %s" % cls.services["ostype"]
cls.services["domainid"] = cls.domain.id
cls.services["zoneid"] = cls.zone.id
cls.services["diskofferingid"] = cls.disk_offering.id
cls.services["serviceofferingid"] = cls.service_offering.id
cls.services["networkid"] = cls.user_network.id
cls.services["account"] = cls.adminaccount.name
cls.sharedfs = SharedFS.create(
cls.apiclient,
cls.services,
name='Test Shared FileSystem 1',
size=2,
filesystem='XFS'
)
cls._cleanup.insert(0, cls.sharedfs)
cls.virtual_machine1 = VirtualMachine.create(
cls.apiclient,
cls.services["virtual_machine"],
templateid=cls.template.id,
serviceofferingid=cls.service_offering.id,
networkids=cls.user_network.id,
domainid=cls.domain.id,
accountid=cls.adminaccount.name,
zoneid=cls.zone.id
)
cls._cleanup.insert(0, cls.virtual_machine1)
cls.public_ipaddress = cls.setUpSNAT(cls, cls.user_network)
cls.debug("Public ipaddress: " + cls.public_ipaddress.ipaddress)
port = cls.setUpPortForwarding(cls, cls.virtual_machine1.id)
cls.vm1_ssh_client = cls.getSSHClient(cls, cls.virtual_machine1, port)
@classmethod
def tearDownClass(cls):
try:
cleanup_resources(cls.apiclient, cls._cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.dbclient = self.testClient.getDbConnection()
self.cleanup = []
return
def tearDown(self):
try:
self.debug("Cleaning up the resources")
cleanup_resources(self.apiclient, self.cleanup)
self.debug("Cleanup complete!")
except Exception as e:
self.debug("Warning! Exception in tearDown: %s" % e)
def setUpSNAT(self, network):
public_ipaddress = PublicIPAddress.list(
self.apiclient,
account=self.adminaccount.name,
domainid=self.domain.id,
associatednetworkid=network.id
)
createFwRule = createFirewallRule.createFirewallRuleCmd()
createFwRule.cidrlist = "0.0.0.0/0"
createFwRule.startport = 22
createFwRule.endport = 22
createFwRule.ipaddressid = public_ipaddress[0].id
createFwRule.protocol = "tcp"
self.apiclient.createFirewallRule(createFwRule)
return public_ipaddress[0]
def setUpPortForwarding(self, virtualmachineid):
createPfRule = createPortForwardingRule.createPortForwardingRuleCmd()
self.sshpublicport += 1
createPfRule.publicport = self.sshpublicport
createPfRule.privateport = 22
createPfRule.virtualmachineid = virtualmachineid
createPfRule.ipaddressid = self.public_ipaddress.id
createPfRule.protocol = "tcp"
self.apiclient.createPortForwardingRule(createPfRule)
self.debug("Successfully programmed PF rule for :%s"%self.public_ipaddress.ipaddress)
return createPfRule.publicport
def getSSHClient(self, virtual_machine, port):
try:
ssh_client = virtual_machine.get_ssh_client(ipaddress=self.public_ipaddress.ipaddress, port=port)
except Exception as e:
self.fail("SSH failed for virtual machine: %s - %s" % (virtual_machine.ipaddress, e))
return ssh_client
def mountSharedFSOnVM(self, ssh_client, sharedfs):
sharedfs_ip = sharedfs.nic[0].ipaddress
ssh_client.execute("mkdir /mnt/fs1")
cmd = "mount -t nfs -o nolock " + sharedfs_ip + ":/export /mnt/fs1"
ssh_client.execute(cmd)
@attr( tags=[ "advanced", "advancedns", "smokes"], required_hardware="true")
@skipTestIf("hypervisorNotSupported")
def test_mount_shared_fs(self):
"""Mount Shared FileSystem on two VMs and match contents
"""
self.mountSharedFSOnVM(self.vm1_ssh_client, self.sharedfs)
self.vm1_ssh_client.execute("df -Th /mnt/fs1")
self.vm1_ssh_client.execute("touch /mnt/fs1/test")
try:
self.virtual_machine2 = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
templateid=self.template.id,
serviceofferingid=self.service_offering.id,
networkids=self.user_network.id,
domainid=self.domain.id,
accountid=self.adminaccount.name,
zoneid=self.zone.id
)
except Exception as e:
self.vm1_ssh_client.execute("rm /mnt/fs1/test")
self.fail(e)
self.cleanup.append(self.virtual_machine2)
port = self.setUpPortForwarding(self.virtual_machine2.id)
ssh_client = self.getSSHClient(self.virtual_machine2, port)
self.assertIsNotNone(ssh_client)
self.mountSharedFSOnVM(ssh_client, self.sharedfs)
ssh_client.execute("df -Th /mnt/fs1")
result = ssh_client.execute("ls /mnt/fs1/test")
self.assertEqual(result[0], "/mnt/fs1/test")
@attr( tags=[ "advanced", "advancedns", "smokes"], required_hardware="true")
@skipTestIf("hypervisorNotSupported")
def test_resize_shared_fs(self):
"""Resize the shared filesystem by changing the disk offering and validate
"""
self.mountSharedFSOnVM(self.vm1_ssh_client, self.sharedfs)
result = self.vm1_ssh_client.execute("df -Th /mnt/fs1 | grep nfs")[0]
self.debug(result)
size = result.split()[-5]
self.debug("Size of the filesystem is " + size)
self.assertEqual(size, "2.0G", "SharedFS size should be 2.0G")
response = SharedFS.stop(self.sharedfs, self.apiclient)
response = SharedFS.changediskoffering(self.sharedfs, self.apiclient, self.disk_offering.id, 3)
self.debug(response)
response = SharedFS.start(self.sharedfs, self.apiclient)
time.sleep(10)
result = self.vm1_ssh_client.execute("df -Th /mnt/fs1 | grep nfs")[0]
size = result.split()[-5]
self.debug("Size of the filesystem is " + size)
self.assertEqual(size, "3.0G", "SharedFS size should be 3.0G")

View File

@ -276,6 +276,8 @@ known_categories = {
'listBuckets': 'Object Store',
'listVmsForImport': 'Virtual Machine',
'importVm': 'Virtual Machine',
'SharedFS': 'Shared FileSystem',
'SharedFileSystem': 'Shared FileSystem',
'Webhook': 'Webhook',
'Webhooks': 'Webhook',
'purgeExpungedResources': 'Resource'

View File

@ -117,6 +117,7 @@ function configure_services() {
systemctl disable vgauth
systemctl disable sshd
systemctl disable nfs-common
systemctl disable nfs-server
systemctl disable portmap
# Disable guest services which will selectively be started based on hypervisor

View File

@ -60,7 +60,7 @@ function install_packages() {
sysstat \
apache2 ssl-cert \
dnsmasq dnsmasq-utils \
nfs-common \
nfs-common nfs-server xfsprogs \
samba-common cifs-utils \
xl2tpd bcrelay ppp tdb-tools \
xenstore-utils libxenstore4 \

View File

@ -7293,3 +7293,108 @@ class Webhook:
cmd.webhookid = self.id
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
return apiclient.deleteWebhookDelivery(cmd)
class SharedFS:
def __init__(self, items):
self.__dict__.update(items)
"""Manage Shared FileSystem"""
@classmethod
def create(cls, apiclient, services, name, description=None, account=None, domainid=None, projectid=None,
size=None, zoneid=None, diskofferingid=None, serviceofferingid=None,
filesystem=None, provider=None, networkid=None):
"""Create Shared FileSystem"""
cmd = createSharedFileSystem.createSharedFileSystemCmd()
cmd.name = name
if description:
cmd.description = description
if diskofferingid:
cmd.diskofferingid = diskofferingid
elif "diskofferingid" in services:
cmd.diskofferingid = services["diskofferingid"]
if zoneid:
cmd.zoneid = zoneid
elif "zoneid" in services:
cmd.zoneid = services["zoneid"]
if account:
cmd.account = account
elif "account" in services:
cmd.account = services["account"]
if domainid:
cmd.domainid = domainid
elif "domainid" in services:
cmd.domainid = services["domainid"]
if projectid:
cmd.projectid = projectid
if size:
cmd.size = size
if networkid:
cmd.networkid = networkid
elif "networkid" in services:
cmd.networkid = services["networkid"]
if filesystem:
cmd.filesystem = filesystem
if provider:
cmd.provider = provider
if serviceofferingid:
cmd.serviceofferingid = serviceofferingid
elif "serviceofferingid" in services:
cmd.serviceofferingid = services["serviceofferingid"]
return SharedFS(apiclient.createSharedFileSystem(cmd).__dict__)
def delete(self, apiclient, expunge=True, forced=True):
"""Delete Shared FileSystem"""
cmd = destroySharedFileSystem.destroySharedFileSystemCmd()
cmd.id = self.id
cmd.expunge = expunge
cmd.forced = forced
apiclient.destroySharedFileSystem(cmd)
def stop(self, apiclient, forced=True):
"""Stop Shared FileSystem"""
cmd = stopSharedFileSystem.stopSharedFileSystemCmd()
cmd.id = self.id
cmd.forced = forced
apiclient.stopSharedFileSystem(cmd)
def start(self, apiclient):
"""Start Shared FileSystem"""
cmd = startSharedFileSystem.startSharedFileSystemCmd()
cmd.id = self.id
apiclient.startSharedFileSystem(cmd)
@classmethod
def list(cls, apiclient, **kwargs):
cmd = listSharedFileSystems.listSharedFileSystemCmd()
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
return (apiclient.listSharedFileSystems(cmd))
def update(self, apiclient, name=None, description=None):
"""Update Shared FileSystem"""
cmd = updateSharedFileSystem.updateSharedFileSystemCmd()
cmd.id = self.id
if name:
cmd.name = name
if description:
cmd.description = description
return (apiclient.updateSharedFileSystem(cmd))
def changediskoffering(self, apiclient, diskofferingid=None, size=None):
"""Change Disk Offering/Size of the Shared FileSystem"""
cmd = changeSharedFileSystemDiskOffering.changeSharedFileSystemDiskOfferingCmd()
cmd.id = self.id
cmd.diskofferingid = diskofferingid
cmd.size = size
return (apiclient.changeSharedFileSystemDiskOffering(cmd))

View File

@ -171,6 +171,8 @@
"label.action.reboot.router": "Reboot router",
"label.action.reboot.systemvm": "Reboot System VM",
"label.action.recover.volume": "Recover volume",
"label.action.resize.sharedfs": "Resize Shared FileSystem",
"label.action.restart.sharedfs": "Restart Shared FileSystem",
"label.action.recurring.snapshot": "Recurring Snapshots",
"label.action.disable.2FA.user.auth": "Disable User Two Factor Authentication",
"label.action.register.iso": "Register ISO",
@ -193,9 +195,11 @@
"label.action.secure.host": "Provision host security keys",
"label.action.set.as.source.nat.ip": "make source NAT",
"label.action.setup.2FA.user.auth": "Setup User Two Factor Authentication",
"label.action.start.sharedfs": "Start Shared FileSystem",
"label.action.start.instance": "Start Instance",
"label.action.start.router": "Start router",
"label.action.start.systemvm": "Start system VM",
"label.action.stop.sharedfs": "Stop Shared FileSystem",
"label.action.stop.instance": "Stop Instance",
"label.action.stop.router": "Stop router",
"label.action.stop.systemvm": "Stop system VM",
@ -461,6 +465,7 @@
"label.change.affinity": "Change affinity",
"label.change.ip.address": "Change IP address",
"label.change.ipaddress": "Change IP address for NIC",
"label.change.disk.offering": "Change disk offering",
"label.change.offering.for.volume": "Change disk offering for the volume",
"label.change.service.offering": "Change service offering",
"label.character": "Character",
@ -505,6 +510,7 @@
"label.complete": "Complete",
"label.compute": "Compute",
"label.compute.offerings": "Compute offerings",
"label.compute.offering.for.vm": "Compute offering for VM",
"label.computeonly.offering": "Compute only disk offering",
"label.computeonly.offering.tooltip": "Option to specify root disk related information in the compute offering or to directly link a disk offering to the compute offering",
"label.conditions": "Conditions",
@ -578,6 +584,7 @@
"label.create.instance": "Create cloud server",
"label.create.account": "Create Account",
"label.create.backup": "Start backup",
"label.create.sharedfs": "Create Shared FileSystem",
"label.create.network": "Create new Network",
"label.create.nfs.secondary.staging.storage": "Create NFS secondary staging storage",
"label.create.project": "Create project",
@ -663,6 +670,7 @@
"label.delete.domain": "Delete domain",
"label.delete.events": "Delete events",
"label.delete.f5": "Delete F5",
"label.destroy.sharedfs": "Destroy Shared FileSystem",
"label.delete.gateway": "Delete gateway",
"label.delete.icon": "Delete icon",
"label.delete.instance.group": "Delete Instance group",
@ -916,6 +924,7 @@
"label.existing": "Existing",
"label.execute": "Execute",
"label.expunge": "Expunge",
"label.expunge.sharedfs": "Expunge Shared FileSystem",
"label.expungevmgraceperiod": "Expunge Instance grace period (in sec)",
"label.expunged": "Expunged",
"label.expunging": "Expunging",
@ -936,6 +945,8 @@
"label.filename": "File Name",
"label.fetched": "Fetched",
"label.files": "Alternate files to retrieve",
"label.sharedfs": "Shared FileSystem",
"label.filesystem": "Filesystem",
"label.filter": "Filter",
"label.filter.annotations.all": "All comments",
"label.filter.annotations.self": "Created by me",
@ -967,6 +978,7 @@
"label.friday": "Friday",
"label.from": "from",
"label.from.lb": "from LB",
"label.fsprovidername": "Shared FileSystem Provider",
"label.full": "Full",
"label.full.path": "Full path",
"label.fwdeviceid": "ID",
@ -1420,6 +1432,7 @@
"label.monitor.url": "URL Path",
"label.monthly": "Monthly",
"label.more.access.dashboard.ui": "More about accessing dashboard UI",
"label.mount.sharedfs": "Mount Shared FileSystem via NFS",
"label.move.down.row": "Move down one row",
"label.move.to.bottom": "Move to bottom",
"label.move.to.top": "Move to top",
@ -1774,6 +1787,7 @@
"label.reboot": "Reboot",
"label.recent.deliveries": "Recent deliveries",
"label.receivedbytes": "Bytes received",
"label.recover.sharedfs": "Recover Shared FileSystem",
"label.recover.vm": "Recover Instance",
"label.recovering": "Recovering",
"label.redeliver": "Redeliver",
@ -2289,6 +2303,7 @@
"label.updateinsequence": "Update in sequence",
"label.update.autoscale.vmgroup": "Update AutoScaling Group",
"label.update.condition": "Update condition",
"label.update.sharedfs": "Update Shared FileSystem",
"label.update.instance.group": "Update Instance group",
"label.update.ip.range": "Update IP range",
"label.update.network": "Update Network",
@ -2448,8 +2463,9 @@
"label.volgroup": "Volume group",
"label.volume": "Volume",
"label.volume.empty": "No data volumes attached to this Instance",
"label.volume.volumefileupload.description": "Click or drag file to this area to upload.",
"label.volume.encryption.support": "Volume Encryption Supported",
"label.volume.metrics": "Volume Metrics",
"label.volume.volumefileupload.description": "Click or drag file to this area to upload.",
"label.volumechecksum": "MD5 checksum",
"label.volumechecksum.description": "Use the hash that you created at the start of the volume upload procedure.",
"label.volumefileupload": "Local file",
@ -2567,6 +2583,7 @@
"message.action.delete.volume": "Please confirm that you want to delete this volume. Note: this will not delete any Snapshots of this volume.",
"message.action.delete.vpn.user": "Please confirm that you want to delete the VPN user.",
"message.action.delete.zone": "Please confirm that you want to delete this zone.",
"message.action.destroy.sharedfs": "Please confirm that you want to destroy this Shared FileSystem.<br><b>Caution: This will delete all the data of the Shared FileSystem as well.</b>",
"message.action.destroy.instance": "Please confirm that you want to destroy the Instance.",
"message.action.destroy.instance.with.backups": "Please confirm that you want to destroy the Instance. There may be backups associated with the Instance which will not be deleted.",
"message.action.destroy.systemvm": "Please confirm that you want to destroy the System VM.",
@ -2592,6 +2609,7 @@
"message.action.enable.physical.network": "Please confirm that you want to enable this physical Network.",
"message.action.enable.pod": "Please confirm that you want to enable this pod.",
"message.action.enable.zone": "Please confirm that you want to enable this zone.",
"message.action.expunge.sharedfs": "Please confirm that you want to expunge this Shared FileSystem.",
"message.action.expunge.instance": "Please confirm that you want to expunge this Instance.",
"message.action.expunge.instance.with.backups": "Please confirm that you want to expunge this Instance. There may be backups associated with the Instance which will not be deleted.",
"message.action.host.enable.maintenance.mode": "Enabling maintenance mode will cause a live migration of all running Instances on this host to any available host.",
@ -2609,6 +2627,7 @@
"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.sharedfs": "Please confirm that you would like to recover this Shared FileSystem.",
"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.",
@ -2616,6 +2635,7 @@
"message.action.remove.routing.policy": "Please confirm that you want to remove Routing Policy from this Network",
"message.action.release.reserved.ip": "Please confirm that you want to release this reserved IP.",
"message.action.reserve.ip": "Please confirm that you want to reserve this IP.",
"message.action.restart.sharedfs": "Please confirm that you want to restart this Shared FileSystem. This will cause a downtime to the user.<br>Restart with cleanup will re-initialize the Shared FileSystem VM without affecting the installed file system.",
"message.action.revert.snapshot": "Please confirm that you want to revert the owning volume to this Snapshot.",
"message.action.router.health.checks": "Health checks result will be fetched from router.",
"message.action.router.health.checks.disabled.warning": "Please enable router health checks.",
@ -2624,9 +2644,11 @@
"message.action.secondary.storage.read.write": "Please confirm that you want to make this secondary storage read write.",
"message.action.secure.host": "This will restart the host agent and libvirtd process after applying new X509 certificates, please confirm?",
"message.action.settings.warning.vm.running": "Please stop the Instance to access settings.",
"message.action.start.sharedfs": "Please confirm that you want to start this Shared FileSystem.",
"message.action.start.instance": "Please confirm that you want to start this Instance.",
"message.action.start.router": "Please confirm that you want to start this router.",
"message.action.start.systemvm": "Please confirm that you want to start this system VM.",
"message.action.stop.sharedfs": "Please confirm that you want to stop this Shared FileSystem.",
"message.action.stop.instance": "Please confirm that you want to stop this Instance.",
"message.action.stop.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to stop this router.",
"message.action.stop.systemvm": "Please confirm that you want to stop this system VM.",
@ -2719,13 +2741,17 @@
"message.backup.restore": "Please confirm that you want to restore the Instance backup?",
"message.cancel.shutdown": "Please confirm that you would like to cancel the shutdown on this Management server. It will resume accepting any new Async Jobs.",
"message.certificate.upload.processing": "Certificate upload in progress",
"message.change.disk.offering.sharedfs.failed": "Failed to change disk offering for the Shared FileSystem.",
"message.change.disk.offering.sharedfs.processing": "Changing disk offering for the Shared FileSystem.",
"message.change.offering.confirm": "Please confirm that you wish to change the service offering of this virtual Instance.",
"message.change.offering.for.volume": "Successfully changed offering for the volume",
"message.change.offering.for.volume.failed": "Change offering for the volume failed",
"message.change.offering.for.volume.processing": "Changing offering for the volume...",
"message.change.offering.processing": "Changing offering for the volume...",
"message.change.password": "Please change your password.",
"message.change.scope.failed": "Scope change failed",
"message.change.scope.processing": "Scope change in progress",
"message.change.service.offering.sharedfs.failed": "Failed to change service offering for the Shared FileSystem.",
"message.change.service.offering.sharedfs.processing": "Changing service offering for the Shared FileSystem.",
"message.cluster.dedicated": "Cluster Dedicated",
"message.cluster.dedication.released": "Cluster dedication released.",
"message.config.health.monitor.failed": "Configure Health Monitor failed",
@ -2744,7 +2770,9 @@
"message.confirm.archive.selected.alerts": "Please confirm you would like to archive the selected alerts",
"message.confirm.archive.selected.events": "Please confirm you would like to archive the selected events",
"message.confirm.attach.disk": "Are you sure you want to attach disk?",
"message.confirm.change.disk.offering.for.sharedfs": "Please confirm that you want to change the disk offering for the Shared FileSystem. This might migrate the underlying volume to a different storage pool if required.",
"message.confirm.change.offering.for.volume": "Please confirm that you want to change disk offering for the volume",
"message.confirm.change.service.offering.for.sharedfs": "Please confirm that you want to change the service offering for the Shared FileSystem.",
"message.confirm.configure.ovs": "Are you sure you want to configure Ovs?",
"message.confirm.delete.acl.list": "Are you sure you want to delete this ACL list?",
"message.confirm.delete.bigswitchbcf": "Please confirm that you would like to delete this BigSwitch BCF Controller.",
@ -2795,6 +2823,8 @@
"message.create.bucket.failed": "Failed to create bucket.",
"message.create.bucket.processing": "Bucket creation in progress",
"message.create.compute.offering": "Compute offering created",
"message.create.sharedfs.failed": "Failed to create Shared FileSystem.",
"message.create.sharedfs.processing": "Shared FileSystem creation in progress.",
"message.create.tungsten.public.network": "Create Tungsten-Fabric public Network",
"message.create.internallb": "Creating internal LB",
"message.create.internallb.failed": "Failed to create internal LB.",
@ -3124,6 +3154,7 @@
"message.instances.unmanaged": "Instances not controlled by CloudStack.",
"message.instances.migrate.vmware": "Instances that can be migrated from VMware.",
"message.interloadbalance.not.return.elementid": "error: listInternalLoadBalancerElements API doesn't return internal LB element ID.",
"message.ip.address": "IP address : ",
"message.ip.address.changes.effect.after.vm.restart": "IP address changes takes effect only after Instance restart.",
"message.ip.v6.prefix.delete": "IPv6 prefix deleted",
"message.iso.desc": "Disc image containing data or bootable media for OS.",
@ -3203,6 +3234,7 @@
"message.offering.internet.protocol.warning": "WARNING: IPv6 supported Networks use static routing and will require upstream routes to be configured manually.",
"message.offering.ipv6.warning": "Please refer documentation for creating IPv6 enabled Network/VPC offering <a href='http://docs.cloudstack.apache.org/en/latest/plugins/ipv6.html#isolated-network-and-vpc-tier'>IPv6 support in CloudStack - Isolated Networks and VPC Network Tiers</a>",
"message.ovf.configurations": "OVF configurations available for the selected appliance. Please select the desired value. Incompatible compute offerings will get disabled.",
"message.path": "Path : ",
"message.path.description": "NFS: exported path from the server. VMFS: /datacenter name/datastore name. SharedMountPoint: path where primary storage is mounted, such as /mnt/primary.",
"message.please.confirm.remove.ssh.key.pair": "Please confirm that you want to remove this SSH key pair.",
"message.please.confirm.remove.user.data": "Please confirm that you want to remove this Userdata",
@ -3287,6 +3319,7 @@
"message.select.temporary.storage.instance.conversion": "(Optional) Select a Storage temporary destination for the converted disks through virt-v2v",
"message.select.zone.description": "Select type of zone basic/advanced.",
"message.select.zone.hint": "This is the type of zone deployment that you want to use. Basic zone: provides a single Network where each Instance is assigned an IP directly from the Network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering). Advanced zone: For more sophisticated Network topologies. This Network model provides the most flexibility in defining guest Networks and providing custom Network offerings such as firewall, VPN, or load balancer support.",
"message.server": "Server : ",
"message.server.description": "NFS, iSCSI, or PreSetup: IP address or DNS name of the storage device. VMWare PreSetup: IP address or DNS name of the vCenter server. Linstor: http(s) url of the linstor-controller.",
"message.set.default.nic": "Please confirm that you would like to make this NIC the default for this Instance.",
"message.set.default.nic.manual": "Please manually update the default NIC on the Instance now.",
@ -3353,6 +3386,7 @@
"message.success.copy.clipboard": "Successfully copied to clipboard",
"message.success.create.account": "Successfully created Account",
"message.success.create.bucket": "Successfully created bucket",
"message.success.create.sharedfs": "Successfully created Shared FileSystem",
"message.success.create.internallb": "Successfully created Internal Load Balancer",
"message.success.create.isolated.network": "Successfully created isolated Network",
"message.success.create.keypair": "Successfully created SSH key pair",
@ -3419,6 +3453,7 @@
"message.success.unmanage.volume": "Successfully unmanaged Volume",
"message.success.update.bucket": "Successfully updated bucket",
"message.success.update.condition": "Successfully updated condition",
"message.success.update.sharedfs": "Successfully updated Shared FileSystem",
"message.success.update.ipaddress": "Successfully updated IP address",
"message.success.update.iprange": "Successfully updated IP range",
"message.success.update.kubeversion": "Successfully updated Kubernetes supported version",

View File

@ -697,7 +697,7 @@ export default {
'/project', '/account', 'buckets', 'objectstore',
'/zone', '/pod', '/cluster', '/host', '/storagepool', '/imagestore', '/systemvm', '/router', '/ilbvm', '/annotation',
'/computeoffering', '/systemoffering', '/diskoffering', '/backupoffering', '/networkoffering', '/vpcoffering',
'/tungstenfabric', '/oauthsetting', '/guestos', '/guestoshypervisormapping', '/webhook', 'webhookdeliveries', '/quotatariff'].join('|'))
'/tungstenfabric', '/oauthsetting', '/guestos', '/guestoshypervisormapping', '/webhook', 'webhookdeliveries', '/quotatariff', '/sharedfs'].join('|'))
.test(this.$route.path)
},
enableGroupAction () {
@ -705,7 +705,7 @@ export default {
'vmsnapshot', 'backup', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway', 'vnfapp',
'project', 'account', 'systemvm', 'router', 'computeoffering', 'systemoffering',
'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment', 'buckets',
'webhook', 'webhookdeliveries'
'webhook', 'webhookdeliveries', 'sharedfs'
].includes(this.$route.name)
},
getDateAtTimeZone (date, timezone) {

View File

@ -303,7 +303,7 @@ export default {
}
if (['zoneid', 'domainid', 'imagestoreid', 'storageid', 'state', 'account', 'hypervisor', 'level',
'clusterid', 'podid', 'groupid', 'entitytype', 'accounttype', 'systemvmtype', 'scope', 'provider',
'type', 'scope', 'managementserverid', 'serviceofferingid', 'diskofferingid', 'usagetype', 'restartrequired'].includes(item)
'type', 'scope', 'managementserverid', 'serviceofferingid', 'diskofferingid', 'networkid', 'usagetype', 'restartrequired'].includes(item)
) {
type = 'list'
} else if (item === 'tags') {
@ -437,6 +437,7 @@ export default {
let managementServerIdIndex = -1
let serviceOfferingIndex = -1
let diskOfferingIndex = -1
let networkIndex = -1
let usageTypeIndex = -1
let volumeIndex = -1
@ -524,6 +525,12 @@ export default {
promises.push(await this.fetchDiskOfferings(searchKeyword))
}
if (arrayField.includes('networkid')) {
networkIndex = this.fields.findIndex(item => item.name === 'networkid')
this.fields[networkIndex].loading = true
promises.push(await this.fetchNetworks(searchKeyword))
}
if (arrayField.includes('usagetype')) {
usageTypeIndex = this.fields.findIndex(item => item.name === 'usagetype')
this.fields[usageTypeIndex].loading = true
@ -618,6 +625,14 @@ export default {
this.fields[diskOfferingIndex].opts = this.sortArray(diskOfferings[0].data)
}
}
if (networkIndex > -1) {
const networks = response.filter(item => item.type === 'networkid')
if (networks && networks.length > 0) {
this.fields[networkIndex].opts = this.sortArray(networks[0].data)
}
}
if (usageTypeIndex > -1) {
const usageTypes = response.filter(item => item.type === 'usagetype')
if (usageTypes?.length > 0) {
@ -661,6 +676,9 @@ export default {
if (diskOfferingIndex > -1) {
this.fields[diskOfferingIndex].loading = false
}
if (networkIndex > -1) {
this.fields[networkIndex].loading = false
}
if (usageTypeIndex > -1) {
this.fields[usageTypeIndex].loading = false
}
@ -851,6 +869,19 @@ export default {
})
})
},
fetchNetworks (searchKeyword) {
return new Promise((resolve, reject) => {
api('listNetworks', { listAll: true, keyword: searchKeyword }).then(json => {
const networks = json.listnetworksresponse.network
resolve({
type: 'networkid',
data: networks
})
}).catch(error => {
reject(error.response.headers['x-description'])
})
})
},
fetchAlertTypes () {
if (this.alertTypes.length > 0) {
return new Promise((resolve, reject) => {

View File

@ -108,6 +108,7 @@ export default {
docHelp: 'adminguide/virtual_machines.html#changing-the-vm-name-os-or-group',
dataView: true,
popup: true,
show: (record) => { return record.vmtype !== 'sharedfsvm' },
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/EditVM.vue')))
},
{
@ -169,7 +170,7 @@ export default {
message: 'message.reinstall.vm',
dataView: true,
popup: true,
show: (record) => { return ['Running', 'Stopped'].includes(record.state) },
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && record.vmtype !== 'sharedfsvm' },
disabled: (record) => { return record.hostcontrolstate === 'Offline' },
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ReinstallVm.vue')))
},
@ -181,9 +182,9 @@ export default {
dataView: true,
args: ['virtualmachineid', 'name', 'description', 'snapshotmemory', 'quiescevm'],
show: (record) => {
return ((['Running'].includes(record.state) && record.hypervisor !== 'LXC') ||
return (((['Running'].includes(record.state) && record.hypervisor !== 'LXC') ||
(['Stopped'].includes(record.state) && ((record.hypervisor !== 'KVM' && record.hypervisor !== 'LXC') ||
(record.hypervisor === 'KVM' && record.pooltype === 'PowerFlex'))))
(record.hypervisor === 'KVM' && record.pooltype === 'PowerFlex')))) && record.vmtype !== 'sharedfsvm')
},
disabled: (record) => { return record.hostcontrolstate === 'Offline' && record.hypervisor === 'KVM' },
mapping: {
@ -214,7 +215,7 @@ export default {
docHelp: 'adminguide/virtual_machines.html#backup-offerings',
dataView: true,
args: ['virtualmachineid', 'backupofferingid'],
show: (record) => { return !record.backupofferingid },
show: (record) => { return !record.backupofferingid && record.vmtype !== 'sharedfsvm' },
mapping: {
virtualmachineid: {
value: (record, params) => { return record.id }
@ -229,7 +230,7 @@ export default {
docHelp: 'adminguide/virtual_machines.html#creating-vm-backups',
dataView: true,
args: ['virtualmachineid'],
show: (record) => { return record.backupofferingid },
show: (record) => { return record.backupofferingid && record.vmtype !== 'sharedfsvm' },
mapping: {
virtualmachineid: {
value: (record, params) => { return record.id }
@ -243,7 +244,7 @@ export default {
docHelp: 'adminguide/virtual_machines.html#creating-vm-backups',
dataView: true,
popup: true,
show: (record) => { return record.backupofferingid },
show: (record) => { return record.backupofferingid && record.vmtype !== 'sharedfsvm' },
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/BackupScheduleWizard.vue'))),
mapping: {
virtualmachineid: {
@ -262,7 +263,7 @@ export default {
docHelp: 'adminguide/virtual_machines.html#restoring-vm-backups',
dataView: true,
args: ['virtualmachineid', 'forced'],
show: (record) => { return record.backupofferingid },
show: (record) => { return record.backupofferingid && record.vmtype !== 'sharedfsvm' },
mapping: {
virtualmachineid: {
value: (record, params) => { return record.id }
@ -276,7 +277,7 @@ export default {
docHelp: 'adminguide/templates.html#attaching-an-iso-to-a-vm',
dataView: true,
popup: true,
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && !record.isoid },
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && !record.isoid && record.vmtype !== 'sharedfsvm' },
disabled: (record) => { return record.hostcontrolstate === 'Offline' || record.hostcontrolstate === 'Maintenance' },
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/AttachIso.vue')))
},
@ -293,7 +294,7 @@ export default {
}
return args
},
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && 'isoid' in record && record.isoid },
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && 'isoid' in record && record.isoid && record.vmtype !== 'sharedfsvm' },
disabled: (record) => { return record.hostcontrolstate === 'Offline' || record.hostcontrolstate === 'Maintenance' },
mapping: {
virtualmachineid: {
@ -308,7 +309,7 @@ export default {
docHelp: 'adminguide/virtual_machines.html#change-affinity-group-for-an-existing-vm',
dataView: true,
args: ['affinitygroupids'],
show: (record) => { return ['Stopped'].includes(record.state) },
show: (record) => { return ['Stopped'].includes(record.state) && record.vmtype !== 'sharedfsvm' },
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ChangeAffinity'))),
popup: true
},
@ -318,7 +319,7 @@ export default {
label: 'label.scale.vm',
docHelp: 'adminguide/virtual_machines.html#how-to-dynamically-scale-cpu-and-ram',
dataView: true,
show: (record) => { return ['Stopped'].includes(record.state) || (['Running'].includes(record.state) && record.hypervisor !== 'LXC') },
show: (record) => { return (['Stopped'].includes(record.state) || (['Running'].includes(record.state) && record.hypervisor !== 'LXC')) && record.vmtype !== 'sharedfsvm' },
disabled: (record) => { return record.state === 'Running' && !record.isdynamicallyscalable },
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ScaleVM.vue')))
@ -369,7 +370,7 @@ export default {
message: 'message.desc.reset.ssh.key.pair',
docHelp: 'adminguide/virtual_machines.html#resetting-ssh-keys',
dataView: true,
show: (record) => { return ['Stopped'].includes(record.state) },
show: (record) => { return ['Stopped'].includes(record.state) && record.vmtype !== 'sharedfsvm' },
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ResetSshKeyPair')))
},
@ -380,7 +381,7 @@ export default {
message: 'message.desc.reset.userdata',
docHelp: 'adminguide/virtual_machines.html#resetting-userdata',
dataView: true,
show: (record) => { return ['Stopped'].includes(record.state) },
show: (record) => { return ['Stopped'].includes(record.state) && record.vmtype !== 'sharedfsvm' },
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ResetUserData')))
},
@ -391,7 +392,7 @@ export default {
dataView: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/AssignInstance'))),
popup: true,
show: (record) => { return ['Stopped'].includes(record.state) }
show: (record) => { return ['Stopped'].includes(record.state) && record.vmtype !== 'sharedfsvm' }
},
{
api: 'recoverVirtualMachine',
@ -399,7 +400,7 @@ export default {
label: 'label.recover.vm',
message: 'message.recover.vm',
dataView: true,
show: (record, store) => { return ['Destroyed'].includes(record.state) && store.features.allowuserexpungerecovervm }
show: (record, store) => { return ['Destroyed'].includes(record.state) && store.features.allowuserexpungerecovervm && record.vmtype !== 'sharedfsvm' }
},
{
api: 'unmanageVirtualMachine',
@ -407,7 +408,7 @@ export default {
label: 'label.action.unmanage.virtualmachine',
message: 'message.action.unmanage.virtualmachine',
dataView: true,
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && ['VMware', 'KVM'].includes(record.hypervisor) }
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && ['VMware', 'KVM'].includes(record.hypervisor) && record.vmtype !== 'sharedfsvm' }
},
{
api: 'expungeVirtualMachine',
@ -416,7 +417,7 @@ export default {
message: (record) => { return record.backupofferingid ? 'message.action.expunge.instance.with.backups' : 'message.action.expunge.instance' },
docHelp: 'adminguide/virtual_machines.html#deleting-vms',
dataView: true,
show: (record, store) => { return ['Destroyed', 'Expunging'].includes(record.state) && store.features.allowuserexpungerecovervm }
show: (record, store) => { return ['Destroyed', 'Expunging'].includes(record.state) && store.features.allowuserexpungerecovervm && record.vmtype !== 'sharedfsvm' }
},
{
api: 'destroyVirtualMachine',
@ -432,7 +433,7 @@ export default {
},
popup: true,
groupMap: (selection, values) => { return selection.map(x => { return { id: x, expunge: values.expunge } }) },
show: (record) => { return ['Running', 'Stopped', 'Error'].includes(record.state) },
show: (record) => { return ['Running', 'Stopped', 'Error'].includes(record.state) && record.vmtype !== 'sharedfsvm' },
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/DestroyVM.vue')))
}
]

View File

@ -543,6 +543,149 @@ export default {
groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
}
]
},
{
name: 'sharedfs',
title: 'label.sharedfs',
icon: 'file-text-outlined',
permission: ['listSharedFileSystems'],
resourceType: 'SharedFS',
columns: () => {
const fields = ['name', 'state', 'sizegb']
const metricsFields = ['diskkbsread', 'diskkbswrite', 'utilization', 'physicalsize']
if (store.getters.metrics) {
fields.push(...metricsFields)
}
if (store.getters.userInfo.roletype === 'Admin') {
fields.push('storage')
fields.push('account')
} else if (store.getters.userInfo.roletype === 'DomainAdmin') {
fields.push('account')
}
if (store.getters.listAllProjects) {
fields.push('project')
}
fields.push('zonename')
return fields
},
details: ['id', 'name', 'description', 'state', 'filesystem', 'diskofferingdisplaytext', 'ipaddress', 'sizegb', 'provider', 'protocol', 'provisioningtype', 'utilization', 'diskkbsread', 'diskkbswrite', 'diskioread', 'diskiowrite', 'account', 'domain', 'created'],
tabs: [{
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/SharedFSTab.vue')))
}],
searchFilters: () => {
var filters = ['name', 'zoneid', 'domainid', 'account', 'networkid', 'serviceofferingid', 'diskofferingid']
return filters
},
actions: [
{
api: 'createSharedFileSystem',
icon: 'plus-outlined',
docHelp: 'adminguide/storage.html#creating-a-new-file-share',
label: 'label.create.sharedfs',
listView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateSharedFS.vue')))
},
{
api: 'updateSharedFileSystem',
icon: 'edit-outlined',
docHelp: 'adminguide/storage.html#lifecycle-operations',
label: 'label.update.sharedfs',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/UpdateSharedFS.vue')))
},
{
api: 'startSharedFileSystem',
icon: 'caret-right-outlined',
label: 'label.action.start.sharedfs',
message: 'message.action.start.sharedfs',
docHelp: 'adminguide/storage.html#lifecycle-operations',
dataView: true,
popup: true,
groupAction: true,
groupMap: (selection) => { return selection.map(x => { return { id: x } }) },
show: (record) => { return ['Stopped'].includes(record.state) }
},
{
api: 'stopSharedFileSystem',
icon: 'poweroff-outlined',
label: 'label.action.stop.sharedfs',
message: 'message.action.stop.sharedfs',
docHelp: 'adminguide/storage.html#lifecycle-operations',
dataView: true,
popup: true,
groupAction: true,
groupMap: (selection, values) => { return selection.map(x => { return { id: x, forced: values.forced } }) },
args: ['forced'],
show: (record) => { return ['Ready'].includes(record.state) }
},
{
api: 'restartSharedFileSystem',
icon: 'reload-outlined',
docHelp: 'adminguide/storage.html#lifecycle-operations',
label: 'label.action.restart.sharedfs',
message: 'message.action.restart.sharedfs',
dataView: true,
popup: true,
args: ['cleanup'],
show: (record) => { return ['Stopped', 'Ready', 'Detached'].includes(record.state) }
},
{
api: 'changeSharedFileSystemDiskOffering',
icon: 'swap-outlined',
docHelp: 'adminguide/storage.html#lifecycle-operations',
label: 'label.change.disk.offering',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/ChangeSharedFSDiskOffering.vue'))),
show: (record) => { return ['Stopped', 'Ready'].includes(record.state) }
},
{
api: 'changeSharedFileSystemServiceOffering',
icon: 'arrows-alt-outlined',
docHelp: 'adminguide/storage.html#lifecycle-operations',
label: 'label.change.service.offering',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/ChangeSharedFSServiceOffering.vue'))),
show: (record) => { return ['Stopped'].includes(record.state) }
},
{
api: 'destroySharedFileSystem',
icon: 'delete-outlined',
docHelp: 'adminguide/storage.html#lifecycle-operations',
label: 'label.destroy.sharedfs',
message: 'message.action.destroy.sharedfs',
dataView: true,
popup: true,
groupAction: true,
groupMap: (selection, values) => { return selection.map(x => { return { id: x, expunge: values.expunge, forced: values.forced } }) },
args: ['expunge', 'forced'],
show: (record) => { return !['Destroyed', 'Expunging', 'Error'].includes(record.state) }
},
{
api: 'recoverSharedFileSystem',
icon: 'medicine-box-outlined',
docHelp: 'adminguide/storage.html#lifecycle-operations',
label: 'label.recover.sharedfs',
message: 'message.action.recover.sharedfs',
dataView: true,
show: (record) => { return record.state === 'Destroyed' }
},
{
api: 'expungeSharedFileSystem',
icon: 'delete-outlined',
docHelp: 'adminguide/storage.html#lifecycle-operations',
label: 'label.expunge.sharedfs',
message: 'message.action.expunge.sharedfs',
dataView: true,
popup: true,
show: (record) => { return ['Destroyed', 'Expunging', 'Error'].includes(record.state) }
}
]
}
]
}

View File

@ -82,6 +82,7 @@ import {
FieldTimeOutlined,
FileDoneOutlined,
FileProtectOutlined,
FileTextOutlined,
FilterOutlined,
FilterTwoTone,
FireOutlined,
@ -248,6 +249,7 @@ export default {
app.component('FieldTimeOutlined', FieldTimeOutlined)
app.component('FileDoneOutlined', FileDoneOutlined)
app.component('FileProtectOutlined', FileProtectOutlined)
app.component('FileTextOutlined', FileTextOutlined)
app.component('FilterOutlined', FilterOutlined)
app.component('FilterTwoTone', FilterTwoTone)
app.component('FireOutlined', FireOutlined)

View File

@ -51,7 +51,7 @@
<template #suffixIcon><filter-outlined class="ant-select-suffix" /></template>
<a-select-option
v-if="['Admin', 'DomainAdmin'].includes($store.getters.userInfo.roletype) &&
['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool', 'kubernetes', 'computeoffering', 'systemoffering', 'diskoffering'].includes($route.name) ||
['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool', 'kubernetes', 'computeoffering', 'systemoffering', 'diskoffering', 'sharedfs'].includes($route.name) ||
['account'].includes($route.name)"
key="all"
:label="$t('label.all')">
@ -67,7 +67,7 @@
</a-select>
</a-tooltip>
<a-switch
v-if="!dataView && ['vm', 'volume', 'zone', 'cluster', 'host', 'storagepool', 'managementserver'].includes($route.name)"
v-if="!dataView && ['vm', 'volume', 'zone', 'cluster', 'host', 'storagepool', 'managementserver', 'sharedfs'].includes($route.name)"
style="margin-left: 8px; min-height: 23px; margin-bottom: 4px"
:checked-children="$t('label.metrics')"
:un-checked-children="$t('label.metrics')"
@ -800,7 +800,7 @@ export default {
this.projectView = Boolean(store.getters.project && store.getters.project.id)
this.hasProjectId = ['vm', 'vmgroup', 'ssh', 'affinitygroup', 'userdata', 'volume', 'snapshot', 'buckets', 'vmsnapshot', 'guestnetwork',
'vpc', 'securitygroups', 'publicip', 'vpncustomergateway', 'template', 'iso', 'event', 'kubernetes',
'vpc', 'securitygroups', 'publicip', 'vpncustomergateway', 'template', 'iso', 'event', 'kubernetes', 'sharedfs',
'autoscalevmgroup', 'vnfapp', 'webhook'].includes(this.$route.name)
if ((this.$route && this.$route.params && this.$route.params.id) || this.$route.query.dataView) {

View File

@ -45,60 +45,7 @@
<volumes-tab :resource="vm" :loading="loading" />
</a-tab-pane>
<a-tab-pane :tab="$t('label.nics')" key="nics" v-if="'listNics' in $store.getters.apis">
<a-button
type="primary"
style="width: 100%; margin-bottom: 10px"
@click="showAddNicModal"
:loading="loadingNic"
:disabled="!('addNicToVirtualMachine' in $store.getters.apis)">
<template #icon><plus-outlined /></template> {{ $t('label.network.addvm') }}
</a-button>
<NicsTable :resource="vm" :loading="loading">
<template #actions="record">
<a-popconfirm
:title="$t('label.set.default.nic')"
@confirm="setAsDefault(record.nic)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')"
v-if="!record.nic.isdefault"
>
<tooltip-button
tooltipPlacement="bottom"
:tooltip="$t('label.set.default.nic')"
:disabled="!('updateDefaultNicForVirtualMachine' in $store.getters.apis)"
icon="check-square-outlined" />
</a-popconfirm>
<tooltip-button
v-if="record.nic.type !== 'L2'"
tooltipPlacement="bottom"
:tooltip="$t('label.change.ip.address')"
icon="swap-outlined"
:disabled="!('updateVmNicIp' in $store.getters.apis)"
@onClick="onChangeIPAddress(record)" />
<tooltip-button
v-if="record.nic.type !== 'L2'"
tooltipPlacement="bottom"
:tooltip="$t('label.edit.secondary.ips')"
icon="environment-outlined"
:disabled="(!('addIpToNic' in $store.getters.apis) && !('addIpToNic' in $store.getters.apis))"
@onClick="onAcquireSecondaryIPAddress(record)" />
<a-popconfirm
:title="$t('message.network.removenic')"
@confirm="removeNIC(record.nic)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')"
v-if="!record.nic.isdefault"
>
<tooltip-button
tooltipPlacement="bottom"
:tooltip="$t('label.action.remove.nic')"
:disabled="!('removeNicFromVirtualMachine' in $store.getters.apis)"
type="primary"
:danger="true"
icon="delete-outlined" />
</a-popconfirm>
</template>
</NicsTable>
<NicsTab :resource="vm"/>
</a-tab-pane>
<a-tab-pane :tab="$t('label.vm.snapshots')" key="vmsnapshots" v-if="'listVMSnapshot' in $store.getters.apis">
<ListResourceTable
@ -175,153 +122,6 @@
<CreateVolume :resource="resource" @close-action="closeModals" />
</a-modal>
<a-modal
:visible="showAddNetworkModal"
:title="$t('label.network.addvm')"
:maskClosable="false"
:closable="true"
:footer="null"
@cancel="closeModals">
{{ $t('message.network.addvm.desc') }}
<a-form @finish="submitAddNetwork" v-ctrl-enter="submitAddNetwork">
<div class="modal-form">
<p class="modal-form__label">{{ $t('label.network') }}:</p>
<a-select
:value="addNetworkData.network"
@change="e => addNetworkData.network = e"
v-focus="true"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="network in addNetworkData.allNetworks"
:key="network.id"
:value="network.id"
:label="network.name">
<span>
<resource-icon v-if="network.icon" :image="network.icon.base64image" size="1x" style="margin-right: 5px"/>
<apartment-outlined v-else style="margin-right: 5px" />
{{ network.name }}
</span>
</a-select-option>
</a-select>
<p class="modal-form__label">{{ $t('label.publicip') }}:</p>
<a-input v-model:value="addNetworkData.ip"></a-input>
</div>
<div :span="24" class="action-button">
<a-button @click="closeModals">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="submitAddNetwork">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-modal>
<a-modal
:visible="showUpdateIpModal"
:title="$t('label.change.ipaddress')"
:maskClosable="false"
:closable="true"
:footer="null"
@cancel="closeModals"
>
{{ $t('message.network.updateip') }}
<a-form @finish="submitUpdateIP" v-ctrl-enter="submitUpdateIP">
<div class="modal-form">
<p class="modal-form__label">{{ $t('label.publicip') }}:</p>
<a-select
v-if="editNicResource.type==='Shared'"
v-model:value="editIpAddressValue"
:loading="listIps.loading"
v-focus="editNicResource.type==='Shared'"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<a-select-option v-for="ip in listIps.opts" :key="ip.ipaddress">
{{ ip.ipaddress }}
</a-select-option>
</a-select>
<a-input
v-else
v-model:value="editIpAddressValue"
v-focus="editNicResource.type!=='Shared'"></a-input>
</div>
<div :span="24" class="action-button">
<a-button @click="closeModals">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="submitUpdateIP">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-modal>
<a-modal
:visible="showSecondaryIpModal"
:title="$t('label.acquire.new.secondary.ip')"
:maskClosable="false"
:footer="null"
:closable="false"
class="wide-modal"
@cancel="closeModals"
>
<p>
{{ $t('message.network.secondaryip') }}
</p>
<a-divider />
<div v-ctrl-enter="submitSecondaryIP">
<div class="modal-form">
<p class="modal-form__label">{{ $t('label.publicip') }}:</p>
<a-select
v-if="editNicResource.type==='Shared'"
v-model:value="newSecondaryIp"
:loading="listIps.loading"
v-focus="editNicResource.type==='Shared'"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<a-select-option v-for="ip in listIps.opts" :key="ip.ipaddress">
{{ ip.ipaddress }}
</a-select-option>
</a-select>
<a-input
v-else
:placeholder="$t('label.new.secondaryip.description')"
v-model:value="newSecondaryIp"
v-focus="editNicResource.type!=='Shared'"></a-input>
</div>
<div style="margin-top: 10px; display: flex; justify-content:flex-end;">
<a-button @click="submitSecondaryIP" ref="submit" type="primary" style="margin-right: 10px;">{{ $t('label.add.secondary.ip') }}</a-button>
<a-button @click="closeModals">{{ $t('label.close') }}</a-button>
</div>
</div>
<a-divider />
<a-list itemLayout="vertical">
<a-list-item v-for="(ip, index) in secondaryIPs" :key="index">
<a-popconfirm
:title="`${$t('label.action.release.ip')}?`"
@confirm="removeSecondaryIP(ip.id)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')"
>
<tooltip-button
tooltipPlacement="top"
:tooltip="$t('label.action.release.ip')"
type="primary"
:danger="true"
icon="delete-outlined" />
{{ ip.ipaddress }}
</a-popconfirm>
</a-list-item>
</a-list>
</a-modal>
</a-spin>
</template>
@ -335,7 +135,7 @@ import StatsTab from '@/components/view/StatsTab'
import EventsTab from '@/components/view/EventsTab'
import DetailSettings from '@/components/view/DetailSettings'
import CreateVolume from '@/views/storage/CreateVolume'
import NicsTable from '@/views/network/NicsTable'
import NicsTab from '@/views/network/NicsTab'
import InstanceSchedules from '@/views/compute/InstanceSchedules.vue'
import ListResourceTable from '@/components/view/ListResourceTable'
import TooltipButton from '@/components/widgets/TooltipButton'
@ -353,7 +153,7 @@ export default {
EventsTab,
DetailSettings,
CreateVolume,
NicsTable,
NicsTab,
InstanceSchedules,
ListResourceTable,
SecurityGroupSelection,
@ -380,29 +180,7 @@ export default {
totalStorage: 0,
currentTab: 'details',
showAddVolumeModal: false,
showAddNetworkModal: false,
showUpdateIpModal: false,
showSecondaryIpModal: false,
showUpdateSecurityGroupsModal: false,
diskOfferings: [],
addNetworkData: {
allNetworks: [],
network: '',
ip: ''
},
loadingNic: false,
loadingSG: false,
editIpAddressNic: '',
editIpAddressValue: '',
editNetworkId: '',
secondaryIPs: [],
selectedNicId: '',
newSecondaryIp: '',
editNicResource: {},
listIps: {
loading: false,
opts: []
},
annotations: [],
dataResource: {},
dataPreFill: {},
@ -473,62 +251,10 @@ export default {
this.diskOfferings = response.listdiskofferingsresponse.diskoffering
})
},
listNetworks () {
api('listNetworks', {
listAll: 'true',
showicon: true,
zoneid: this.vm.zoneid
}).then(response => {
this.addNetworkData.allNetworks = response.listnetworksresponse.network.filter(network => !this.vm.nic.map(nic => nic.networkid).includes(network.id))
this.addNetworkData.network = this.addNetworkData.allNetworks[0].id
})
},
fetchSecondaryIPs (nicId) {
this.showSecondaryIpModal = true
this.selectedNicId = nicId
api('listNics', {
nicId: nicId,
keyword: '',
virtualmachineid: this.vm.id
}).then(response => {
this.secondaryIPs = response.listnicsresponse.nic[0].secondaryip
})
},
fetchPublicIps (networkid) {
this.listIps.loading = true
this.listIps.opts = []
api('listPublicIpAddresses', {
networkid: networkid,
allocatedonly: false,
forvirtualnetwork: false
}).then(json => {
const listPublicIps = json.listpublicipaddressesresponse.publicipaddress || []
listPublicIps.forEach(item => {
if (item.state === 'Free') {
this.listIps.opts.push({
ipaddress: item.ipaddress
})
}
})
this.listIps.opts.sort(function (a, b) {
const currentIp = a.ipaddress.replaceAll('.', '')
const nextIp = b.ipaddress.replaceAll('.', '')
if (parseInt(currentIp) < parseInt(nextIp)) { return -1 }
if (parseInt(currentIp) > parseInt(nextIp)) { return 1 }
return 0
})
}).finally(() => {
this.listIps.loading = false
})
},
showAddVolModal () {
this.showAddVolumeModal = true
this.listDiskOfferings()
},
showAddNicModal () {
this.showAddNetworkModal = true
this.listNetworks()
},
showUpdateSGModal () {
this.loadingSG = true
if (this.vm.securitygroup && this.vm.securitygroup?.length > 0) {
@ -543,234 +269,7 @@ export default {
},
closeModals () {
this.showAddVolumeModal = false
this.showAddNetworkModal = false
this.showUpdateIpModal = false
this.showSecondaryIpModal = false
this.showUpdateSecurityGroupsModal = false
this.addNetworkData.network = ''
this.addNetworkData.ip = ''
this.editIpAddressValue = ''
this.newSecondaryIp = ''
},
onChangeIPAddress (record) {
this.editNicResource = record.nic
this.editIpAddressNic = record.nic.id
this.showUpdateIpModal = true
if (record.nic.type === 'Shared') {
this.fetchPublicIps(record.nic.networkid)
}
},
onAcquireSecondaryIPAddress (record) {
if (record.nic.type === 'Shared') {
this.fetchPublicIps(record.nic.networkid)
} else {
this.listIps.opts = []
}
this.editNicResource = record.nic
this.editNetworkId = record.nic.networkid
this.fetchSecondaryIPs(record.nic.id)
},
submitAddNetwork () {
if (this.loadingNic) return
const params = {}
params.virtualmachineid = this.vm.id
params.networkid = this.addNetworkData.network
if (this.addNetworkData.ip) {
params.ipaddress = this.addNetworkData.ip
}
this.showAddNetworkModal = false
this.loadingNic = true
api('addNicToVirtualMachine', params).then(response => {
this.$pollJob({
jobId: response.addnictovirtualmachineresponse.jobid,
successMessage: this.$t('message.success.add.network'),
successMethod: () => {
this.loadingNic = false
this.closeModals()
},
errorMessage: this.$t('message.add.network.failed'),
errorMethod: () => {
this.loadingNic = false
this.closeModals()
},
loadingMessage: this.$t('message.add.network.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.closeModals()
this.parentFetchData()
}
})
}).catch(error => {
this.$notifyError(error)
this.loadingNic = false
})
},
setAsDefault (item) {
this.loadingNic = true
api('updateDefaultNicForVirtualMachine', {
virtualmachineid: this.vm.id,
nicid: item.id
}).then(response => {
this.$pollJob({
jobId: response.updatedefaultnicforvirtualmachineresponse.jobid,
successMessage: `${this.$t('label.success.set')} ${item.networkname} ${this.$t('label.as.default')}. ${this.$t('message.set.default.nic.manual')}.`,
successMethod: () => {
this.loadingNic = false
},
errorMessage: `${this.$t('label.error.setting')} ${item.networkname} ${this.$t('label.as.default')}`,
errorMethod: () => {
this.loadingNic = false
},
loadingMessage: `${this.$t('label.setting')} ${item.networkname} ${this.$t('label.as.default')}...`,
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.parentFetchData()
}
})
}).catch(error => {
this.$notifyError(error)
this.loadingNic = false
})
},
submitUpdateIP () {
if (this.loadingNic) return
this.loadingNic = true
this.showUpdateIpModal = false
const params = {
nicId: this.editIpAddressNic
}
if (this.editIpAddressValue) {
params.ipaddress = this.editIpAddressValue
}
api('updateVmNicIp', params).then(response => {
this.$pollJob({
jobId: response.updatevmnicipresponse.jobid,
successMessage: this.$t('message.success.update.ipaddress'),
successMethod: () => {
this.loadingNic = false
this.closeModals()
},
errorMessage: this.$t('label.error'),
errorMethod: () => {
this.loadingNic = false
this.closeModals()
},
loadingMessage: this.$t('message.update.ipaddress.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.closeModals()
this.parentFetchData()
}
})
})
.catch(error => {
this.$notifyError(error)
this.loadingNic = false
})
},
removeNIC (item) {
this.loadingNic = true
api('removeNicFromVirtualMachine', {
nicid: item.id,
virtualmachineid: this.vm.id
}).then(response => {
this.$pollJob({
jobId: response.removenicfromvirtualmachineresponse.jobid,
successMessage: this.$t('message.success.remove.nic'),
successMethod: () => {
this.loadingNic = false
},
errorMessage: this.$t('message.error.remove.nic'),
errorMethod: () => {
this.loadingNic = false
},
loadingMessage: this.$t('message.remove.nic.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.parentFetchData()
}
})
})
.catch(error => {
this.$notifyError(error)
this.loadingNic = false
})
},
submitSecondaryIP () {
if (this.loadingNic) return
this.loadingNic = true
const params = {}
params.nicid = this.selectedNicId
if (this.newSecondaryIp) {
params.ipaddress = this.newSecondaryIp
}
api('addIpToNic', params).then(response => {
this.$pollJob({
jobId: response.addiptovmnicresponse.jobid,
successMessage: this.$t('message.success.add.secondary.ipaddress'),
successMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
},
errorMessage: this.$t('message.error.add.secondary.ipaddress'),
errorMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
},
loadingMessage: this.$t('message.add.secondary.ipaddress.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
this.parentFetchData()
}
})
}).catch(error => {
this.$notifyError(error)
this.loadingNic = false
}).finally(() => {
this.newSecondaryIp = null
this.fetchPublicIps(this.editNetworkId)
})
},
removeSecondaryIP (id) {
this.loadingNic = true
api('removeIpFromNic', { id }).then(response => {
this.$pollJob({
jobId: response.removeipfromnicresponse.jobid,
successMessage: this.$t('message.success.remove.secondary.ipaddress'),
successMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
this.fetchPublicIps(this.editNetworkId)
},
errorMessage: this.$t('message.error.remove.secondary.ipaddress'),
errorMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
},
loadingMessage: this.$t('message.remove.secondary.ipaddress.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
this.parentFetchData()
}
})
}).catch(error => {
this.$notifyError(error)
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
})
},
updateSecurityGroupsSelection (securitygroupids) {
this.securitygroupids = securitygroupids || []

View File

@ -0,0 +1,602 @@
// 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.
<template>
<a-spin :spinning="loadingNic">
<a-button
type="primary"
style="width: 100%; margin-bottom: 10px"
@click="showAddNicModal"
:loading="loadingNic"
:disabled="!('addNicToVirtualMachine' in $store.getters.apis)">
<template #icon><plus-outlined /></template> {{ $t('label.network.addvm') }}
</a-button>
<NicsTable :resource="resource" :loading="loading">
<template #actions="record">
<a-popconfirm
:title="$t('label.set.default.nic')"
@confirm="setAsDefault(record.nic)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')"
v-if="!record.nic.isdefault"
>
<tooltip-button
tooltipPlacement="bottom"
:tooltip="$t('label.set.default.nic')"
:disabled="!('updateDefaultNicForVirtualMachine' in $store.getters.apis)"
icon="check-square-outlined" />
</a-popconfirm>
<tooltip-button
v-if="record.nic.type !== 'L2'"
tooltipPlacement="bottom"
:tooltip="$t('label.change.ip.address')"
icon="swap-outlined"
:disabled="!('updateVmNicIp' in $store.getters.apis)"
@onClick="onChangeIPAddress(record)" />
<tooltip-button
v-if="record.nic.type !== 'L2'"
tooltipPlacement="bottom"
:tooltip="$t('label.edit.secondary.ips')"
icon="environment-outlined"
:disabled="(!('addIpToNic' in $store.getters.apis) && !('addIpToNic' in $store.getters.apis))"
@onClick="onAcquireSecondaryIPAddress(record)" />
<a-popconfirm
:title="$t('message.network.removenic')"
@confirm="removeNIC(record.nic)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')"
v-if="!record.nic.isdefault"
>
<tooltip-button
tooltipPlacement="bottom"
:tooltip="$t('label.action.remove.nic')"
:disabled="!('removeNicFromVirtualMachine' in $store.getters.apis)"
type="primary"
:danger="true"
icon="delete-outlined" />
</a-popconfirm>
</template>
</NicsTable>
<a-modal
:visible="showAddNetworkModal"
:title="$t('label.network.addvm')"
:maskClosable="false"
:closable="true"
:footer="null"
@cancel="closeModals">
{{ $t('message.network.addvm.desc') }}
<a-form @finish="submitAddNetwork" v-ctrl-enter="submitAddNetwork">
<div class="modal-form">
<p class="modal-form__label">{{ $t('label.network') }}:</p>
<a-select
:value="addNetworkData.network"
@change="e => addNetworkData.network = e"
v-focus="true"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="network in addNetworkData.allNetworks"
:key="network.id"
:value="network.id"
:label="network.name">
<span>
<resource-icon v-if="network.icon" :image="network.icon.base64image" size="1x" style="margin-right: 5px"/>
<apartment-outlined v-else style="margin-right: 5px" />
{{ network.name }}
</span>
</a-select-option>
</a-select>
<p class="modal-form__label">{{ $t('label.publicip') }}:</p>
<a-input v-model:value="addNetworkData.ip"></a-input>
</div>
<div :span="24" class="action-button">
<a-button @click="closeModals">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="submitAddNetwork">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-modal>
<a-modal
:visible="showUpdateIpModal"
:title="$t('label.change.ipaddress')"
:maskClosable="false"
:closable="true"
:footer="null"
@cancel="closeModals"
>
{{ $t('message.network.updateip') }}
<a-form @finish="submitUpdateIP" v-ctrl-enter="submitUpdateIP">
<div class="modal-form">
<p class="modal-form__label">{{ $t('label.publicip') }}:</p>
<a-select
v-if="editNicResource.type==='Shared'"
v-model:value="editIpAddressValue"
:loading="listIps.loading"
v-focus="editNicResource.type==='Shared'"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<a-select-option v-for="ip in listIps.opts" :key="ip.ipaddress">
{{ ip.ipaddress }}
</a-select-option>
</a-select>
<a-input
v-else
v-model:value="editIpAddressValue"
v-focus="editNicResource.type!=='Shared'"></a-input>
</div>
<div :span="24" class="action-button">
<a-button @click="closeModals">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="submitUpdateIP">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-modal>
<a-modal
:visible="showSecondaryIpModal"
:title="$t('label.acquire.new.secondary.ip')"
:maskClosable="false"
:footer="null"
:closable="false"
class="wide-modal"
@cancel="closeModals"
>
<p>
{{ $t('message.network.secondaryip') }}
</p>
<a-divider />
<div v-ctrl-enter="submitSecondaryIP">
<div class="modal-form">
<p class="modal-form__label">{{ $t('label.publicip') }}:</p>
<a-select
v-if="editNicResource.type==='Shared'"
v-model:value="newSecondaryIp"
:loading="listIps.loading"
v-focus="editNicResource.type==='Shared'"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<a-select-option v-for="ip in listIps.opts" :key="ip.ipaddress">
{{ ip.ipaddress }}
</a-select-option>
</a-select>
<a-input
v-else
:placeholder="$t('label.new.secondaryip.description')"
v-model:value="newSecondaryIp"
v-focus="editNicResource.type!=='Shared'"></a-input>
</div>
<div style="margin-top: 10px; display: flex; justify-content:flex-end;">
<a-button @click="submitSecondaryIP" ref="submit" type="primary" style="margin-right: 10px;">{{ $t('label.add.secondary.ip') }}</a-button>
<a-button @click="closeModals">{{ $t('label.close') }}</a-button>
</div>
</div>
<a-divider />
<a-list itemLayout="vertical">
<a-list-item v-for="(ip, index) in secondaryIPs" :key="index">
<a-popconfirm
:title="`${$t('label.action.release.ip')}?`"
@confirm="removeSecondaryIP(ip.id)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')"
>
<tooltip-button
tooltipPlacement="top"
:tooltip="$t('label.action.release.ip')"
type="primary"
:danger="true"
icon="delete-outlined" />
{{ ip.ipaddress }}
</a-popconfirm>
</a-list-item>
</a-list>
</a-modal>
</a-spin>
</template>
<script>
import { api } from '@/api'
import NicsTable from '@/views/network/NicsTable'
import TooltipButton from '@/components/widgets/TooltipButton'
import ResourceIcon from '@/components/view/ResourceIcon'
export default {
name: 'NicsTab',
components: {
NicsTable,
TooltipButton,
ResourceIcon
},
props: {
resource: {
type: Object,
required: true
},
loading: {
type: Boolean,
default: false
}
},
inject: ['parentFetchData'],
data () {
return {
vm: {},
showAddNetworkModal: false,
showUpdateIpModal: false,
showSecondaryIpModal: false,
addNetworkData: {
allNetworks: [],
network: '',
ip: ''
},
loadingNic: false,
editIpAddressNic: '',
editIpAddressValue: '',
editNetworkId: '',
secondaryIPs: [],
selectedNicId: '',
newSecondaryIp: '',
editNicResource: {},
listIps: {
loading: false,
opts: []
}
}
},
created () {
this.vm = this.resource
},
methods: {
listNetworks () {
api('listNetworks', {
listAll: 'true',
showicon: true,
zoneid: this.vm.zoneid
}).then(response => {
this.addNetworkData.allNetworks = response.listnetworksresponse.network.filter(network => !this.vm.nic.map(nic => nic.networkid).includes(network.id))
this.addNetworkData.network = this.addNetworkData.allNetworks[0].id
})
},
fetchSecondaryIPs (nicId) {
this.showSecondaryIpModal = true
this.selectedNicId = nicId
api('listNics', {
nicId: nicId,
keyword: '',
virtualmachineid: this.vm.id
}).then(response => {
this.secondaryIPs = response.listnicsresponse.nic[0].secondaryip
})
},
fetchPublicIps (networkid) {
this.listIps.loading = true
this.listIps.opts = []
api('listPublicIpAddresses', {
networkid: networkid,
allocatedonly: false,
forvirtualnetwork: false
}).then(json => {
const listPublicIps = json.listpublicipaddressesresponse.publicipaddress || []
listPublicIps.forEach(item => {
if (item.state === 'Free') {
this.listIps.opts.push({
ipaddress: item.ipaddress
})
}
})
this.listIps.opts.sort(function (a, b) {
const currentIp = a.ipaddress.replaceAll('.', '')
const nextIp = b.ipaddress.replaceAll('.', '')
if (parseInt(currentIp) < parseInt(nextIp)) { return -1 }
if (parseInt(currentIp) > parseInt(nextIp)) { return 1 }
return 0
})
}).finally(() => {
this.listIps.loading = false
})
},
showAddNicModal () {
this.showAddNetworkModal = true
this.listNetworks()
},
closeModals () {
this.showAddNetworkModal = false
this.showUpdateIpModal = false
this.showSecondaryIpModal = false
this.addNetworkData.network = ''
this.addNetworkData.ip = ''
this.editIpAddressValue = ''
this.newSecondaryIp = ''
},
onChangeIPAddress (record) {
this.editNicResource = record.nic
this.editIpAddressNic = record.nic.id
this.showUpdateIpModal = true
if (record.nic.type === 'Shared') {
this.fetchPublicIps(record.nic.networkid)
}
},
onAcquireSecondaryIPAddress (record) {
if (record.nic.type === 'Shared') {
this.fetchPublicIps(record.nic.networkid)
} else {
this.listIps.opts = []
}
this.editNicResource = record.nic
this.editNetworkId = record.nic.networkid
this.fetchSecondaryIPs(record.nic.id)
},
submitAddNetwork () {
if (this.loadingNic) return
const params = {}
params.virtualmachineid = this.vm.id
params.networkid = this.addNetworkData.network
if (this.addNetworkData.ip) {
params.ipaddress = this.addNetworkData.ip
}
this.showAddNetworkModal = false
this.loadingNic = true
api('addNicToVirtualMachine', params).then(response => {
this.$pollJob({
jobId: response.addnictovirtualmachineresponse.jobid,
successMessage: this.$t('message.success.add.network'),
successMethod: () => {
this.loadingNic = false
this.closeModals()
},
errorMessage: this.$t('message.add.network.failed'),
errorMethod: () => {
this.loadingNic = false
this.closeModals()
},
loadingMessage: this.$t('message.add.network.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.closeModals()
this.$emit('refresh')
}
})
}).catch(error => {
this.$notifyError(error)
this.loadingNic = false
})
},
setAsDefault (item) {
this.loadingNic = true
api('updateDefaultNicForVirtualMachine', {
virtualmachineid: this.vm.id,
nicid: item.id
}).then(response => {
this.$pollJob({
jobId: response.updatedefaultnicforvirtualmachineresponse.jobid,
successMessage: `${this.$t('label.success.set')} ${item.networkname} ${this.$t('label.as.default')}. ${this.$t('message.set.default.nic.manual')}.`,
successMethod: () => {
this.loadingNic = false
},
errorMessage: `${this.$t('label.error.setting')} ${item.networkname} ${this.$t('label.as.default')}`,
errorMethod: () => {
this.loadingNic = false
},
loadingMessage: `${this.$t('label.setting')} ${item.networkname} ${this.$t('label.as.default')}...`,
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.$emit('refresh')
}
})
}).catch(error => {
this.$notifyError(error)
this.loadingNic = false
})
},
submitUpdateIP () {
if (this.loadingNic) return
this.loadingNic = true
this.showUpdateIpModal = false
const params = {
nicId: this.editIpAddressNic
}
if (this.editIpAddressValue) {
params.ipaddress = this.editIpAddressValue
}
api('updateVmNicIp', params).then(response => {
this.$pollJob({
jobId: response.updatevmnicipresponse.jobid,
successMessage: this.$t('message.success.update.ipaddress'),
successMethod: () => {
this.loadingNic = false
this.closeModals()
},
errorMessage: this.$t('label.error'),
errorMethod: () => {
this.loadingNic = false
this.closeModals()
},
loadingMessage: this.$t('message.update.ipaddress.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.closeModals()
this.$emit('refresh')
}
})
})
.catch(error => {
this.$notifyError(error)
this.loadingNic = false
})
},
removeNIC (item) {
this.loadingNic = true
api('removeNicFromVirtualMachine', {
nicid: item.id,
virtualmachineid: this.vm.id
}).then(response => {
this.$pollJob({
jobId: response.removenicfromvirtualmachineresponse.jobid,
successMessage: this.$t('message.success.remove.nic'),
successMethod: () => {
this.loadingNic = false
},
errorMessage: this.$t('message.error.remove.nic'),
errorMethod: () => {
this.loadingNic = false
},
loadingMessage: this.$t('message.remove.nic.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.$emit('refresh')
}
})
})
.catch(error => {
this.$notifyError(error)
this.loadingNic = false
})
},
submitSecondaryIP () {
if (this.loadingNic) return
this.loadingNic = true
const params = {}
params.nicid = this.selectedNicId
if (this.newSecondaryIp) {
params.ipaddress = this.newSecondaryIp
}
api('addIpToNic', params).then(response => {
this.$pollJob({
jobId: response.addiptovmnicresponse.jobid,
successMessage: this.$t('message.success.add.secondary.ipaddress'),
successMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
},
errorMessage: this.$t('message.error.add.secondary.ipaddress'),
errorMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
},
loadingMessage: this.$t('message.add.secondary.ipaddress.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
this.$emit('refresh')
}
})
}).catch(error => {
this.$notifyError(error)
this.loadingNic = false
}).finally(() => {
this.newSecondaryIp = null
this.fetchPublicIps(this.editNetworkId)
})
},
removeSecondaryIP (id) {
this.loadingNic = true
api('removeIpFromNic', { id }).then(response => {
this.$pollJob({
jobId: response.removeipfromnicresponse.jobid,
successMessage: this.$t('message.success.remove.secondary.ipaddress'),
successMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
this.fetchPublicIps(this.editNetworkId)
},
errorMessage: this.$t('message.error.remove.secondary.ipaddress'),
errorMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
},
loadingMessage: this.$t('message.remove.secondary.ipaddress.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
this.$emit('refresh')
}
})
}).catch(error => {
this.$notifyError(error)
this.loadingNic = false
this.fetchSecondaryIPs(this.selectedNicId)
})
}
}
}
</script>
<style scoped>
.modal-form {
display: flex;
flex-direction: column;
&__label {
margin-top: 20px;
margin-bottom: 5px;
font-weight: bold;
&--no-margin {
margin-top: 0;
}
}
}
.action-button {
display: flex;
flex-wrap: wrap;
button {
padding: 5px;
height: auto;
margin-bottom: 10px;
align-self: flex-start;
&:not(:last-child) {
margin-right: 10px;
}
}
}
.wide-modal {
min-width: 50vw;
}
:deep(.ant-list-item) {
padding-top: 12px;
padding-bottom: 12px;
}
</style>

View File

@ -0,0 +1,236 @@
// 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.
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-form
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
@finish="handleSubmit"
>
<a-form-item ref="diskofferingid" name="diskofferingid">
<template #label>
<tooltip-label :title="$t('label.diskofferingid')" :tooltip="apiParams.diskofferingid.description || 'Disk Offering'"/>
</template>
<a-select
v-model:value="form.diskofferingid"
:loading="diskofferingLoading"
@change="id => handleDiskOfferingChange(id)"
:placeholder="apiParams.diskofferingid.description || $t('label.diskofferingid')"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="(diskoffering, index) in diskofferings"
:value="diskoffering.id"
:key="index"
:label="diskoffering.displaytext || diskoffering.name">
{{ diskoffering.displaytext || diskoffering.name }}
</a-select-option>
</a-select>
</a-form-item>
<span v-if="customDiskOffering">
<a-form-item ref="size" name="size">
<template #label>
<tooltip-label :title="$t('label.sizegb')" :tooltip="apiParams.size.description"/>
</template>
<a-input
v-model:value="form.size"
:placeholder="apiParams.size.description"/>
</a-form-item>
</span>
<span v-if="isCustomizedDiskIOps">
<a-form-item ref="miniops" name="miniops">
<template #label>
<tooltip-label :title="$t('label.miniops')" :tooltip="apiParams.miniops.description"/>
</template>
<a-input
v-model:value="form.miniops"
:placeholder="apiParams.miniops.description"/>
</a-form-item>
<a-form-item ref="maxiops" name="maxiops">
<template #label>
<tooltip-label :title="$t('label.maxiops')" :tooltip="apiParams.maxiops.description"/>
</template>
<a-input
v-model:value="form.maxiops"
:placeholder="apiParams.maxiops.description"/>
</a-form-item>
</span>
<div :span="24" class="action-button">
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
import { mixinForm } from '@/utils/mixin'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import store from '@/store'
export default {
name: 'CreateSharedFS',
mixins: [mixinForm],
props: {
resource: {
type: Object,
required: true
}
},
components: {
ResourceIcon,
TooltipLabel
},
inject: ['parentFetchData'],
data () {
return {
owner: {
projectid: store.getters.project?.id,
domainid: store.getters.project?.id ? null : store.getters.userInfo.domainid,
account: store.getters.project?.id ? null : store.getters.userInfo.account
},
loading: false,
diskofferings: [],
diskofferingLoading: false,
customDiskOffering: false,
isCustomizedDiskIOps: false
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('changeSharedFileSystemDiskOffering')
},
created () {
this.initForm()
this.fetchData()
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
})
this.rules = reactive({
diskofferingid: [{ required: true, message: this.$t('label.required') }],
size: [{ required: true, message: this.$t('message.error.custom.disk.size') }],
miniops: [{
validator: async (rule, value) => {
if (value && (isNaN(value) || value <= 0)) {
return Promise.reject(this.$t('message.error.number'))
}
return Promise.resolve()
}
}],
maxiops: [{
validator: async (rule, value) => {
if (value && (isNaN(value) || value <= 0)) {
return Promise.reject(this.$t('message.error.number'))
}
return Promise.resolve()
}
}]
})
},
fetchData () {
this.fetchDiskOfferings()
},
fetchDiskOfferings () {
this.diskOfferingLoading = true
var params = {
zoneid: this.resource.zoneid,
listall: true,
domainid: this.owner.domainid
}
if (this.owner.projectid) {
params.projectid = this.owner.projectid
} else {
params.account = this.owner.account
}
api('listDiskOfferings', params).then(json => {
this.diskofferings = json.listdiskofferingsresponse.diskoffering || []
this.form.diskofferingid = this.diskofferings[0].id || ''
this.customDiskOffering = this.diskofferings[0].iscustomized || false
this.isCustomizedDiskIOps = this.diskofferings[0]?.iscustomizediops || false
}).finally(() => {
this.diskOfferingLoading = false
})
},
closeModal () {
this.$emit('close-action')
},
handleDiskOfferingChange (id) {
const diskoffering = this.diskofferings.filter(x => x.id === id)
this.customDiskOffering = diskoffering[0]?.iscustomized || false
this.isCustomizedDiskIOps = diskoffering[0]?.iscustomizediops || false
},
handleSubmit (e) {
e.preventDefault()
if (this.loading) return
this.formRef.value.validate().then(async () => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
var data = {
id: this.resource.id,
diskofferingid: values.diskofferingid,
size: values.size,
minops: values.miniops,
maxops: values.maxiops
}
this.loading = true
api('changeSharedFileSystemDiskOffering', data).then(response => {
this.$pollJob({
jobId: response.changesharedfilesystemdiskofferingresponse.jobid,
title: this.$t('label.change.disk.offering'),
description: values.name,
successMessage: this.$t('message.success.change.offering'),
errorMessage: this.$t('message.change.disk.offering.sharedfs.failed'),
loadingMessage: this.$t('message.change.disk.offering.sharedfs.processing'),
catchMessage: this.$t('error.fetching.async.job.result')
})
this.closeModal()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}).catch((error) => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
}
}
}
</script>
<style lang="scss" scoped>
.form-layout {
width: 85vw;
@media (min-width: 1000px) {
width: 35vw;
}
}
</style>

View File

@ -0,0 +1,204 @@
// 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.
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-form
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
@finish="handleSubmit"
>
<a-form-item>
<a-alert type="warning">
<template #message>
<span v-html="$t('message.confirm.change.service.offering.for.sharedfs')" />
</template>
</a-alert>
</a-form-item>
<a-form-item ref="serviceofferingid" name="serviceofferingid">
<template #label>
<tooltip-label :title="$t('label.serviceofferingid')" :tooltip="apiParams.serviceofferingid.description || 'Service Offering'"/>
</template>
<a-select
v-model:value="form.serviceofferingid"
:loading="serviceofferingLoading"
:placeholder="apiParams.serviceofferingid.description || $t('label.serviceofferingid')"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="(serviceoffering, index) in serviceofferings"
:value="serviceoffering.id"
:key="index"
:label="serviceoffering.name || serviceoffering.displaytext">
{{ serviceoffering.name || serviceoffering.displaytext }}
</a-select-option>
</a-select>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
import { mixinForm } from '@/utils/mixin'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import store from '@/store'
export default {
name: 'CreateSharedFS',
mixins: [mixinForm],
props: {
resource: {
type: Object,
required: true
}
},
components: {
ResourceIcon,
TooltipLabel
},
inject: ['parentFetchData'],
data () {
return {
owner: {
projectid: store.getters.project?.id,
domainid: store.getters.project?.id ? null : store.getters.userInfo.domainid,
account: store.getters.project?.id ? null : store.getters.userInfo.account
},
loading: false,
configLoading: false,
serviceofferings: [],
serviceofferingLoding: false
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('changeSharedFileSystemServiceOffering')
},
created () {
this.initForm()
this.fetchData()
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
})
this.rules = reactive({
serviceofferingid: [{ required: true, message: this.$t('label.required') }]
})
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
fetchData () {
this.fetchServiceOfferings()
},
fetchCapabilities (id) {
api('listCapabilities').then(json => {
this.capability = json.listcapabilitiesresponse.capability || []
this.minCpu = this.capability.sharedfsvmmincpucount
this.minMemory = this.capability.sharedfsvmminramsize
})
},
fetchServiceOfferings () {
this.fetchCapabilities()
this.serviceofferingLoading = true
var params = {
zoneid: this.resource.zoneid,
listall: true,
domainid: this.owner.domainid
}
if (this.owner.projectid) {
params.projectid = this.owner.projectid
} else {
params.account = this.owner.account
}
api('listServiceOfferings', params).then(json => {
var items = json.listserviceofferingsresponse.serviceoffering || []
if (items != null) {
for (var i = 0; i < items.length; i++) {
if (items[i].iscustomized === false && items[i].offerha === true &&
items[i].cpunumber >= this.minCpu && items[i].memory >= this.minMemory) {
this.serviceofferings.push(items[i])
}
}
}
this.form.serviceofferingid = this.serviceofferings[0].id || ''
})
this.serviceofferingLoading = false
},
closeModal () {
this.$emit('close-action')
},
handleSubmit (e) {
e.preventDefault()
if (this.loading) return
this.formRef.value.validate().then(async () => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
var data = {
id: this.resource.id,
serviceofferingid: values.serviceofferingid
}
this.loading = true
api('changeSharedFileSystemServiceOffering', data).then(response => {
this.$pollJob({
jobId: response.changesharedfilesystemserviceofferingresponse.jobid,
title: this.$t('label.change.service.offering'),
description: values.name,
successMessage: this.$t('message.success.change.offering'),
errorMessage: this.$t('message.change.service.offering.sharedfs.failed'),
loadingMessage: this.$t('message.change.service.offering.sharedfs.processing'),
catchMessage: this.$t('error.fetching.async.job.result')
})
this.closeModal()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}).catch((error) => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
}
}
}
</script>
<style lang="scss" scoped>
.form-layout {
width: 85vw;
@media (min-width: 1000px) {
width: 35vw;
}
}
</style>

View File

@ -0,0 +1,459 @@
// 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.
<template>
<a-spin :spinning="loading">
<div v-if="!isNormalUserOrProject">
<ownership-selection @fetch-owner="fetchOwnerOptions" />
</div>
<a-form
class="form"
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
@finish="handleSubmit"
v-ctrl-enter="handleSubmit"
>
<a-form-item name="name" ref="name" :label="$t('label.name')">
<a-input v-model:value="form.name" v-focus="true" />
</a-form-item>
<a-form-item name="description" ref="description" :label="$t('label.description')">
<a-input v-model:value="form.description" />
</a-form-item>
<a-form-item ref="zoneid" name="zoneid">
<template #label>
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
</template>
<a-select
v-model:value="form.zoneid"
:loading="zoneLoading"
@change="zone => handleZoneChange(id)"
:placeholder="apiParams.zoneid.description"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="(zone, index) in zones"
:value="zone.id"
:key="index"
:label="zone.name">
<span>
<resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px"/>
{{ zone.name }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item ref="filesystem" name="filesystem">
<template #label>
<tooltip-label :title="$t('label.filesystem')" :tooltip="apiParams.filesystem.description"/>
</template>
<a-select
v-model:value="form.filesystem"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option value="XFS" label="XFS">XFS</a-select-option>
<a-select-option value="EXT4" label="EXT4">EXT4</a-select-option>
</a-select>
</a-form-item>
<a-form-item ref="networkid" name="networkid">
<template #label>
<tooltip-label :title="$t('label.networkid')" :tooltip="apiParams.networkid.description || 'Network'"/>
</template>
<a-select
v-model:value="form.networkid"
:loading="networkLoading"
:placeholder="apiParams.networkid.description || $t('label.networkid')"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="(network, index) in networks"
:value="network.id"
:key="index"
:label="network.name"> {{ network.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item ref="diskofferingid" name="diskofferingid">
<template #label>
<tooltip-label :title="$t('label.diskofferingid')" :tooltip="apiParams.diskofferingid.description || 'Disk Offering'"/>
</template>
<a-select
v-model:value="form.diskofferingid"
:loading="diskofferingLoading"
@change="id => handleDiskOfferingChange(id)"
:placeholder="apiParams.diskofferingid.description || $t('label.diskofferingid')"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="(diskoffering, index) in diskofferings"
:value="diskoffering.id"
:key="index"
:label="diskoffering.displaytext || diskoffering.name">
{{ diskoffering.displaytext || diskoffering.name }}
</a-select-option>
</a-select>
</a-form-item>
<span v-if="customDiskOffering">
<a-form-item ref="size" name="size">
<template #label>
<tooltip-label :title="$t('label.sizegb')" :tooltip="apiParams.size.description"/>
</template>
<a-input
v-model:value="form.size"
:placeholder="apiParams.size.description"/>
</a-form-item>
</span>
<span v-if="isCustomizedDiskIOps">
<a-form-item ref="miniops" name="miniops">
<template #label>
<tooltip-label :title="$t('label.miniops')" :tooltip="apiParams.miniops.description"/>
</template>
<a-input
v-model:value="form.miniops"
:placeholder="apiParams.miniops.description"/>
</a-form-item>
<a-form-item ref="maxiops" name="maxiops">
<template #label>
<tooltip-label :title="$t('label.maxiops')" :tooltip="apiParams.maxiops.description"/>
</template>
<a-input
v-model:value="form.maxiops"
:placeholder="apiParams.maxiops.description"/>
</a-form-item>
</span>
<a-form-item ref="serviceofferingid" name="serviceofferingid">
<template #label>
<tooltip-label :title="$t('label.compute.offering.for.vm')" :tooltip="apiParams.serviceofferingid.description || 'Service Offering'"/>
</template>
<a-select
v-model:value="form.serviceofferingid"
:loading="serviceofferingLoading"
:placeholder="$t('label.serviceofferingid')"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="(serviceoffering, index) in serviceofferings"
:value="serviceoffering.id"
:key="index"
:label="serviceoffering.displaytext || serviceoffering.name">
{{ serviceoffering.displaytext || serviceoffering.name }}
</a-select-option>
</a-select>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-spin>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
import { mixinForm } from '@/utils/mixin'
import ResourceIcon from '@/components/view/ResourceIcon'
import OwnershipSelection from '@/views/compute/wizard/OwnershipSelection.vue'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import store from '@/store'
export default {
name: 'CreateSharedFS',
mixins: [mixinForm],
props: {
resource: {
type: Object,
required: true
}
},
components: {
OwnershipSelection,
ResourceIcon,
TooltipLabel
},
inject: ['parentFetchData'],
data () {
return {
owner: {
projectid: store.getters.project?.id,
domainid: store.getters.project?.id ? null : store.getters.userInfo.domainid,
account: store.getters.project?.id ? null : store.getters.userInfo.account
},
loading: false,
zones: [],
zoneLoading: false,
configLoading: false,
networks: [],
networkLoading: false,
serviceofferings: [],
serviceofferingLoading: false,
diskofferings: [],
diskofferingLoading: false,
customDiskOffering: false,
isCustomizedDiskIOps: false
}
},
computed: {
isNormalUserOrProject () {
return ['User'].includes(this.$store.getters.userInfo.roletype) || store.getters.project?.id
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('createSharedFileSystem')
},
created () {
this.initForm()
this.fetchData()
this.form.filesystem = 'XFS'
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
})
this.rules = reactive({
zoneid: [{ required: true, message: this.$t('message.error.zone') }],
name: [{ required: true, message: this.$t('label.required') }],
networkid: [{ required: true, message: this.$t('label.required') }],
serviceofferingid: [{ required: true, message: this.$t('label.required') }],
diskofferingid: [{ required: true, message: this.$t('label.required') }],
size: [{ required: true, message: this.$t('message.error.custom.disk.size') }],
miniops: [{
validator: async (rule, value) => {
if (value && (isNaN(value) || value <= 0)) {
return Promise.reject(this.$t('message.error.number'))
}
return Promise.resolve()
}
}],
maxiops: [{
validator: async (rule, value) => {
if (value && (isNaN(value) || value <= 0)) {
return Promise.reject(this.$t('message.error.number'))
}
return Promise.resolve()
}
}]
})
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
fetchOwnerOptions (OwnerOptions) {
this.owner = {}
console.log('fetching owner')
if (OwnerOptions.selectedAccountType === this.$t('label.account')) {
if (!OwnerOptions.selectedAccount) {
return
}
console.log('fetched account')
this.owner.account = OwnerOptions.selectedAccount
this.owner.domainid = OwnerOptions.selectedDomain
} else if (OwnerOptions.selectedAccountType === this.$t('label.project')) {
if (!OwnerOptions.selectedProject) {
return
}
console.log('fetched project')
this.owner.projectid = OwnerOptions.selectedProject
}
console.log('fetched owner')
this.fetchData()
},
fetchData () {
this.minCpu = store.getters.features.sharedfsvmmincpucount
this.minMemory = store.getters.features.sharedfsvmminramsize
this.fetchZones()
},
fetchZones () {
this.zoneLoading = true
const params = { showicon: true }
api('listZones', params).then(json => {
var listZones = json.listzonesresponse.zone
if (listZones) {
this.zones = []
listZones = listZones.filter(x => (x.allocationstate === 'Enabled' && x.networktype === 'Advanced' && x.securitygroupsenabled === false))
this.zones = this.zones.concat(listZones)
}
}).finally(() => {
this.zoneLoading = false
if (this.arrayHasItems(this.zones)) {
this.form.zoneid = this.zones[0].id
this.handleZoneChange(this.zones[0])
}
})
},
handleZoneChange (zone) {
this.selectedZone = zone
this.fetchServiceOfferings()
this.fetchDiskOfferings()
this.fetchNetworks()
},
fetchServiceOfferings () {
this.serviceofferingLoading = true
this.serviceofferings = []
var params = {
zoneid: this.selectedZone.id,
listall: true,
domainid: this.owner.domainid
}
if (this.owner.projectid) {
params.projectid = this.owner.projectid
} else {
params.account = this.owner.account
}
api('listServiceOfferings', params).then(json => {
var items = json.listserviceofferingsresponse.serviceoffering || []
if (items != null) {
for (var i = 0; i < items.length; i++) {
if (items[i].iscustomized === false && items[i].offerha === true &&
items[i].cpunumber >= this.minCpu && items[i].memory >= this.minMemory) {
this.serviceofferings.push(items[i])
}
}
}
this.form.serviceofferingid = this.serviceofferings[0].id || ''
}).finally(() => {
this.serviceofferingLoading = false
})
},
fetchDiskOfferings () {
this.diskofferingLoading = true
this.form.diskofferingid = null
var params = {
zoneid: this.selectedZone.id,
listall: true,
domainid: this.owner.domainid
}
if (this.owner.projectid) {
params.projectid = this.owner.projectid
} else {
params.account = this.owner.account
}
api('listDiskOfferings', params).then(json => {
this.diskofferings = json.listdiskofferingsresponse.diskoffering || []
this.form.diskofferingid = this.diskofferings[0].id || ''
this.customDiskOffering = this.diskofferings[0].iscustomized || false
this.isCustomizedDiskIOps = this.diskofferings[0]?.iscustomizediops || false
}).finally(() => {
this.diskofferingLoading = false
})
},
fetchNetworks () {
this.networkLoading = true
this.form.networkid = null
var params = {
zoneid: this.selectedZone.id,
canusefordeploy: true,
domainid: this.owner.domainid
}
if (this.owner.projectid) {
params.projectid = this.owner.projectid
} else {
params.account = this.owner.account
}
api('listNetworks', params).then(json => {
this.networks = json.listnetworksresponse.network || []
this.form.networkid = this.networks[0].id || ''
}).finally(() => {
this.networkLoading = false
})
},
closeModal () {
this.$emit('close-action')
},
handleDiskOfferingChange (id) {
const diskoffering = this.diskofferings.filter(x => x.id === id)
this.customDiskOffering = diskoffering[0]?.iscustomized || false
this.isCustomizedDiskIOps = diskoffering[0]?.iscustomizediops || false
},
handleSubmit (e) {
e.preventDefault()
if (this.loading) return
this.formRef.value.validate().then(async () => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
var data = {
name: values.name,
description: values.description,
zoneid: values.zoneid,
serviceofferingid: values.serviceofferingid,
diskofferingid: values.diskofferingid,
networkid: values.networkid,
size: values.size,
filesystem: values.filesystem,
miniops: values.miniops,
maxiops: values.maxiops,
domainid: this.owner.domainid
}
if (this.owner.projectid) {
data.projectid = this.owner.projectid
} else {
data.account = this.owner.account
}
this.loading = true
api('createSharedFileSystem', data).then(response => {
this.$pollJob({
jobId: response.createsharedfilesystemresponse.jobid,
title: this.$t('label.create.sharedfs'),
description: values.name,
successMessage: this.$t('message.success.create.sharedfs'),
errorMessage: this.$t('message.create.sharedfs.failed'),
loadingMessage: this.$t('message.create.sharedfs.processing'),
catchMessage: this.$t('error.fetching.async.job.result')
})
this.closeModal()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}).catch((error) => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
}
}
}
</script>
<style lang="scss" scoped>
.form {
width: 80vw;
@media (min-width: 500px) {
min-width: 400px;
width: 100%;
}
}
</style>

View File

@ -0,0 +1,202 @@
// 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.
<template>
<a-spin :spinning="loading">
<a-tabs
:activeKey="currentTab"
:tabPosition="device === 'mobile' ? 'top' : 'left'"
:animated="false"
@change="handleChangeTab">
<a-tab-pane :tab="$t('label.details')" key="details">
<DetailsTab :resource="dataResource" :loading="loading" />
</a-tab-pane>
<a-tab-pane :tab="$t('label.access')" key="access">
<h3>{{ $t('label.mount.sharedfs') }}</h3>
<div v-for="(nic, index) in resource.nic" :key="index" class="content">
<a-card>
<template #title>
<router-link :to="{ path: '/guestnetwork/' + nic.networkid }">
{{ nic.networkname }}
</router-link>
</template>
<timeline>
<a-timeline-item color="blue">
<h3>mount -t nfs {{ nic.ipaddress }}:{{ resource.path }} [local_mount_path]</h3>
<p class="info"><i>(Mount the NFS share. Additional mount options can be given as required.)</i></p>
</a-timeline-item>
<a-timeline-item color="blue">
<h3>showmount -e {{ nic.ipaddress }}</h3>
<p class="info"><i>(Check the status of the NFS server using the showmount command.)</i></p>
</a-timeline-item>
</timeline>
</a-card>
</div>
</a-tab-pane>
<a-tab-pane :tab="$t('label.networks')" key="nics" v-if="'listNics' in $store.getters.apis">
<NicsTab :resource="vm"/>
</a-tab-pane>
<a-tab-pane v-if="$store.getters.features.instancesdisksstatsretentionenabled" :tab="$t('label.volume.metrics')" key="volumestats">
<StatsTab :resource="volume" :resourceType="'Volume'"/>
</a-tab-pane>
<a-tab-pane :tab="$t('label.metrics')" key="vmstats">
<StatsTab :resource="vm"/>
</a-tab-pane>
<a-tab-pane :tab="$t('label.events')" key="events" v-if="'listEvents' in $store.getters.apis">
<events-tab :resource="resource" resourceType="SharedFS" :loading="loading" />
</a-tab-pane>
</a-tabs>
</a-spin>
</template>
<script>
import { api } from '@/api'
import { mixinDevice } from '@/utils/mixin.js'
import Status from '@/components/widgets/Status'
import DetailsTab from '@/components/view/DetailsTab'
import StatsTab from '@/components/view/StatsTab'
import EventsTab from '@/components/view/EventsTab'
import NicsTab from '@/views/network/NicsTab.vue'
import TooltipButton from '@/components/widgets/TooltipButton'
export default {
name: 'SharedFSTab',
components: {
DetailsTab,
StatsTab,
EventsTab,
NicsTab,
TooltipButton,
Status
},
mixins: [mixinDevice],
props: {
resource: {
type: Object,
required: true
},
loading: {
type: Boolean,
default: false
}
},
inject: ['parentFetchData'],
data () {
return {
vm: {},
virtualmachines: [],
currentTab: 'details',
dataResource: {}
}
},
created () {
const self = this
this.dataResource = this.resource
this.fetchData()
window.addEventListener('popstate', function () {
self.setCurrentTab()
})
},
watch: {
resource: {
deep: true,
handler (newData, oldData) {
if (newData !== oldData) {
this.dataResource = newData
this.fetchData()
}
}
},
'$route.fullPath': function () {
this.setCurrentTab()
}
},
mounted () {
this.setCurrentTab()
},
methods: {
setCurrentTab () {
this.currentTab = this.$route.query.tab ? this.$route.query.tab : 'details'
},
handleChangeTab (e) {
this.currentTab = e
const query = Object.assign({}, this.$route.query)
query.tab = e
history.pushState(
{},
null,
'#' + this.$route.path + '?' + Object.keys(query).map(key => {
return (
encodeURIComponent(key) + '=' + encodeURIComponent(query[key])
)
}).join('&')
)
},
fetchInstances () {
if (!this.resource.virtualmachineid) {
return
}
this.instanceLoading = true
var params = {
id: this.resource.virtualmachineid,
listall: true
}
if (this.$store.getters.listAllProjects) {
params.projectid = '-1'
}
api('listVirtualMachines', params).then(json => {
this.virtualmachines = json.listvirtualmachinesresponse.virtualmachine || []
this.vm = this.virtualmachines[0]
})
this.instanceLoading = false
},
fetchVolumes () {
if (!this.resource.volumeid) {
return
}
this.volumeLoading = true
var params = {
id: this.resource.volumeid,
listsystemvms: 'true',
listall: true
}
api('listVolumes', params).then(json => {
this.volumes = json.listvolumesresponse.volume || []
this.volume = this.volumes[0]
})
this.volumeLoading = false
},
fetchData () {
this.fetchInstances()
this.fetchVolumes()
}
}
}
</script>
<style lang="scss" scoped>
.page-header-wrapper-grid-content-main {
width: 100%;
height: 100%;
min-height: 100%;
transition: 0.3s;
}
.info {
font-size: 0.8rem;
}
</style>

View File

@ -0,0 +1,147 @@
// 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.
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-form
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
@finish="handleSubmit"
>
<a-form-item name="name" ref="name" :label="$t('label.name')">
<a-input
v-model:value="form.name"
:placeholder="$t('label.name')"/>
</a-form-item>
<a-form-item name="description" ref="description" :label="$t('label.description')">
<a-input
v-model:value="form.description"
:placeholder="$t('label.description')"/>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
import { mixinForm } from '@/utils/mixin'
import TooltipLabel from '@/components/widgets/TooltipLabel'
export default {
name: 'updateSharedFS',
mixins: [mixinForm],
props: {
resource: {
type: Object,
required: true
}
},
components: {
TooltipLabel
},
inject: ['parentFetchData'],
data () {
return {
loading: false
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('updateSharedFileSystem')
},
created () {
this.initForm()
this.fetchData()
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({})
this.rules = reactive({})
},
closeModal () {
this.$emit('close-action')
},
fetchData () {
this.loading = false
this.fillEditFormFieldValues()
},
fillEditFormFieldValues () {
const form = this.form
this.loading = true
Object.keys(this.apiParams).forEach(item => {
const field = this.apiParams[item]
let fieldValue = null
let fieldName = null
fieldName = field.name
fieldValue = this.resource[fieldName] ? this.resource[fieldName] : null
if (fieldValue) {
form[field.name] = fieldValue
}
})
this.loading = false
},
handleSubmit (e) {
if (this.loading) return
this.formRef.value.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
var data = {
id: this.resource.id,
name: values.name,
description: values.description
}
console.log(data)
console.log(this.form)
this.loading = true
api('updateSharedFileSystem', data).then(response => {
this.$emit('refresh-data')
this.$notification.success({
message: this.$t('label.update.sharedfs'),
description: `${this.$t('message.success.update.sharedfs')} ${data.name}`
})
this.closeModal()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}).catch((error) => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
}
}
}
</script>
<style lang="scss" scoped>
.form-layout {
width: 85vw;
@media (min-width: 760px) {
width: 500px;
}
}
</style>

View File

@ -33,10 +33,12 @@ import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -154,4 +156,8 @@ public class FileUtil {
}
return false;
}
public static String readResourceFile(String resource) throws IOException {
return IOUtils.toString(Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)), com.cloud.utils.StringUtils.getPreferredCharset());
}
}