weizhouapache commented on code in PR #9208: URL: https://github.com/apache/cloudstack/pull/9208#discussion_r1643938919
########## api/src/main/java/org/apache/cloudstack/storage/fileshare/FileShareService.java: ########## @@ -0,0 +1,41 @@ +// 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.fileshare; + +import java.util.List; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.user.storage.fileshare.CreateFileShareCmd; + +import com.cloud.utils.component.PluggableService; + +public interface FileShareService extends PluggableService { + + boolean configure(String name, Map<String, Object> params) throws ConfigurationException; Review Comment: this is not required IMHO ########## api/src/main/java/org/apache/cloudstack/api/command/user/storage/fileshare/CreateFileShareCmd.java: ########## @@ -0,0 +1,168 @@ +// 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.fileshare; + +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +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.FileShareResponse; +import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareService; + +@APICommand(name = "createFileShare", responseObject= FileShareResponse.class, description = "Creates a new file share of specified size and disk offering and attached to the given guest network", + responseView = ResponseObject.ResponseView.Restricted, entityType = FileShare.class, requestHasSensitiveInfo = false, since = "4.20.0") +public class CreateFileShareCmd extends BaseAsyncCreateCmd implements UserCmd { + + @Inject + FileShareService fileShareService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + required = true, + description = "the name of the file share.") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "the description for the file share.") + private String description; + + @Parameter(name = ApiConstants.SIZE, + type = CommandType.LONG, + required = true, + description = "the size of the file share in bytes") + private Long size; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "the zone id.") + private Long zoneId; + + @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.MOUNT_OPTIONS, + type = CommandType.STRING, + description = "the comma separated list of mount options to use for mounting this file share.") + private String mountOptions; + + @Parameter(name = ApiConstants.FORMAT, + type = CommandType.STRING, + description = "the filesystem format which will be installed on the file share.") Review Comment: what are the valid options ? ########## api/src/main/java/org/apache/cloudstack/api/command/user/storage/fileshare/ChangeFileShareDiskOfferingCmd.java: ########## @@ -0,0 +1,149 @@ +// 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.fileshare; + +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.FileShareResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareService; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.user.Account; +import com.cloud.user.AccountService; + +@APICommand(name = "changeFileShareDiskOffering", + responseObject= FileShareResponse.class, + description = "Change Disk offering of a File Share", + responseView = ResponseObject.ResponseView.Restricted, + entityType = FileShare.class, + requestHasSensitiveInfo = false, + since = "4.20.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ChangeFileShareDiskOfferingCmd extends BaseAsyncCmd implements UserCmd { + + @Inject + FileShareService fileShareService; + + @Inject + protected AccountService accountService; Review Comment: `BaseCmd` already has ``` public AccountService _accountService; ``` ########## api/src/main/java/com/cloud/storage/VolumeApiService.java: ########## @@ -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; Review Comment: is it necessary to be here (interface) ? ########## plugins/storage/fileshare/storagefsvm/src/main/resources/conf/fsvm-init.yml: ########## @@ -0,0 +1,196 @@ +#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/add-partition + 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 + } + + log "Running add script" + + # Variables + PARTITION="$1" + MOUNT_POINT="/mnt/fs" + EXPORT_DIR="$MOUNT_POINT/share" + PERMISSIONS="rw,sync,no_subtree_check" + + mkdir -p "$MOUNT_POINT" + + 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 $MOUNT_POINT $TYPE defaults 0 2" >> /etc/fstab + log "Added fstab entry." + + mount -a + + if mountpoint -q "$MOUNT_POINT"; then + log "$PARTITION is successfully mounted on $MOUNT_POINT" + else + log "Failed to mount $PARTITION on $MOUNT_POINT" + exit 1 + fi + + if [ ! -d "$EXPORT_DIR" ]; then + log "Creating export directory..." + mkdir -p "$EXPORT_DIR" + chown nobody:nogroup "$EXPORT_DIR" + chmod 777 "$EXPORT_DIR" + 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 + + log "NFS share created successfully." + log "Finished running add script." + + - path: /usr/local/bin/resize-partition + 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 + } + log "Running resize script." + PARTITION="$1" + + FILESYSTEM={{ fsvm.filesystem }} + if [ "$FILESYSTEM" == "xfs" ]; then + xfs_growfs "$PARTITION" + elif [ "$FILESYSTEM" == "ext4" ]; then + resize2fs "$PARTITION" + else + log "Invalid filesystem type specified. Use 'xfs' or 'ext4'." + exit 1 + fi + + log "Finished running resize script." + + - path: /etc/systemd/system/[email protected] + permissions: '0700' + owner: root:root + content: | + [Unit] + Description=CloudStack service to start dhclient + + [Install] + WantedBy=multi-user.target + + [Service] + Type=simple + ExecStart=/usr/sbin/dhclient %I + Restart=on-failure + + - path: /usr/local/bin/fsvm-setup + 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 + } + + HYPERVISOR_INFO=$(dmesg | grep -i hypervisor) Review Comment: hypervisor can be set in a similar way as `{{ fsvm.filesystem }}` ########## plugins/storage/fileshare/storagefsvm/src/main/java/org/apache/cloudstack/storage/fileshare/lifecycle/StorageFsVmFileShareLifeCycle.java: ########## @@ -0,0 +1,329 @@ +// 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.fileshare.lifecycle; + +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareVmNamePrefix; +import static org.apache.cloudstack.storage.fileshare.provider.StorageFsVmFileShareProvider.STORAGEFSVM_MIN_CPU_COUNT; +import static org.apache.cloudstack.storage.fileshare.provider.StorageFsVmFileShareProvider.STORAGEFSVM_MIN_RAM_SIZE; + +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceAllocationException; +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.Pair; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javax.inject.Inject; + +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.NicVO; +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.BaseCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareLifeCycle; +import org.apache.cloudstack.storage.fileshare.FileShareService; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +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.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachineManager; + +public class StorageFsVmFileShareLifeCycle implements FileShareLifeCycle { + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + private FileShareService fileShareService; + + @Inject + private AccountManager accountMgr; + + @Inject + private EntityManager entityMgr; + + @Inject + protected ResourceManager resourceMgr; + + @Inject + private VirtualMachineManager virtualMachineManager; + + @Inject + private VolumeApiService volumeApiService; + + @Inject + protected UserVmService userVmService; + + @Inject + protected UserVmManager userVmManager; + + @Inject + private VMTemplateDao templateDao; + + @Inject + VolumeDao volumeDao; + + @Inject + private UserVmDao userVmDao; + + @Inject + NicDao nicDao; + + @Inject + ServiceOfferingDao serviceOfferingDao; + + @Inject + private DiskOfferingDao diskOfferingDao; + + @Inject + protected LaunchPermissionDao launchPermissionDao; + + private String readResourceFile(String resource) { + try { + return IOUtils.toString(Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)), com.cloud.utils.StringUtils.getPreferredCharset()); + } catch (IOException e) { + throw new CloudRuntimeException("Unable to read the user data resource file due to exception " + e.getMessage()); + } + } + + private String getStorageFsVmConfig(final String fileSystem) { + String fsVmConfig = readResourceFile("/conf/fsvm-init.yml"); + final String filesystem = "{{ fsvm.filesystem }}"; + fsVmConfig = fsVmConfig.replace(filesystem, fileSystem); + return fsVmConfig; + } + + private String getStorageFsVmName(String fileShareName) { + String prefix = String.format("%s-%s", FileShareVmNamePrefix, 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() + FileShareVmNamePrefix.length(); + if (nameLength > 63) { + int prefixLength = prefix.length() - (nameLength - 63); + prefix = prefix.substring(0, prefixLength); + } + return (String.format("%s-%s", prefix, suffix)); + } + + private UserVm createFileShareVM(Long zoneId, Account owner, List<Long> networkIds, String name, Long serviceOfferingId, Long diskOfferingId, FileShare.FileSystemType fileSystem, Long size, Long minIops, Long maxIops) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId); + + Long diskSize = null; + if (diskOfferingId != null) { + DiskOfferingVO diskOffering = diskOfferingDao.findById(diskOfferingId); + if (diskOffering.isCustomized()) { + diskSize = size; + } + } + + DataCenter zone = entityMgr.findById(DataCenter.class, zoneId); + Hypervisor.HypervisorType availableHypervisor = resourceMgr.getAvailableHypervisor(zoneId); + VMTemplateVO template = templateDao.findSystemVMReadyTemplate(zoneId, availableHypervisor); + + LaunchPermissionVO existingPermission = launchPermissionDao.findByTemplateAndAccount(template.getId(), owner.getId()); + if (existingPermission == null) { + LaunchPermissionVO launchPermission = new LaunchPermissionVO(template.getId(), owner.getId()); + launchPermissionDao.persist(launchPermission); + } + + String hostName = getStorageFsVmName(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>(); + + UserVm vm; + String fsVmConfig = getStorageFsVmConfig(fileSystem.toString().toLowerCase()); + String base64UserData = Base64.encodeBase64String(fsVmConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset())); + vm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, hostName, hostName, + diskOfferingId, diskSize, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, + null, null, keypairs, null, addrs, null, null, null, + customParameterMap, null, null, null, null, + true, UserVmManager.STORAGEFSVM, null); + return vm; + } + + @Override + public void checkPrerequisites(DataCenter zone, Long serviceOfferingId) { + ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId); + if (serviceOffering.getCpu() < STORAGEFSVM_MIN_CPU_COUNT.valueIn(zone.getId())) { + throw new InvalidParameterValueException("Service offering's number of cpu should be greater than or equal to " + STORAGEFSVM_MIN_CPU_COUNT.key()); + } + if (serviceOffering.getRamSize() < STORAGEFSVM_MIN_RAM_SIZE.valueIn(zone.getId())) { + throw new InvalidParameterValueException("Service offering's ram size should be greater than or equal to " + STORAGEFSVM_MIN_RAM_SIZE.key()); + } + if (serviceOffering.isOfferHA() == false) { Review Comment: ```suggestion if (!serviceOffering.isOfferHA()) { ``` ########## api/src/main/java/org/apache/cloudstack/api/command/user/storage/fileshare/CreateFileShareCmd.java: ########## @@ -0,0 +1,168 @@ +// 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.fileshare; + +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +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.FileShareResponse; +import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareService; + +@APICommand(name = "createFileShare", responseObject= FileShareResponse.class, description = "Creates a new file share of specified size and disk offering and attached to the given guest network", + responseView = ResponseObject.ResponseView.Restricted, entityType = FileShare.class, requestHasSensitiveInfo = false, since = "4.20.0") +public class CreateFileShareCmd extends BaseAsyncCreateCmd implements UserCmd { + + @Inject + FileShareService fileShareService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + required = true, + description = "the name of the file share.") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "the description for the file share.") + private String description; + + @Parameter(name = ApiConstants.SIZE, + type = CommandType.LONG, + required = true, + description = "the size of the file share in bytes") + private Long size; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "the zone id.") + private Long zoneId; + + @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.MOUNT_OPTIONS, + type = CommandType.STRING, + description = "the comma separated list of mount options to use for mounting this file share.") + private String mountOptions; + + @Parameter(name = ApiConstants.FORMAT, + type = CommandType.STRING, + description = "the filesystem format which will be installed on the file share.") + private String fsFormat; + + @Parameter(name = ApiConstants.PROVIDER, + type = CommandType.STRING, + description = "the provider to be used for the file share.") Review Comment: we could add a sentence like `the list of providers can be fetched by listFileShareProviders API` ? ########## api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java: ########## @@ -82,6 +82,12 @@ public class ListDiskOfferingsCmd extends BaseListProjectAndAccountResourcesCmd since = "4.20.0") private Long virtualMachineId; + @Parameter(name = ApiConstants.FILESHARE, + type = CommandType.BOOLEAN, + description = "Disk offering is meant for File shares", + since = "4.20.0") + private Boolean fileShare; Review Comment: maybe `forFileShare` ? (similar as `forVpc` ?) ########## api/src/main/java/org/apache/cloudstack/api/command/user/storage/fileshare/ChangeFileShareServiceOfferingCmd.java: ########## @@ -0,0 +1,151 @@ +// 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.fileshare; + +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.FileShareResponse; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareService; + +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.user.AccountService; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = "changeFileShareServiceOffering", + responseObject= FileShareResponse.class, + description = "Change Service offering of a File Share", + responseView = ResponseObject.ResponseView.Restricted, + entityType = FileShare.class, + requestHasSensitiveInfo = false, + since = "4.20.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ChangeFileShareServiceOfferingCmd extends BaseAsyncCmd implements UserCmd { + + @Inject + FileShareService fileShareService; + + @Inject + protected AccountService accountService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + required = true, + entityType = FileShareResponse.class, + description = "the ID of the file share") + private Long id; + + @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, + type = CommandType.UUID, + entityType = ServiceOfferingResponse.class, + required = true, + description = "the offering to use for the file share vm.") + private Long serviceOfferingId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getServiceOfferingId() { + return serviceOfferingId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getEventType() { + return EventTypes.EVENT_FILESHARE_UPDATE; Review Comment: maybe another event like `EVENT_FILESHARE_CHANGE_SERVICE_OFFERING` ? ########## api/src/main/java/org/apache/cloudstack/api/command/user/storage/fileshare/ChangeFileShareDiskOfferingCmd.java: ########## @@ -0,0 +1,149 @@ +// 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.fileshare; + +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.FileShareResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareService; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.user.Account; +import com.cloud.user.AccountService; + +@APICommand(name = "changeFileShareDiskOffering", + responseObject= FileShareResponse.class, + description = "Change Disk offering of a File Share", + responseView = ResponseObject.ResponseView.Restricted, + entityType = FileShare.class, + requestHasSensitiveInfo = false, + since = "4.20.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ChangeFileShareDiskOfferingCmd extends BaseAsyncCmd implements UserCmd { + + @Inject + FileShareService fileShareService; + + @Inject + protected AccountService accountService; Review Comment: these can be injected in `BaseCmd` ########## api/src/main/java/org/apache/cloudstack/api/command/user/storage/fileshare/CreateFileShareCmd.java: ########## @@ -0,0 +1,317 @@ +// 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.fileshare; + +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.user.AccountService; +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.FileShareResponse; +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.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareService; + +@APICommand(name = "createFileShare", + responseObject= FileShareResponse.class, + description = "Create a new File Share of specified size and disk offering, attached to the given network", + responseView = ResponseObject.ResponseView.Restricted, + entityType = FileShare.class, + requestHasSensitiveInfo = false, + since = "4.20.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class CreateFileShareCmd extends BaseAsyncCreateCmd implements UserCmd { + + @Inject + FileShareService fileShareService; + + @Inject + protected AccountService accountService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + required = true, + description = "the name of the file share.") + private String name; + + @Parameter(name = ApiConstants.ACCOUNT, + type = BaseCmd.CommandType.STRING, + description = "the account associated with the file share. 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 file share. If used with the account parameter" + + " returns the file share associated with the account for the specified domain." + + "If account is NOT provided then the file share 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 file share. Mutually exclusive with account parameter") + private Long projectId; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "the description for the file share.") + private String description; + + @Parameter(name = ApiConstants.SIZE, + type = CommandType.LONG, + description = "the size of the file share 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.") + private Long diskOfferingId; + + @Parameter(name = ApiConstants.MIN_IOPS, Review Comment: are `miniops` and `maxiops` properties of the disk offering ? ########## api/src/main/java/org/apache/cloudstack/api/command/user/storage/fileshare/CreateFileShareCmd.java: ########## @@ -0,0 +1,168 @@ +// 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.fileshare; + +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +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.FileShareResponse; +import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareService; + +@APICommand(name = "createFileShare", responseObject= FileShareResponse.class, description = "Creates a new file share of specified size and disk offering and attached to the given guest network", + responseView = ResponseObject.ResponseView.Restricted, entityType = FileShare.class, requestHasSensitiveInfo = false, since = "4.20.0") +public class CreateFileShareCmd extends BaseAsyncCreateCmd implements UserCmd { + + @Inject + FileShareService fileShareService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + required = true, + description = "the name of the file share.") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "the description for the file share.") + private String description; + + @Parameter(name = ApiConstants.SIZE, + type = CommandType.LONG, + required = true, + description = "the size of the file share in bytes") + private Long size; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "the zone id.") + private Long zoneId; + + @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.MOUNT_OPTIONS, + type = CommandType.STRING, + description = "the comma separated list of mount options to use for mounting this file share.") + private String mountOptions; + + @Parameter(name = ApiConstants.FORMAT, + type = CommandType.STRING, + description = "the filesystem format which will be installed on the file share.") + private String fsFormat; + + @Parameter(name = ApiConstants.PROVIDER, + type = CommandType.STRING, + description = "the provider to be used for the file share.") + private String fileShareProviderName; + + @Parameter(name = ApiConstants.NETWORK_IDS, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = NetworkResponse.class, + description = "list of network ids to attach file share to") + private List<Long> networkIds; Review Comment: is this `required` as well ? ########## api/src/main/java/org/apache/cloudstack/api/response/FileShareResponse.java: ########## @@ -0,0 +1,360 @@ +// 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.fileshare.FileShare; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + + +@EntityReference(value = FileShare.class) +public class FileShareResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the file share") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "name of the file share") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "description of the file share") + 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 file share") + private Long size; + + @SerializedName(ApiConstants.SIZEGB) + @Param(description = "size of the file share in GiB") + private String sizeGB; + + @SerializedName(ApiConstants.DISK_OFFERING_ID) + @Param(description = "disk offering for the file share") Review Comment: ```suggestion @Param(description = "disk offering ID for the file share") ``` ########## api/src/main/java/org/apache/cloudstack/storage/fileshare/FileShareLifeCycle.java: ########## @@ -0,0 +1,40 @@ +// 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.fileshare; + +import com.cloud.dc.DataCenter; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.Pair; + +public interface FileShareLifeCycle { + void checkPrerequisites(DataCenter zone, Long serviceOfferingId); + + Pair<Long, Long> commitFileShare(FileShare fileShare, Long networkId, Long diskOfferingId, Long size, Long minIops, Long maxIops) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException; Review Comment: is it needed to be defined in the interface ? ########## api/src/main/java/org/apache/cloudstack/storage/fileshare/FileShareService.java: ########## @@ -0,0 +1,41 @@ +// 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.fileshare; + +import java.util.List; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.user.storage.fileshare.CreateFileShareCmd; + +import com.cloud.utils.component.PluggableService; + +public interface FileShareService extends PluggableService { + + boolean configure(String name, Map<String, Object> params) throws ConfigurationException; + + List<Class<?>> getCommands(); Review Comment: this is not needed either ########## api/src/main/java/org/apache/cloudstack/api/response/FileShareResponse.java: ########## @@ -0,0 +1,360 @@ +// 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.fileshare.FileShare; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + + +@EntityReference(value = FileShare.class) +public class FileShareResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the file share") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "name of the file share") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "description of the file share") + 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 file share") + private Long size; + + @SerializedName(ApiConstants.SIZEGB) + @Param(description = "size of the file share in GiB") + private String sizeGB; + + @SerializedName(ApiConstants.DISK_OFFERING_ID) + @Param(description = "disk offering for the file share") + private String diskOfferingId; + + @SerializedName("diskofferingname") + @Param(description = "disk offering for the file share") + private String diskOfferingName; + + @SerializedName("iscustomdiskoffering") + @Param(description = "disk offering for the file share has custom size") + private Boolean isCustomDiskOffering; + + @SerializedName("diskofferingdisplaytext") + @Param(description = "disk offering display text for the file share") + private String diskOfferingDisplayText; + + @SerializedName(ApiConstants.SERVICE_OFFERING_ID) + @Param(description = "service offering for the file share") Review Comment: ``` @Param(description = "service offering ID for the file share") ``` ########## plugins/storage/fileshare/storagefsvm/src/main/java/org/apache/cloudstack/storage/fileshare/lifecycle/StorageFsVmFileShareLifeCycle.java: ########## @@ -0,0 +1,329 @@ +// 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.fileshare.lifecycle; + +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareVmNamePrefix; +import static org.apache.cloudstack.storage.fileshare.provider.StorageFsVmFileShareProvider.STORAGEFSVM_MIN_CPU_COUNT; +import static org.apache.cloudstack.storage.fileshare.provider.StorageFsVmFileShareProvider.STORAGEFSVM_MIN_RAM_SIZE; + +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceAllocationException; +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.Pair; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javax.inject.Inject; + +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.NicVO; +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.BaseCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareLifeCycle; +import org.apache.cloudstack.storage.fileshare.FileShareService; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +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.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachineManager; + +public class StorageFsVmFileShareLifeCycle implements FileShareLifeCycle { + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + private FileShareService fileShareService; + + @Inject + private AccountManager accountMgr; + + @Inject + private EntityManager entityMgr; + + @Inject + protected ResourceManager resourceMgr; + + @Inject + private VirtualMachineManager virtualMachineManager; + + @Inject + private VolumeApiService volumeApiService; + + @Inject + protected UserVmService userVmService; + + @Inject + protected UserVmManager userVmManager; + + @Inject + private VMTemplateDao templateDao; + + @Inject + VolumeDao volumeDao; + + @Inject + private UserVmDao userVmDao; + + @Inject + NicDao nicDao; + + @Inject + ServiceOfferingDao serviceOfferingDao; + + @Inject + private DiskOfferingDao diskOfferingDao; + + @Inject + protected LaunchPermissionDao launchPermissionDao; + + private String readResourceFile(String resource) { + try { + return IOUtils.toString(Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)), com.cloud.utils.StringUtils.getPreferredCharset()); + } catch (IOException e) { + throw new CloudRuntimeException("Unable to read the user data resource file due to exception " + e.getMessage()); + } + } + + private String getStorageFsVmConfig(final String fileSystem) { + String fsVmConfig = readResourceFile("/conf/fsvm-init.yml"); + final String filesystem = "{{ fsvm.filesystem }}"; + fsVmConfig = fsVmConfig.replace(filesystem, fileSystem); + return fsVmConfig; + } + + private String getStorageFsVmName(String fileShareName) { + String prefix = String.format("%s-%s", FileShareVmNamePrefix, 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() + FileShareVmNamePrefix.length(); + if (nameLength > 63) { + int prefixLength = prefix.length() - (nameLength - 63); + prefix = prefix.substring(0, prefixLength); + } + return (String.format("%s-%s", prefix, suffix)); + } + + private UserVm createFileShareVM(Long zoneId, Account owner, List<Long> networkIds, String name, Long serviceOfferingId, Long diskOfferingId, FileShare.FileSystemType fileSystem, Long size, Long minIops, Long maxIops) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId); + + Long diskSize = null; + if (diskOfferingId != null) { + DiskOfferingVO diskOffering = diskOfferingDao.findById(diskOfferingId); + if (diskOffering.isCustomized()) { + diskSize = size; + } + } + + DataCenter zone = entityMgr.findById(DataCenter.class, zoneId); + Hypervisor.HypervisorType availableHypervisor = resourceMgr.getAvailableHypervisor(zoneId); + VMTemplateVO template = templateDao.findSystemVMReadyTemplate(zoneId, availableHypervisor); + + LaunchPermissionVO existingPermission = launchPermissionDao.findByTemplateAndAccount(template.getId(), owner.getId()); + if (existingPermission == null) { + LaunchPermissionVO launchPermission = new LaunchPermissionVO(template.getId(), owner.getId()); + launchPermissionDao.persist(launchPermission); + } + + String hostName = getStorageFsVmName(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>(); + + UserVm vm; + String fsVmConfig = getStorageFsVmConfig(fileSystem.toString().toLowerCase()); + String base64UserData = Base64.encodeBase64String(fsVmConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset())); + vm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, hostName, hostName, + diskOfferingId, diskSize, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, + null, null, keypairs, null, addrs, null, null, null, + customParameterMap, null, null, null, null, + true, UserVmManager.STORAGEFSVM, null); + return vm; + } + + @Override + public void checkPrerequisites(DataCenter zone, Long serviceOfferingId) { + ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId); + if (serviceOffering.getCpu() < STORAGEFSVM_MIN_CPU_COUNT.valueIn(zone.getId())) { + throw new InvalidParameterValueException("Service offering's number of cpu should be greater than or equal to " + STORAGEFSVM_MIN_CPU_COUNT.key()); + } + if (serviceOffering.getRamSize() < STORAGEFSVM_MIN_RAM_SIZE.valueIn(zone.getId())) { + throw new InvalidParameterValueException("Service offering's ram size should be greater than or equal to " + STORAGEFSVM_MIN_RAM_SIZE.key()); + } + if (serviceOffering.isOfferHA() == false) { + throw new InvalidParameterValueException("Service offering's should be HA enabled"); + } + + Hypervisor.HypervisorType availableHypervisor = resourceMgr.getAvailableHypervisor(zone.getId()); + VMTemplateVO template = templateDao.findSystemVMReadyTemplate(zone.getId(), availableHypervisor); + if (template == null) { + throw new CloudRuntimeException(String.format("Unable to find the system templates or it was not downloaded in %s.", zone.toString())); + } + } + + @Override + public Pair<Long, Long> commitFileShare(FileShare fileShare, Long networkId, Long diskOfferingId, Long size, Long minIops, Long maxIops) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + Account owner = accountMgr.getActiveAccountById(fileShare.getAccountId()); + UserVm vm = createFileShareVM(fileShare.getDataCenterId(), owner, List.of(networkId), fileShare.getName(), fileShare.getServiceOfferingId(), diskOfferingId, fileShare.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 startFileShare(FileShare fileShare) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException { + UserVmVO vm = userVmDao.findById(fileShare.getVmId()); + userVmManager.startVirtualMachine(vm); + } + + @Override + public boolean stopFileShare(FileShare fileShare, Boolean forced) { + userVmManager.stopVirtualMachine(fileShare.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)) { Review Comment: the previous line will expunge the vm, right ? ``` UserVm vm = userVmService.destroyVm(userVM.getId(), true); ``` ########## systemvm/debian/opt/cloud/bin/setup/storagefsvm.sh: ########## @@ -0,0 +1,61 @@ +#!/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_storagefsvm() { + log_it "Starting cloud-init services" + log_it "Setting up storagefsvm" + + 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 + + swapoff -a + sudo sed -i '/ swap / s/^/#/' /etc/fstab + log_it "Swap disabled" + + log_it "Setting up interfaces" + setup_system_rfc1918_internal + + log_it "Setting up entry in hosts" + sed -i /$NAME/d /etc/hosts + echo "$ETH0_IP $NAME" >> /etc/hosts + + echo "export PATH='$PATH:/opt/bin/'">> ~/.bashrc + + disable_rpfilter + enable_fwding 1 Review Comment: should it be 0 or 1 ? ########## plugins/storage/fileshare/storagefsvm/src/main/resources/conf/fsvm-init.yml: ########## @@ -0,0 +1,196 @@ +#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/add-partition + 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 + } + + log "Running add script" + + # Variables + PARTITION="$1" + MOUNT_POINT="/mnt/fs" + EXPORT_DIR="$MOUNT_POINT/share" + PERMISSIONS="rw,sync,no_subtree_check" + + mkdir -p "$MOUNT_POINT" + + 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 $MOUNT_POINT $TYPE defaults 0 2" >> /etc/fstab + log "Added fstab entry." + + mount -a + + if mountpoint -q "$MOUNT_POINT"; then + log "$PARTITION is successfully mounted on $MOUNT_POINT" + else + log "Failed to mount $PARTITION on $MOUNT_POINT" + exit 1 + fi + + if [ ! -d "$EXPORT_DIR" ]; then + log "Creating export directory..." + mkdir -p "$EXPORT_DIR" + chown nobody:nogroup "$EXPORT_DIR" + chmod 777 "$EXPORT_DIR" + 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 + + log "NFS share created successfully." + log "Finished running add script." + + - path: /usr/local/bin/resize-partition + 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 + } + log "Running resize script." + PARTITION="$1" + + FILESYSTEM={{ fsvm.filesystem }} + if [ "$FILESYSTEM" == "xfs" ]; then + xfs_growfs "$PARTITION" + elif [ "$FILESYSTEM" == "ext4" ]; then + resize2fs "$PARTITION" + else + log "Invalid filesystem type specified. Use 'xfs' or 'ext4'." + exit 1 + fi + + log "Finished running resize script." + + - path: /etc/systemd/system/[email protected] + permissions: '0700' + owner: root:root + content: | + [Unit] + Description=CloudStack service to start dhclient + + [Install] + WantedBy=multi-user.target + + [Service] + Type=simple + ExecStart=/usr/sbin/dhclient %I + Restart=on-failure + + - path: /usr/local/bin/fsvm-setup + 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 + } + + HYPERVISOR_INFO=$(dmesg | grep -i hypervisor) + if echo "$HYPERVISOR_INFO" | grep -i "kvm"; then + BLOCK_DEVICE="vdb" + elif echo "$HYPERVISOR_INFO" | grep -i "xen"; then + BLOCK_DEVICE="xvdb" + elif echo "$HYPERVISOR_INFO" | grep -i "vmware"; then + BLOCK_DEVICE="sdb" + else + log "Unknown hypervisor" + exit 1 + fi + + PARTITION="/dev/$BLOCK_DEVICE" + ADD_PARTITION_FILE="/usr/local/bin/add-partition" + RESIZE_PARTITION_FILE="/usr/local/bin/resize-partition" + + UDEV_ADD_RULE='KERNEL=="$BLOCK_DEVICE", ACTION=="add", SUBSYSTEM=="block", RUN+="$ADD_PARTITION_FILE /dev/%k"' + UDEV_RESIZE_RULE='KERNEL=="$BLOCK_DEVICE", ACTION=="change", SUBSYSTEM=="block", RUN+="$RESIZE_PARTITION_FILE /dev/%k"' + 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-partition.rules + + echo "$UDEV_ADD_NIC_RULE" > /etc/udev/rules.d/99-add-nic.rules + + if [ ! -b "$PARTITION" ]; then + log "Partition $PARTITION does not exist. Adding udev rule for adding the partition" + echo "$UDEV_ADD_RULE" > /etc/udev/rules.d/99-add-partition.rules + else + log "Partition $PARTITION already 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." + +runcmd: + - echo "test" > test Review Comment: maybe add a message to file `/root/success` ? ########## systemvm/debian/opt/cloud/bin/setup/storagefsvm.sh: ########## @@ -0,0 +1,61 @@ +#!/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_storagefsvm() { + log_it "Starting cloud-init services" + log_it "Setting up storagefsvm" + + 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 + + swapoff -a + sudo sed -i '/ swap / s/^/#/' /etc/fstab + log_it "Swap disabled" + + log_it "Setting up interfaces" + setup_system_rfc1918_internal + + log_it "Setting up entry in hosts" + sed -i /$NAME/d /etc/hosts + echo "$ETH0_IP $NAME" >> /etc/hosts + + echo "export PATH='$PATH:/opt/bin/'">> ~/.bashrc + + disable_rpfilter + enable_fwding 1 + enable_irqbalance 0 + setup_ntp + dhclient -1 + + rm -f /etc/logrotate.d/cloud + + if [ -f /home/cloud/success ]; then Review Comment: is the file written/updated somewhere ? maybe write to `/root/success` and use the filename here ? ########## plugins/storage/fileshare/storagefsvm/src/main/java/org/apache/cloudstack/storage/fileshare/lifecycle/StorageFsVmFileShareLifeCycle.java: ########## @@ -0,0 +1,329 @@ +// 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.fileshare.lifecycle; + +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareVmNamePrefix; +import static org.apache.cloudstack.storage.fileshare.provider.StorageFsVmFileShareProvider.STORAGEFSVM_MIN_CPU_COUNT; +import static org.apache.cloudstack.storage.fileshare.provider.StorageFsVmFileShareProvider.STORAGEFSVM_MIN_RAM_SIZE; + +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceAllocationException; +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.Pair; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javax.inject.Inject; + +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.NicVO; +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.BaseCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.fileshare.FileShare; +import org.apache.cloudstack.storage.fileshare.FileShareLifeCycle; +import org.apache.cloudstack.storage.fileshare.FileShareService; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +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.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachineManager; + +public class StorageFsVmFileShareLifeCycle implements FileShareLifeCycle { + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + private FileShareService fileShareService; + + @Inject + private AccountManager accountMgr; + + @Inject + private EntityManager entityMgr; + + @Inject + protected ResourceManager resourceMgr; + + @Inject + private VirtualMachineManager virtualMachineManager; + + @Inject + private VolumeApiService volumeApiService; + + @Inject + protected UserVmService userVmService; + + @Inject + protected UserVmManager userVmManager; + + @Inject + private VMTemplateDao templateDao; + + @Inject + VolumeDao volumeDao; + + @Inject + private UserVmDao userVmDao; + + @Inject + NicDao nicDao; + + @Inject + ServiceOfferingDao serviceOfferingDao; + + @Inject + private DiskOfferingDao diskOfferingDao; + + @Inject + protected LaunchPermissionDao launchPermissionDao; + + private String readResourceFile(String resource) { + try { + return IOUtils.toString(Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)), com.cloud.utils.StringUtils.getPreferredCharset()); + } catch (IOException e) { + throw new CloudRuntimeException("Unable to read the user data resource file due to exception " + e.getMessage()); + } + } + + private String getStorageFsVmConfig(final String fileSystem) { + String fsVmConfig = readResourceFile("/conf/fsvm-init.yml"); + final String filesystem = "{{ fsvm.filesystem }}"; + fsVmConfig = fsVmConfig.replace(filesystem, fileSystem); + return fsVmConfig; + } + + private String getStorageFsVmName(String fileShareName) { + String prefix = String.format("%s-%s", FileShareVmNamePrefix, 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() + FileShareVmNamePrefix.length(); + if (nameLength > 63) { + int prefixLength = prefix.length() - (nameLength - 63); + prefix = prefix.substring(0, prefixLength); + } + return (String.format("%s-%s", prefix, suffix)); + } + + private UserVm createFileShareVM(Long zoneId, Account owner, List<Long> networkIds, String name, Long serviceOfferingId, Long diskOfferingId, FileShare.FileSystemType fileSystem, Long size, Long minIops, Long maxIops) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId); + + Long diskSize = null; + if (diskOfferingId != null) { + DiskOfferingVO diskOffering = diskOfferingDao.findById(diskOfferingId); + if (diskOffering.isCustomized()) { + diskSize = size; + } + } + + DataCenter zone = entityMgr.findById(DataCenter.class, zoneId); + Hypervisor.HypervisorType availableHypervisor = resourceMgr.getAvailableHypervisor(zoneId); + VMTemplateVO template = templateDao.findSystemVMReadyTemplate(zoneId, availableHypervisor); + + LaunchPermissionVO existingPermission = launchPermissionDao.findByTemplateAndAccount(template.getId(), owner.getId()); + if (existingPermission == null) { + LaunchPermissionVO launchPermission = new LaunchPermissionVO(template.getId(), owner.getId()); + launchPermissionDao.persist(launchPermission); + } + + String hostName = getStorageFsVmName(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>(); + + UserVm vm; + String fsVmConfig = getStorageFsVmConfig(fileSystem.toString().toLowerCase()); + String base64UserData = Base64.encodeBase64String(fsVmConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset())); + vm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, hostName, hostName, Review Comment: maybe check the zone type here ? (only advanced zone without SG is supported, right ?) ########## server/src/main/java/org/apache/cloudstack/storage/fileshare/FileShareServiceImpl.java: ########## @@ -0,0 +1,687 @@ +// 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.fileshare; + +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareCleanupDelay; +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareCleanupInterval; +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareFeatureEnabled; + +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.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +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.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; +import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.db.EntityManager; +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.api.ResponseObject; +import org.apache.cloudstack.api.command.user.storage.fileshare.ChangeFileShareDiskOfferingCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ChangeFileShareServiceOfferingCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.CreateFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ExpungeFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ListFileShareProvidersCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ListFileSharesCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.DestroyFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.RecoverFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.RestartFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.StartFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.StopFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.UpdateFileShareCmd; +import org.apache.cloudstack.api.response.FileShareResponse; +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.fileshare.dao.FileShareDao; +import org.apache.cloudstack.storage.fileshare.FileShare.Event; +import org.apache.cloudstack.storage.fileshare.FileShare.State; +import org.apache.cloudstack.storage.fileshare.query.dao.FileShareJoinDao; +import org.apache.cloudstack.storage.fileshare.query.vo.FileShareJoinVO; + +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; + +public class FileShareServiceImpl extends ManagerBase implements FileShareService, Configurable { + + @Inject + private AccountManager accountMgr; + + @Inject + private EntityManager entityMgr; + + @Inject + private ConfigurationManager configMgr; + + @Inject + private VolumeApiService volumeApiService; + + @Inject + private FileShareDao fileShareDao; + + @Inject + private FileShareJoinDao fileShareJoinDao; + + @Inject + private DiskOfferingDao diskOfferingDao; + + @Inject + ConfigurationDao configDao; + + @Inject + VolumeDao volumeDao; + + protected List<FileShareProvider> fileShareProviders; + + private Map<String, FileShareProvider> fileShareProviderMap = new HashMap<>(); + + private final StateMachine2<State, Event, FileShare> fileShareStateMachine; + + ScheduledExecutorService _executor = null; + + public FileShareServiceImpl() { + this.fileShareStateMachine = State.getStateMachine(); + } + + @Override + public boolean start() { + fileShareProviderMap.clear(); + for (final FileShareProvider provider : fileShareProviders) { + fileShareProviderMap.put(provider.getName(), provider); + provider.configure(); + } + _executor.scheduleWithFixedDelay(new FileShareGarbageCollector(), FileShareCleanupInterval.value(), FileShareCleanupInterval.value(), TimeUnit.SECONDS); + return true; + } + + public boolean stop() { + _executor.shutdown(); + return true; + } + + @Override + public List<FileShareProvider> getFileShareProviders() { + return fileShareProviders; + } + + @Override + public boolean stateTransitTo(FileShare fileShare, Event event) { + try { + return fileShareStateMachine.transitTo(fileShare, event, null, fileShareDao); + } catch (NoTransitionException e) { + logger.debug(String.format("Failed during event % for File Share %s [%s] due to exception %", + event.toString(), fileShare.getName(), fileShare.getId(), e)); + return false; + } + } + + @Override + public void setFileShareProviders(List<FileShareProvider> fileShareProviders) { + this.fileShareProviders = fileShareProviders; + } + + @Override + public FileShareProvider getFileShareProvider(String fileShareProviderName) { + if (fileShareProviderMap.containsKey(fileShareProviderName)) { + return fileShareProviderMap.get(fileShareProviderName); + } + throw new CloudRuntimeException("Invalid file share provider name!"); + } + + @Override + public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException { + Map<String, String> configs = configDao.getConfiguration("management-server", params); + String workers = configs.get("expunge.workers"); + int wrks = NumbersUtil.parseInt(workers, 10); + _executor = Executors.newScheduledThreadPool(wrks, new NamedThreadFactory("FileShare-Scavenger")); + return true; + } + + @Override + public List<Class<?>> getCommands() { + final List<Class<?>> cmdList = new ArrayList<>(); + if (FileShareFeatureEnabled.value() == true) { + cmdList.add(ListFileShareProvidersCmd.class); + cmdList.add(CreateFileShareCmd.class); + cmdList.add(ListFileSharesCmd.class); + cmdList.add(UpdateFileShareCmd.class); + cmdList.add(DestroyFileShareCmd.class); + cmdList.add(RestartFileShareCmd.class); + cmdList.add(StartFileShareCmd.class); + cmdList.add(StopFileShareCmd.class); + cmdList.add(ChangeFileShareDiskOfferingCmd.class); + cmdList.add(ChangeFileShareServiceOfferingCmd.class); + cmdList.add(RecoverFileShareCmd.class); + cmdList.add(ExpungeFileShareCmd.class); + } + return cmdList; + } + + private DataCenter validateAndGetZone(Long zoneId) { + DataCenter zone = entityMgr.findById(DataCenter.class, 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())); + } + return zone; + } + + private DiskOfferingVO validateAndGetDiskOffering(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"); + } + return diskOffering; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FILESHARE_CREATE, eventDescription = "creating fileshare", create = true) + public FileShare createFileShare(CreateFileShareCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + Account caller = CallContext.current().getCallingAccount(); + + long ownerId = cmd.getEntityOwnerId(); + Account owner = accountMgr.getActiveAccountById(ownerId); + accountMgr.checkAccess(caller, null, true, accountMgr.getActiveAccountById(ownerId)); + DataCenter zone = validateAndGetZone(cmd.getZoneId()); + + Long diskOfferingId = cmd.getDiskOfferingId(); + Long size = cmd.getSize(); + Long minIops = cmd.getMinIops(); + Long maxIops = cmd.getMaxIops(); + DiskOfferingVO diskOffering = validateAndGetDiskOffering(diskOfferingId, size, minIops, maxIops, zone); + + FileShareProvider provider = getFileShareProvider(cmd.getFileShareProviderName()); + FileShareLifeCycle lifeCycle = provider.getFileShareLifeCycle(); + lifeCycle.checkPrerequisites(zone, cmd.getServiceOfferingId()); + + FileShare.FileSystemType fsType; + try { + fsType = FileShare.FileSystemType.valueOf(cmd.getFsFormat().toUpperCase()); + } catch (IllegalArgumentException ex) { + throw new InvalidParameterValueException("Invalid File system format specified. Supported formats are EXT4 and XFS"); + } + + FileShareVO fileShare = new FileShareVO(cmd.getName(), cmd.getDescription(),owner.getDomainId(), + ownerId, cmd.getZoneId(), cmd.getFileShareProviderName(), FileShare.Protocol.NFS, + fsType, cmd.getServiceOfferingId()); + fileShareDao.persist(fileShare); + + fileShare = fileShareDao.findById(fileShare.getId()); + Pair<Long, Long> result = null; + try { + result = lifeCycle.commitFileShare(fileShare, cmd.getNetworkId(), diskOfferingId, size, minIops, maxIops); + } catch (Exception ex) { + stateTransitTo(fileShare, Event.OperationFailed); + throw ex; + } + fileShare.setVolumeId(result.first()); + fileShare.setVmId(result.second()); + fileShareDao.update(fileShare.getId(), fileShare); + stateTransitTo(fileShare, Event.OperationSucceeded); + return fileShare; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FILESHARE_START, eventDescription = "Starting fileshare", async = true) + public FileShare startFileShare(Long fileShareId) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + FileShareVO fileShare = fileShareDao.findById(fileShareId); + + Account caller = CallContext.current().getCallingAccount(); + accountMgr.checkAccess(caller, null, false, fileShare); + Set<State> validStates = new HashSet<>(List.of(State.Stopped, State.Detached)); + if (!validStates.contains(fileShare.getState())) { + throw new InvalidParameterValueException("File share can be started only if it is in the " + validStates.toString() + " states"); + } + + FileShareProvider provider = getFileShareProvider(fileShare.getFsProviderName()); + FileShareLifeCycle lifeCycle = provider.getFileShareLifeCycle(); + + if (fileShare.getState().equals(State.Detached)) { + Pair<Boolean, Long> result = lifeCycle.reDeployFileShare(fileShare); + if (result.first() == true) { + fileShare.setVmId(result.second()); + fileShareDao.update(fileShare.getId(), fileShare); + stateTransitTo(fileShare, Event.OperationSucceeded); + } else { + stateTransitTo(fileShare, Event.OperationFailed); + return null; + } + } + + stateTransitTo(fileShare, Event.StartRequested); + try { + lifeCycle.startFileShare(fileShare); + } catch (Exception ex) { + stateTransitTo(fileShare, Event.OperationFailed); + throw ex; + } + stateTransitTo(fileShare, Event.OperationSucceeded); + fileShare = fileShareDao.findById(fileShareId); + return fileShare; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FILESHARE_STOP, eventDescription = "Stopping fileshare", async = true) + public FileShare stopFileShare(Long fileShareId, Boolean forced) { + FileShareVO fileShare = fileShareDao.findById(fileShareId); + Account caller = CallContext.current().getCallingAccount(); + accountMgr.checkAccess(caller, null, false, fileShare); + Set<State> validStates = new HashSet<>(List.of(State.Ready)); + if (!validStates.contains(fileShare.getState())) { + throw new InvalidParameterValueException("File share can be stopped only if it is in the " + State.Ready + " state"); + } + + stateTransitTo(fileShare, Event.StopRequested); + FileShareProvider provider = getFileShareProvider(fileShare.getFsProviderName()); + FileShareLifeCycle lifeCycle = provider.getFileShareLifeCycle(); + try { + lifeCycle.stopFileShare(fileShare, forced); + } catch (Exception e) { + stateTransitTo(fileShare, Event.OperationFailed); + throw e; + } + stateTransitTo(fileShare, Event.OperationSucceeded); + return fileShare; + } + + private FileShareVO reDeployFileShare(FileShareVO fileShare, Boolean startVm) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + FileShareProvider provider = getFileShareProvider(fileShare.getFsProviderName()); + FileShareLifeCycle lifeCycle = provider.getFileShareLifeCycle(); + DataCenter zone = validateAndGetZone(fileShare.getDataCenterId()); + lifeCycle.checkPrerequisites(zone, fileShare.getServiceOfferingId()); + + if (!fileShare.getState().equals(State.Stopped)) { + stopFileShare(fileShare.getId(), false); + } + + Pair<Boolean, Long> result = lifeCycle.reDeployFileShare(fileShare); + if (result.first() == true) { + fileShare.setVmId(result.second()); + fileShareDao.update(fileShare.getId(), fileShare); + if (startVm) { + startFileShare(fileShare.getId()); + } + return fileShare; + } else { + stateTransitTo(fileShare, Event.Detach); + logger.error("Redeploy failed for fileshare " + fileShare.toString() + ". File share is left in detached state."); + return null; + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FILESHARE_RESTART, eventDescription = "Restarting fileshare", async = true) + public FileShare restartFileShare(Long fileShareId, boolean cleanup) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + FileShareVO fileShare = fileShareDao.findById(fileShareId); + Account caller = CallContext.current().getCallingAccount(); + accountMgr.checkAccess(caller, null, false, fileShare); + + Set<State> validStates = new HashSet<>(List.of(State.Ready, State.Stopped)); + if (!validStates.contains(fileShare.getState())) { + throw new InvalidParameterValueException("Stop file share can be done only if the file share is in " + validStates.toString() + " states"); + } + + if (cleanup == false) { Review Comment: ```suggestion if (!cleanup) { ``` ########## server/src/main/java/org/apache/cloudstack/storage/fileshare/FileShareServiceImpl.java: ########## @@ -0,0 +1,687 @@ +// 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.fileshare; + +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareCleanupDelay; +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareCleanupInterval; +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareFeatureEnabled; + +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.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +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.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; +import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.db.EntityManager; +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.api.ResponseObject; +import org.apache.cloudstack.api.command.user.storage.fileshare.ChangeFileShareDiskOfferingCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ChangeFileShareServiceOfferingCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.CreateFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ExpungeFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ListFileShareProvidersCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ListFileSharesCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.DestroyFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.RecoverFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.RestartFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.StartFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.StopFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.UpdateFileShareCmd; +import org.apache.cloudstack.api.response.FileShareResponse; +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.fileshare.dao.FileShareDao; +import org.apache.cloudstack.storage.fileshare.FileShare.Event; +import org.apache.cloudstack.storage.fileshare.FileShare.State; +import org.apache.cloudstack.storage.fileshare.query.dao.FileShareJoinDao; +import org.apache.cloudstack.storage.fileshare.query.vo.FileShareJoinVO; + +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; + +public class FileShareServiceImpl extends ManagerBase implements FileShareService, Configurable { + + @Inject + private AccountManager accountMgr; + + @Inject + private EntityManager entityMgr; + + @Inject + private ConfigurationManager configMgr; + + @Inject + private VolumeApiService volumeApiService; + + @Inject + private FileShareDao fileShareDao; + + @Inject + private FileShareJoinDao fileShareJoinDao; + + @Inject + private DiskOfferingDao diskOfferingDao; + + @Inject + ConfigurationDao configDao; + + @Inject + VolumeDao volumeDao; + + protected List<FileShareProvider> fileShareProviders; + + private Map<String, FileShareProvider> fileShareProviderMap = new HashMap<>(); + + private final StateMachine2<State, Event, FileShare> fileShareStateMachine; + + ScheduledExecutorService _executor = null; + + public FileShareServiceImpl() { + this.fileShareStateMachine = State.getStateMachine(); + } + + @Override + public boolean start() { + fileShareProviderMap.clear(); + for (final FileShareProvider provider : fileShareProviders) { + fileShareProviderMap.put(provider.getName(), provider); + provider.configure(); + } + _executor.scheduleWithFixedDelay(new FileShareGarbageCollector(), FileShareCleanupInterval.value(), FileShareCleanupInterval.value(), TimeUnit.SECONDS); + return true; + } + + public boolean stop() { + _executor.shutdown(); + return true; + } + + @Override + public List<FileShareProvider> getFileShareProviders() { + return fileShareProviders; + } + + @Override + public boolean stateTransitTo(FileShare fileShare, Event event) { + try { + return fileShareStateMachine.transitTo(fileShare, event, null, fileShareDao); + } catch (NoTransitionException e) { + logger.debug(String.format("Failed during event % for File Share %s [%s] due to exception %", + event.toString(), fileShare.getName(), fileShare.getId(), e)); + return false; + } + } + + @Override + public void setFileShareProviders(List<FileShareProvider> fileShareProviders) { + this.fileShareProviders = fileShareProviders; + } + + @Override + public FileShareProvider getFileShareProvider(String fileShareProviderName) { + if (fileShareProviderMap.containsKey(fileShareProviderName)) { + return fileShareProviderMap.get(fileShareProviderName); + } + throw new CloudRuntimeException("Invalid file share provider name!"); + } + + @Override + public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException { + Map<String, String> configs = configDao.getConfiguration("management-server", params); + String workers = configs.get("expunge.workers"); + int wrks = NumbersUtil.parseInt(workers, 10); + _executor = Executors.newScheduledThreadPool(wrks, new NamedThreadFactory("FileShare-Scavenger")); + return true; + } + + @Override + public List<Class<?>> getCommands() { + final List<Class<?>> cmdList = new ArrayList<>(); + if (FileShareFeatureEnabled.value() == true) { Review Comment: ```suggestion if (FileShareFeatureEnabled.value()) { ``` ########## server/src/main/java/org/apache/cloudstack/storage/fileshare/FileShareServiceImpl.java: ########## @@ -0,0 +1,687 @@ +// 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.fileshare; + +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareCleanupDelay; +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareCleanupInterval; +import static org.apache.cloudstack.storage.fileshare.FileShare.FileShareFeatureEnabled; + +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.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +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.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; +import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.db.EntityManager; +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.api.ResponseObject; +import org.apache.cloudstack.api.command.user.storage.fileshare.ChangeFileShareDiskOfferingCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ChangeFileShareServiceOfferingCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.CreateFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ExpungeFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ListFileShareProvidersCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.ListFileSharesCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.DestroyFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.RecoverFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.RestartFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.StartFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.StopFileShareCmd; +import org.apache.cloudstack.api.command.user.storage.fileshare.UpdateFileShareCmd; +import org.apache.cloudstack.api.response.FileShareResponse; +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.fileshare.dao.FileShareDao; +import org.apache.cloudstack.storage.fileshare.FileShare.Event; +import org.apache.cloudstack.storage.fileshare.FileShare.State; +import org.apache.cloudstack.storage.fileshare.query.dao.FileShareJoinDao; +import org.apache.cloudstack.storage.fileshare.query.vo.FileShareJoinVO; + +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; + +public class FileShareServiceImpl extends ManagerBase implements FileShareService, Configurable { + + @Inject + private AccountManager accountMgr; + + @Inject + private EntityManager entityMgr; + + @Inject + private ConfigurationManager configMgr; + + @Inject + private VolumeApiService volumeApiService; + + @Inject + private FileShareDao fileShareDao; + + @Inject + private FileShareJoinDao fileShareJoinDao; + + @Inject + private DiskOfferingDao diskOfferingDao; + + @Inject + ConfigurationDao configDao; + + @Inject + VolumeDao volumeDao; + + protected List<FileShareProvider> fileShareProviders; + + private Map<String, FileShareProvider> fileShareProviderMap = new HashMap<>(); + + private final StateMachine2<State, Event, FileShare> fileShareStateMachine; + + ScheduledExecutorService _executor = null; + + public FileShareServiceImpl() { + this.fileShareStateMachine = State.getStateMachine(); + } + + @Override + public boolean start() { + fileShareProviderMap.clear(); + for (final FileShareProvider provider : fileShareProviders) { + fileShareProviderMap.put(provider.getName(), provider); + provider.configure(); + } + _executor.scheduleWithFixedDelay(new FileShareGarbageCollector(), FileShareCleanupInterval.value(), FileShareCleanupInterval.value(), TimeUnit.SECONDS); + return true; + } + + public boolean stop() { + _executor.shutdown(); + return true; + } + + @Override + public List<FileShareProvider> getFileShareProviders() { + return fileShareProviders; + } + + @Override + public boolean stateTransitTo(FileShare fileShare, Event event) { + try { + return fileShareStateMachine.transitTo(fileShare, event, null, fileShareDao); + } catch (NoTransitionException e) { + logger.debug(String.format("Failed during event % for File Share %s [%s] due to exception %", + event.toString(), fileShare.getName(), fileShare.getId(), e)); + return false; + } + } + + @Override + public void setFileShareProviders(List<FileShareProvider> fileShareProviders) { + this.fileShareProviders = fileShareProviders; + } + + @Override + public FileShareProvider getFileShareProvider(String fileShareProviderName) { + if (fileShareProviderMap.containsKey(fileShareProviderName)) { + return fileShareProviderMap.get(fileShareProviderName); + } + throw new CloudRuntimeException("Invalid file share provider name!"); + } + + @Override + public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException { + Map<String, String> configs = configDao.getConfiguration("management-server", params); + String workers = configs.get("expunge.workers"); + int wrks = NumbersUtil.parseInt(workers, 10); + _executor = Executors.newScheduledThreadPool(wrks, new NamedThreadFactory("FileShare-Scavenger")); + return true; + } + + @Override + public List<Class<?>> getCommands() { + final List<Class<?>> cmdList = new ArrayList<>(); + if (FileShareFeatureEnabled.value() == true) { + cmdList.add(ListFileShareProvidersCmd.class); + cmdList.add(CreateFileShareCmd.class); + cmdList.add(ListFileSharesCmd.class); + cmdList.add(UpdateFileShareCmd.class); + cmdList.add(DestroyFileShareCmd.class); + cmdList.add(RestartFileShareCmd.class); + cmdList.add(StartFileShareCmd.class); + cmdList.add(StopFileShareCmd.class); + cmdList.add(ChangeFileShareDiskOfferingCmd.class); + cmdList.add(ChangeFileShareServiceOfferingCmd.class); + cmdList.add(RecoverFileShareCmd.class); + cmdList.add(ExpungeFileShareCmd.class); + } + return cmdList; + } + + private DataCenter validateAndGetZone(Long zoneId) { + DataCenter zone = entityMgr.findById(DataCenter.class, 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())); + } + return zone; + } + + private DiskOfferingVO validateAndGetDiskOffering(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"); + } + return diskOffering; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FILESHARE_CREATE, eventDescription = "creating fileshare", create = true) + public FileShare createFileShare(CreateFileShareCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + Account caller = CallContext.current().getCallingAccount(); + + long ownerId = cmd.getEntityOwnerId(); + Account owner = accountMgr.getActiveAccountById(ownerId); + accountMgr.checkAccess(caller, null, true, accountMgr.getActiveAccountById(ownerId)); + DataCenter zone = validateAndGetZone(cmd.getZoneId()); + + Long diskOfferingId = cmd.getDiskOfferingId(); + Long size = cmd.getSize(); + Long minIops = cmd.getMinIops(); + Long maxIops = cmd.getMaxIops(); + DiskOfferingVO diskOffering = validateAndGetDiskOffering(diskOfferingId, size, minIops, maxIops, zone); + + FileShareProvider provider = getFileShareProvider(cmd.getFileShareProviderName()); + FileShareLifeCycle lifeCycle = provider.getFileShareLifeCycle(); + lifeCycle.checkPrerequisites(zone, cmd.getServiceOfferingId()); + + FileShare.FileSystemType fsType; + try { + fsType = FileShare.FileSystemType.valueOf(cmd.getFsFormat().toUpperCase()); + } catch (IllegalArgumentException ex) { + throw new InvalidParameterValueException("Invalid File system format specified. Supported formats are EXT4 and XFS"); + } + + FileShareVO fileShare = new FileShareVO(cmd.getName(), cmd.getDescription(),owner.getDomainId(), + ownerId, cmd.getZoneId(), cmd.getFileShareProviderName(), FileShare.Protocol.NFS, + fsType, cmd.getServiceOfferingId()); + fileShareDao.persist(fileShare); + + fileShare = fileShareDao.findById(fileShare.getId()); + Pair<Long, Long> result = null; + try { + result = lifeCycle.commitFileShare(fileShare, cmd.getNetworkId(), diskOfferingId, size, minIops, maxIops); + } catch (Exception ex) { + stateTransitTo(fileShare, Event.OperationFailed); + throw ex; + } + fileShare.setVolumeId(result.first()); + fileShare.setVmId(result.second()); + fileShareDao.update(fileShare.getId(), fileShare); + stateTransitTo(fileShare, Event.OperationSucceeded); + return fileShare; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FILESHARE_START, eventDescription = "Starting fileshare", async = true) + public FileShare startFileShare(Long fileShareId) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + FileShareVO fileShare = fileShareDao.findById(fileShareId); + + Account caller = CallContext.current().getCallingAccount(); + accountMgr.checkAccess(caller, null, false, fileShare); + Set<State> validStates = new HashSet<>(List.of(State.Stopped, State.Detached)); + if (!validStates.contains(fileShare.getState())) { + throw new InvalidParameterValueException("File share can be started only if it is in the " + validStates.toString() + " states"); + } + + FileShareProvider provider = getFileShareProvider(fileShare.getFsProviderName()); + FileShareLifeCycle lifeCycle = provider.getFileShareLifeCycle(); + + if (fileShare.getState().equals(State.Detached)) { + Pair<Boolean, Long> result = lifeCycle.reDeployFileShare(fileShare); + if (result.first() == true) { + fileShare.setVmId(result.second()); + fileShareDao.update(fileShare.getId(), fileShare); + stateTransitTo(fileShare, Event.OperationSucceeded); + } else { + stateTransitTo(fileShare, Event.OperationFailed); + return null; + } + } + + stateTransitTo(fileShare, Event.StartRequested); + try { + lifeCycle.startFileShare(fileShare); + } catch (Exception ex) { + stateTransitTo(fileShare, Event.OperationFailed); + throw ex; + } + stateTransitTo(fileShare, Event.OperationSucceeded); + fileShare = fileShareDao.findById(fileShareId); + return fileShare; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FILESHARE_STOP, eventDescription = "Stopping fileshare", async = true) + public FileShare stopFileShare(Long fileShareId, Boolean forced) { + FileShareVO fileShare = fileShareDao.findById(fileShareId); + Account caller = CallContext.current().getCallingAccount(); + accountMgr.checkAccess(caller, null, false, fileShare); + Set<State> validStates = new HashSet<>(List.of(State.Ready)); + if (!validStates.contains(fileShare.getState())) { + throw new InvalidParameterValueException("File share can be stopped only if it is in the " + State.Ready + " state"); + } + + stateTransitTo(fileShare, Event.StopRequested); + FileShareProvider provider = getFileShareProvider(fileShare.getFsProviderName()); + FileShareLifeCycle lifeCycle = provider.getFileShareLifeCycle(); + try { + lifeCycle.stopFileShare(fileShare, forced); + } catch (Exception e) { + stateTransitTo(fileShare, Event.OperationFailed); + throw e; + } + stateTransitTo(fileShare, Event.OperationSucceeded); + return fileShare; + } + + private FileShareVO reDeployFileShare(FileShareVO fileShare, Boolean startVm) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + FileShareProvider provider = getFileShareProvider(fileShare.getFsProviderName()); + FileShareLifeCycle lifeCycle = provider.getFileShareLifeCycle(); + DataCenter zone = validateAndGetZone(fileShare.getDataCenterId()); + lifeCycle.checkPrerequisites(zone, fileShare.getServiceOfferingId()); + + if (!fileShare.getState().equals(State.Stopped)) { + stopFileShare(fileShare.getId(), false); + } + + Pair<Boolean, Long> result = lifeCycle.reDeployFileShare(fileShare); + if (result.first() == true) { + fileShare.setVmId(result.second()); + fileShareDao.update(fileShare.getId(), fileShare); + if (startVm) { + startFileShare(fileShare.getId()); + } + return fileShare; + } else { + stateTransitTo(fileShare, Event.Detach); + logger.error("Redeploy failed for fileshare " + fileShare.toString() + ". File share is left in detached state."); + return null; + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FILESHARE_RESTART, eventDescription = "Restarting fileshare", async = true) + public FileShare restartFileShare(Long fileShareId, boolean cleanup) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + FileShareVO fileShare = fileShareDao.findById(fileShareId); + Account caller = CallContext.current().getCallingAccount(); + accountMgr.checkAccess(caller, null, false, fileShare); + + Set<State> validStates = new HashSet<>(List.of(State.Ready, State.Stopped)); + if (!validStates.contains(fileShare.getState())) { + throw new InvalidParameterValueException("Stop file share can be done only if the file share is in " + validStates.toString() + " states"); + } + + if (cleanup == false) { + if (!fileShare.getState().equals(State.Stopped)) { + stopFileShare(fileShare.getId(), false); + } + return startFileShare(fileShare.getId()); + } else { + return reDeployFileShare(fileShare, true); + } + } + + private Pair<List<Long>, Integer> searchForFileSharesIdsAndCount(ListFileSharesCmd 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(FileShareVO.class, "created", false, startIndex, pageSize); + + SearchBuilder<FileShareVO> fileShareSearchBuilder = fileShareDao.createSearchBuilder(); + fileShareSearchBuilder.select(null, SearchCriteria.Func.DISTINCT, fileShareSearchBuilder.entity().getId()); // select distinct + accountMgr.buildACLSearchBuilder(fileShareSearchBuilder, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); + + fileShareSearchBuilder.and("id", fileShareSearchBuilder.entity().getId(), SearchCriteria.Op.EQ); + fileShareSearchBuilder.and("name", fileShareSearchBuilder.entity().getName(), SearchCriteria.Op.EQ); + fileShareSearchBuilder.and("dataCenterId", fileShareSearchBuilder.entity().getDataCenterId(), SearchCriteria.Op.EQ); + + if (keyword != null) { + fileShareSearchBuilder.and().op("keywordName", fileShareSearchBuilder.entity().getName(), SearchCriteria.Op.LIKE); Review Comment: should this be `fileShareSearchBuilder.and().op` or `fileShareSearchBuilder.and(` ? -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
