diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index ea70b619d09..d4235cbc9bc 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -451,6 +451,7 @@ public class EventTypes { public static final String EVENT_ENABLE_PRIMARY_STORAGE = "ENABLE.PS"; public static final String EVENT_DISABLE_PRIMARY_STORAGE = "DISABLE.PS"; public static final String EVENT_SYNC_STORAGE_POOL = "SYNC.STORAGE.POOL"; + public static final String EVENT_CHANGE_STORAGE_POOL_SCOPE = "CHANGE.STORAGE.POOL.SCOPE"; // VPN public static final String EVENT_REMOTE_ACCESS_VPN_CREATE = "VPN.REMOTE.ACCESS.CREATE"; @@ -1002,6 +1003,7 @@ public class EventTypes { // Primary storage pool entityEventDetails.put(EVENT_ENABLE_PRIMARY_STORAGE, StoragePool.class); entityEventDetails.put(EVENT_DISABLE_PRIMARY_STORAGE, StoragePool.class); + entityEventDetails.put(EVENT_CHANGE_STORAGE_POOL_SCOPE, StoragePool.class); // VPN entityEventDetails.put(EVENT_REMOTE_ACCESS_VPN_CREATE, RemoteAccessVpn.class); diff --git a/api/src/main/java/com/cloud/storage/StorageService.java b/api/src/main/java/com/cloud/storage/StorageService.java index 77800d8955c..1ce335b0115 100644 --- a/api/src/main/java/com/cloud/storage/StorageService.java +++ b/api/src/main/java/com/cloud/storage/StorageService.java @@ -21,6 +21,7 @@ import java.net.UnknownHostException; import java.util.Map; import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaintenanceCmd; +import org.apache.cloudstack.api.command.admin.storage.ChangeStoragePoolScopeCmd; import org.apache.cloudstack.api.command.admin.storage.CreateSecondaryStagingStoreCmd; import org.apache.cloudstack.api.command.admin.storage.CreateStoragePoolCmd; import org.apache.cloudstack.api.command.admin.storage.DeleteImageStoreCmd; @@ -35,6 +36,7 @@ import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd; import com.cloud.exception.DiscoveryException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceInUseException; import com.cloud.exception.ResourceUnavailableException; import org.apache.cloudstack.api.command.admin.storage.heuristics.CreateSecondaryStorageSelectorCmd; @@ -130,4 +132,6 @@ public interface StorageService { boolean deleteObjectStore(DeleteObjectStoragePoolCmd cmd); ObjectStore updateObjectStore(Long id, UpdateObjectStoragePoolCmd cmd); + + void changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws IllegalArgumentException, InvalidParameterValueException, PermissionDeniedException; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index a2bcd103b76..6db1ed06eff 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -450,6 +450,7 @@ public class ApiConstants { public static final String STORAGE_POLICY = "storagepolicy"; public static final String STORAGE_MOTION_ENABLED = "storagemotionenabled"; public static final String STORAGE_CAPABILITIES = "storagecapabilities"; + public static final String STORAGE_CUSTOM_STATS = "storagecustomstats"; public static final String SUBNET = "subnet"; public static final String OWNER = "owner"; public static final String SWAP_OWNER = "swapowner"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java new file mode 100644 index 00000000000..d3b6a074610 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.storage; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.storage.StoragePool; + +@APICommand(name = "changeStoragePoolScope", description = "Changes the scope of a storage pool when the pool is in Disabled state." + + "This feature is officially tested and supported for Hypervisors: KVM and VMware, Protocols: NFS and Ceph, and Storage Provider: DefaultPrimary. " + + "There might be extra steps involved to make this work for other hypervisors and storage options.", + responseObject = SuccessResponse.class, since= "4.19.1", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ChangeStoragePoolScopeCmd extends BaseAsyncCmd { + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, required = true, description = "the Id of the storage pool") + private Long id; + + @Parameter(name = ApiConstants.SCOPE, type = CommandType.STRING, required = true, description = "the scope of the storage: cluster or zone") + private String scope; + + @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "the Id of the cluster to use if scope is being set to Cluster") + private Long clusterId; + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.StoragePool; + } + + @Override + public Long getApiResourceId() { + return getId(); + } + + public String getEventType() { + return EventTypes.EVENT_CHANGE_STORAGE_POOL_SCOPE; + } + + @Override + public String getEventDescription() { + String description = "Change storage pool scope. Storage pool Id: "; + StoragePool pool = _entityMgr.findById(StoragePool.class, getId()); + if (pool != null) { + description += pool.getUuid(); + } else { + description += getId(); + } + description += " to " + getScope(); + return description; + } + + @Override + public void execute() { + _storageService.changeStoragePoolScope(this); + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } + + public Long getId() { + return id; + } + + public String getScope() { + return scope; + } + + public Long getClusterId() { + return clusterId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java index 293ed3103cb..57a87939b6b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java @@ -72,7 +72,8 @@ public class ListStoragePoolsCmd extends BaseListCmd { @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "host ID of the storage pools") private Long hostId; - + @Parameter(name = ApiConstants.STORAGE_CUSTOM_STATS, type = CommandType.BOOLEAN, description = "If true, lists the custom stats of the storage pool", since = "4.18.1") + private Boolean customStats; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -129,6 +130,10 @@ public class ListStoragePoolsCmd extends BaseListCmd { this.scope = scope; } + public Boolean getCustomStats() { + return customStats != null && customStats; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListAffectedVmsForStorageScopeChangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListAffectedVmsForStorageScopeChangeCmd.java new file mode 100644 index 00000000000..d586a81b685 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListAffectedVmsForStorageScopeChangeCmd.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.api.command.admin.vm; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; +import org.apache.cloudstack.api.response.VirtualMachineResponse; + +import com.cloud.vm.VirtualMachine; + +@APICommand(name = "listAffectedVmsForStorageScopeChange", + description = "List user and system VMs that need to be stopped and destroyed respectively for changing the scope of the storage pool from Zone to Cluster.", + responseObject = VirtualMachineResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.1", + authorized = {RoleType.Admin}) +public class ListAffectedVmsForStorageScopeChangeCmd extends BaseListCmd { + + @Parameter(name = ApiConstants.CLUSTER_ID, + type = CommandType.UUID, + entityType = ClusterResponse.class, + required = true, + description = "the Id of the cluster the scope of the storage pool is being changed to") + private Long clusterIdForScopeChange; + + @Parameter(name = ApiConstants.STORAGE_ID, + type = CommandType.UUID, + entityType = StoragePoolResponse.class, + required = true, + description = "the Id of the storage pool on which change scope operation is being done") + private Long storageId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getClusterIdForScopeChange() { + return clusterIdForScopeChange; + } + + public Long getStorageId() { + return storageId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ListResponse response = _queryService.listAffectedVmsForStorageScopeChange(this); + response.setResponseName(getCommandName()); + response.setObjectName(VirtualMachine.class.getSimpleName().toLowerCase()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java index f514c8167ac..9e7f5159e0e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java @@ -97,6 +97,10 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations { @Param(description = "total min IOPS currently in use by volumes") private Long allocatedIops; + @SerializedName(ApiConstants.STORAGE_CUSTOM_STATS) + @Param(description = "the storage pool custom stats", since = "4.18.1") + private Map customStats; + @SerializedName("tags") @Param(description = "the tags for the storage pool") private String tags; @@ -304,6 +308,14 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations { this.allocatedIops = allocatedIops; } + public Map getCustomStats() { + return customStats; + } + + public void setCustomStats(Map customStats) { + this.customStats = customStats; + } + public String getTags() { return tags; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VirtualMachineResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VirtualMachineResponse.java new file mode 100644 index 00000000000..7d676292b8a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/VirtualMachineResponse.java @@ -0,0 +1,124 @@ +// 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.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import com.cloud.serializer.Param; +import com.cloud.vm.VirtualMachine; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = VirtualMachine.class) +public class VirtualMachineResponse extends BaseResponse { + @SerializedName("id") + @Param(description = "the ID of the VM") + private String id; + + @SerializedName("type") + @Param(description = "the type of VM") + private String type; + + @SerializedName("name") + @Param(description = "the name of the VM") + private String name; + + @SerializedName("clusterid") + @Param(description = "the cluster ID for the VM") + private String clusterId; + + @SerializedName("clustername") + @Param(description = "the cluster name for the VM") + private String clusterName; + + @SerializedName("hostid") + @Param(description = "the host ID for the VM") + private String hostId; + + @SerializedName("hostname") + @Param(description = "the hostname for the VM") + private String hostName; + + @Override + public String getObjectId() { + return this.getId(); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getVmType() { + return type; + } + + public void setVmType(String type) { + this.type = type; + } + + public String getVmName() { + return name; + } + + public void setVmName(String name) { + this.name = name; + } + + public String getClusterId() { + return clusterId; + } + + public void setClusterId(String clusterId) { + this.clusterId = clusterId; + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHostId() { + return hostId; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java index 4c53314aef5..c93e43d9f37 100644 --- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java +++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java @@ -52,6 +52,7 @@ import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; import org.apache.cloudstack.api.command.user.tag.ListTagsCmd; import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd; +import org.apache.cloudstack.api.command.admin.vm.ListAffectedVmsForStorageScopeChangeCmd; import org.apache.cloudstack.api.command.user.vm.ListVMsCmd; import org.apache.cloudstack.api.command.user.vmgroup.ListVMGroupsCmd; import org.apache.cloudstack.api.command.user.volume.ListResourceDetailsCmd; @@ -89,6 +90,7 @@ import org.apache.cloudstack.api.response.StorageTagResponse; import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VirtualMachineResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.framework.config.ConfigKey; @@ -140,6 +142,8 @@ public interface QueryService { ListResponse searchForUserVMs(ListVMsCmd cmd); + ListResponse listAffectedVmsForStorageScopeChange(ListAffectedVmsForStorageScopeChangeCmd cmd); + ListResponse searchForSecurityGroups(ListSecurityGroupsCmd cmd); ListResponse searchForRouters(ListRoutersCmd cmd); diff --git a/core/src/main/java/com/cloud/agent/api/PrepareStorageClientAnswer.java b/core/src/main/java/com/cloud/agent/api/PrepareStorageClientAnswer.java new file mode 100644 index 00000000000..85afb925646 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PrepareStorageClientAnswer.java @@ -0,0 +1,43 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +import java.util.Map; + +public class PrepareStorageClientAnswer extends Answer { + Map detailsMap; + + public PrepareStorageClientAnswer() { + super(); + } + + public PrepareStorageClientAnswer(Command command, boolean success, Map detailsMap) { + super(command, success, ""); + this.detailsMap = detailsMap; + } + + public PrepareStorageClientAnswer(Command command, boolean success, String details) { + super(command, success, details); + } + + public Map getDetailsMap() { + return detailsMap; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PrepareStorageClientCommand.java b/core/src/main/java/com/cloud/agent/api/PrepareStorageClientCommand.java new file mode 100644 index 00000000000..8dea9c11c53 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PrepareStorageClientCommand.java @@ -0,0 +1,56 @@ +// +// 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 com.cloud.agent.api; + +import java.util.Map; + +import com.cloud.storage.Storage.StoragePoolType; + +public class PrepareStorageClientCommand extends Command { + private StoragePoolType poolType; + private String poolUuid; + private Map details; + + public PrepareStorageClientCommand() { + } + + public PrepareStorageClientCommand(StoragePoolType poolType, String poolUuid, Map details) { + this.poolType = poolType; + this.poolUuid = poolUuid; + this.details = details; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public StoragePoolType getPoolType() { + return poolType; + } + + public String getPoolUuid() { + return poolUuid; + } + + public Map getDetails() { + return details; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientAnswer.java b/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientAnswer.java new file mode 100644 index 00000000000..1280293db0d --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientAnswer.java @@ -0,0 +1,34 @@ +// +// 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 com.cloud.agent.api; + +public class UnprepareStorageClientAnswer extends Answer { + public UnprepareStorageClientAnswer() { + super(); + } + + public UnprepareStorageClientAnswer(Command command, boolean success) { + super(command, success, ""); + } + + public UnprepareStorageClientAnswer(Command command, boolean success, String details) { + super(command, success, details); + } +} diff --git a/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientCommand.java b/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientCommand.java new file mode 100644 index 00000000000..bebd30ca519 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientCommand.java @@ -0,0 +1,48 @@ +// +// 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 com.cloud.agent.api; + +import com.cloud.storage.Storage.StoragePoolType; + +public class UnprepareStorageClientCommand extends Command { + private StoragePoolType poolType; + private String poolUuid; + + public UnprepareStorageClientCommand() { + } + + public UnprepareStorageClientCommand(StoragePoolType poolType, String poolUuid) { + this.poolType = poolType; + this.poolUuid = poolUuid; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public StoragePoolType getPoolType() { + return poolType; + } + + public String getPoolUuid() { + return poolUuid; + } +} diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java index dbe67e6cca5..d52c656f6db 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java @@ -18,6 +18,8 @@ */ package org.apache.cloudstack.engine.subsystem.api.storage; +import java.util.Map; + import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.storage.command.CommandResult; @@ -86,6 +88,22 @@ public interface PrimaryDataStoreDriver extends DataStoreDriver { */ boolean canProvideStorageStats(); + /** + * intended for managed storage + * returns true if the storage can provide its custom stats + */ + default boolean poolProvidesCustomStorageStats() { + return false; + } + + /** + * intended for managed storage + * returns the custom stats if the storage can provide them + */ + default Map getCustomStorageStats(StoragePool pool) { + return null; + } + /** * intended for managed storage * returns the total capacity and used size in bytes @@ -110,6 +128,14 @@ public interface PrimaryDataStoreDriver extends DataStoreDriver { */ boolean canHostAccessStoragePool(Host host, StoragePool pool); + /** + * intended for managed storage + * returns true if the host can prepare storage client to provide access the storage pool + */ + default boolean canHostPrepareStoragePoolAccess(Host host, StoragePool pool) { + return false; + } + /** * Used by storage pools which want to keep VMs' information * @return true if additional VM info is needed (intended for storage pools). diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java index fcbc19c28b7..54f3c63f8d7 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.engine.subsystem.api.storage; import java.util.Map; +import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.StoragePool; public interface PrimaryDataStoreLifeCycle extends DataStoreLifeCycle { @@ -29,4 +30,6 @@ public interface PrimaryDataStoreLifeCycle extends DataStoreLifeCycle { void updateStoragePool(StoragePool storagePool, Map details); void enableStoragePool(DataStore store); void disableStoragePool(DataStore store); + void changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType); + void changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType); } diff --git a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java index 91197de6a84..b2ae8b89837 100755 --- a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java +++ b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java @@ -134,6 +134,10 @@ public interface ResourceManager extends ResourceService, Configurable { public List listAllHostsInAllZonesByType(Type type); + public List listAllHostsInOneZoneNotInClusterByHypervisor(final HypervisorType type, long dcId, long clusterId); + + public List listAllHostsInOneZoneNotInClusterByHypervisors(List types, long dcId, long clusterId); + public List listAvailHypervisorInZone(Long hostId, Long zoneId); public HostVO findHostByGuid(String guid); diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index 779c08e1505..c3909bc56b0 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -118,7 +118,7 @@ public interface StorageManager extends StorageService { "storage.pool.disk.wait", "Storage", "60", - "Timeout (in secs) for the storage pool disk (of managed pool) to become available in the host. Currently only supported for PowerFlex.", + "Timeout (in secs) for the storage pool disk (of managed pool) to become available in the host. Currently supported for PowerFlex only.", true, ConfigKey.Scope.StoragePool, null); @@ -127,7 +127,7 @@ public interface StorageManager extends StorageService { "storage.pool.client.timeout", "Storage", "60", - "Timeout (in secs) for the storage pool client connection timeout (for managed pools). Currently only supported for PowerFlex.", + "Timeout (in secs) for the API client connection timeout of storage pool (for managed pools). Currently supported for PowerFlex only.", false, ConfigKey.Scope.StoragePool, null); @@ -136,11 +136,20 @@ public interface StorageManager extends StorageService { "storage.pool.client.max.connections", "Storage", "100", - "Maximum connections for the storage pool client (for managed pools). Currently only supported for PowerFlex.", + "Maximum connections for the API client of storage pool (for managed pools). Currently supported for PowerFlex only.", false, ConfigKey.Scope.StoragePool, null); + ConfigKey STORAGE_POOL_CONNECTED_CLIENTS_LIMIT = new ConfigKey<>(Integer.class, + "storage.pool.connected.clients.limit", + "Storage", + "-1", + "Maximum connected storage pool clients supported for the storage (for managed pools), <= 0 for unlimited (default: -1). Currently supported for PowerFlex only.", + true, + ConfigKey.Scope.StoragePool, + null); + ConfigKey STORAGE_POOL_IO_POLICY = new ConfigKey<>(String.class, "kvm.storage.pool.io.policy", "Storage", @@ -252,6 +261,10 @@ public interface StorageManager extends StorageService { boolean canPoolProvideStorageStats(StoragePool pool); + boolean poolProvidesCustomStorageStats(StoragePool pool); + + Map getCustomStorageStats(StoragePool pool); + /** * Checks if a host has running VMs that are using its local storage pool. * @return true if local storage is active on the host @@ -288,6 +301,8 @@ public interface StorageManager extends StorageService { boolean canHostAccessStoragePool(Host host, StoragePool pool); + boolean canHostPrepareStoragePoolAccess(Host host, StoragePool pool); + Host getHost(long hostId); Host updateSecondaryStorage(long secStorageId, String newUrl); diff --git a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java index 132fd3fe5a2..cd62935f17e 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java +++ b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java @@ -135,8 +135,8 @@ public class CapacityVO implements Capacity { return podId; } - public void setPodId(long podId) { - this.podId = new Long(podId); + public void setPodId(Long podId) { + this.podId = podId; } @Override @@ -144,8 +144,8 @@ public class CapacityVO implements Capacity { return clusterId; } - public void setClusterId(long clusterId) { - this.clusterId = new Long(clusterId); + public void setClusterId(Long clusterId) { + this.clusterId = clusterId; } @Override diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java index b099a6d6bdb..62ef5b7570d 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java @@ -41,4 +41,6 @@ public interface StoragePoolHostDao extends GenericDao public void deleteStoragePoolHostDetails(long hostId, long poolId); List listByHostId(long hostId); + + Pair, Integer> listByPoolIdNotInCluster(long clusterId, long poolId); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java index 9e7bdca1181..987a42f410e 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java @@ -23,12 +23,18 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import javax.inject.Inject; + import org.springframework.stereotype.Component; +import com.cloud.host.HostVO; import com.cloud.host.Status; +import com.cloud.host.dao.HostDao; import com.cloud.storage.StoragePoolHostVO; import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; @@ -40,6 +46,11 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase HostSearch; protected final SearchBuilder PoolHostSearch; + protected SearchBuilder poolNotInClusterSearch; + + @Inject + HostDao hostDao; + protected static final String HOST_FOR_POOL_SEARCH = "SELECT * FROM storage_pool_host_ref ph, host h where ph.host_id = h.id and ph.pool_id=? and h.status=? "; protected static final String HOSTS_FOR_POOLS_SEARCH = "SELECT DISTINCT(ph.host_id) FROM storage_pool_host_ref ph, host h WHERE ph.host_id = h.id AND h.status = 'Up' AND resource_state = 'Enabled' AND ph.pool_id IN (?)"; @@ -68,6 +79,15 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase hostSearch = hostDao.createSearchBuilder(); + poolNotInClusterSearch.join("hostSearch", hostSearch, hostSearch.entity().getId(), poolNotInClusterSearch.entity().getHostId(), JoinBuilder.JoinType.INNER); + hostSearch.and("clusterId", hostSearch.entity().getClusterId(), SearchCriteria.Op.NEQ); + } + @Override public List listByPoolId(long id) { SearchCriteria sc = PoolSearch.create(); @@ -194,4 +214,12 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase, Integer> listByPoolIdNotInCluster(long clusterId, long poolId) { + SearchCriteria sc = poolNotInClusterSearch.create(); + sc.setParameters("poolId", poolId); + sc.setJoinParameters("hostSearch", "clusterId", clusterId); + return searchAndCount(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index 16004ac13b9..0c4d707635a 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -77,10 +77,11 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol protected GenericSearchBuilder primaryStorageSearch2; protected GenericSearchBuilder secondaryStorageSearch; private final SearchBuilder poolAndPathSearch; + @Inject ReservationDao reservationDao; @Inject - ResourceTagDao _tagsDao; + ResourceTagDao tagsDao; // need to account for zone-wide primary storage where storage_pool has // null-value pod and cluster, where hypervisor information is stored in @@ -503,7 +504,6 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol poolAndPathSearch.and("poolId", poolAndPathSearch.entity().getPoolId(), Op.EQ); poolAndPathSearch.and("path", poolAndPathSearch.entity().getPath(), Op.EQ); poolAndPathSearch.done(); - } @Override @@ -741,7 +741,7 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol logger.debug(String.format("Removing volume %s from DB", id)); VolumeVO entry = findById(id); if (entry != null) { - _tagsDao.removeByIdAndType(id, ResourceObjectType.Volume); + tagsDao.removeByIdAndType(id, ResourceObjectType.Volume); } boolean result = super.remove(id); @@ -764,7 +764,7 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol destVol.setInstanceId(instanceId); update(srcVolId, srcVol); update(destVolId, destVol); - _tagsDao.updateResourceId(srcVolId, destVolId, ResourceObjectType.Volume); + tagsDao.updateResourceId(srcVolId, destVolId, ResourceObjectType.Volume); } catch (Exception e) { throw new CloudRuntimeException("Unable to persist the sequence number for this host"); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java index ac25eac3480..52bc5aac7e2 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java @@ -168,4 +168,6 @@ public interface VMInstanceDao extends GenericDao, StateDao< List searchRemovedByRemoveDate(final Date startDate, final Date endDate, final Long batchSize, List skippedVmIds); + + Pair, Integer> listByVmsNotInClusterUsingPool(long clusterId, long poolId); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index 78eb08afadc..8ab3bad69d5 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -35,6 +36,8 @@ import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; @@ -97,11 +100,16 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem protected SearchBuilder NotMigratingSearch; protected SearchBuilder BackupSearch; protected SearchBuilder LastHostAndStatesSearch; + protected SearchBuilder VmsNotInClusterUsingPool; @Inject - ResourceTagDao _tagsDao; + ResourceTagDao tagsDao; @Inject - NicDao _nicDao; + NicDao nicDao; + @Inject + VolumeDao volumeDao; + @Inject + HostDao hostDao; protected Attribute _updateTimeAttr; @@ -278,7 +286,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem _updateTimeAttr = _allAttributes.get("updateTime"); assert _updateTimeAttr != null : "Couldn't get this updateTime attribute"; - SearchBuilder nicSearch = _nicDao.createSearchBuilder(); + SearchBuilder nicSearch = nicDao.createSearchBuilder(); nicSearch.and("networkId", nicSearch.entity().getNetworkId(), SearchCriteria.Op.EQ); nicSearch.and("removedNic", nicSearch.entity().getRemoved(), SearchCriteria.Op.NULL); @@ -307,6 +315,16 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem LastHostAndStatesSearch.and("states", LastHostAndStatesSearch.entity().getState(), Op.IN); LastHostAndStatesSearch.done(); + VmsNotInClusterUsingPool = createSearchBuilder(); + SearchBuilder volumeSearch = volumeDao.createSearchBuilder(); + volumeSearch.and("poolId", volumeSearch.entity().getPoolId(), Op.EQ); + volumeSearch.and("removed", volumeSearch.entity().getRemoved(), Op.NULL); + VmsNotInClusterUsingPool.join("volumeSearch", volumeSearch, volumeSearch.entity().getInstanceId(), VmsNotInClusterUsingPool.entity().getId(), JoinType.INNER); + SearchBuilder hostSearch2 = hostDao.createSearchBuilder(); + hostSearch2.and("clusterId", hostSearch2.entity().getClusterId(), SearchCriteria.Op.NEQ); + VmsNotInClusterUsingPool.join("hostSearch2", hostSearch2, hostSearch2.entity().getId(), VmsNotInClusterUsingPool.entity().getHostId(), JoinType.INNER); + VmsNotInClusterUsingPool.and("vmStates", VmsNotInClusterUsingPool.entity().getState(), Op.IN); + VmsNotInClusterUsingPool.done(); } @Override @@ -836,7 +854,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem public List listNonRemovedVmsByTypeAndNetwork(long networkId, VirtualMachine.Type... types) { if (NetworkTypeSearch == null) { - SearchBuilder nicSearch = _nicDao.createSearchBuilder(); + SearchBuilder nicSearch = nicDao.createSearchBuilder(); nicSearch.and("networkId", nicSearch.entity().getNetworkId(), SearchCriteria.Op.EQ); NetworkTypeSearch = createSearchBuilder(); @@ -873,7 +891,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem txn.start(); VMInstanceVO vm = findById(id); if (vm != null && vm.getType() == Type.User) { - _tagsDao.removeByIdAndType(id, ResourceObjectType.UserVm); + tagsDao.removeByIdAndType(id, ResourceObjectType.UserVm); } boolean result = super.remove(id); txn.commit(); @@ -1040,4 +1058,14 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem Filter filter = new Filter(VMInstanceVO.class, "id", true, 0L, batchSize); return searchIncludingRemoved(sc, filter, null, false); } + + public Pair, Integer> listByVmsNotInClusterUsingPool(long clusterId, long poolId) { + SearchCriteria sc = VmsNotInClusterUsingPool.create(); + sc.setParameters("vmStates", State.Starting, State.Running, State.Stopping, State.Migrating, State.Restoring); + sc.setJoinParameters("volumeSearch", "poolId", poolId); + sc.setJoinParameters("hostSearch2", "clusterId", clusterId); + List vms = search(sc, null); + List uniqueVms = vms.stream().distinct().collect(Collectors.toList()); + return new Pair<>(uniqueVms, uniqueVms.size()); + } } diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java index 8044a2dfa5e..e4c26932619 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java @@ -32,6 +32,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import org.springframework.stereotype.Component; +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; @@ -43,17 +44,20 @@ import com.cloud.agent.api.StoragePoolInfo; import com.cloud.capacity.Capacity; import com.cloud.capacity.CapacityVO; import com.cloud.capacity.dao.CapacityDao; -import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.DataStoreRole; import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.StoragePoolStatus; -import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.utils.crypt.DBEncryptionUtil; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; @Component @@ -266,4 +270,48 @@ public class PrimaryDataStoreHelper { return true; } + public void switchToZone(DataStore store, HypervisorType hypervisorType) { + StoragePoolVO pool = dataStoreDao.findById(store.getId()); + CapacityVO capacity = _capacityDao.findByHostIdType(store.getId(), Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED); + Transaction.execute(new TransactionCallbackNoReturn() { + public void doInTransactionWithoutResult(TransactionStatus status) { + pool.setScope(ScopeType.ZONE); + pool.setPodId(null); + pool.setClusterId(null); + pool.setHypervisor(hypervisorType); + dataStoreDao.update(pool.getId(), pool); + + capacity.setPodId(null); + capacity.setClusterId(null); + _capacityDao.update(capacity.getId(), capacity); + } + }); + logger.debug("Scope of storage pool id=" + pool.getId() + " is changed to zone"); + } + + public void switchToCluster(DataStore store, ClusterScope clusterScope) { + List hostPoolRecords = storagePoolHostDao.listByPoolIdNotInCluster(clusterScope.getScopeId(), store.getId()).first(); + StoragePoolVO pool = dataStoreDao.findById(store.getId()); + CapacityVO capacity = _capacityDao.findByHostIdType(store.getId(), Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED); + + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + if (hostPoolRecords != null) { + for (StoragePoolHostVO host : hostPoolRecords) { + storagePoolHostDao.deleteStoragePoolHostDetails(host.getHostId(), host.getPoolId()); + } + } + pool.setScope(ScopeType.CLUSTER); + pool.setPodId(clusterScope.getPodId()); + pool.setClusterId(clusterScope.getScopeId()); + dataStoreDao.update(pool.getId(), pool); + + capacity.setPodId(clusterScope.getPodId()); + capacity.setClusterId(clusterScope.getScopeId()); + _capacityDao.update(capacity.getId(), capacity); + } + }); + logger.debug("Scope of storage pool id=" + pool.getId() + " is changed to cluster id=" + clusterScope.getScopeId()); + } } diff --git a/engine/storage/src/test/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelperTest.java b/engine/storage/src/test/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelperTest.java new file mode 100644 index 00000000000..3927b43f393 --- /dev/null +++ b/engine/storage/src/test/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelperTest.java @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.storage.volume.datastore; + +import java.util.List; + +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.capacity.Capacity; +import com.cloud.capacity.CapacityVO; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage; +import com.cloud.storage.StoragePoolHostVO; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.utils.Pair; + +@RunWith(MockitoJUnitRunner.class) +public class PrimaryDataStoreHelperTest { + + @Mock + private PrimaryDataStoreDao dataStoreDao; + + @Mock + private CapacityDao capacityDao; + + @Mock + private StoragePoolHostDao storagePoolHostDao; + + @Spy + @InjectMocks + PrimaryDataStoreHelper dataStoreHelper; + + private static final Long ZONE_ID = 1L; + private static final Long CLUSTER_ID = 2L; + private static final Long POD_ID = 3L; + private static final Long POOL_ID = 4L; + private static final Short capacityType = 0; + private static final Float usedPercentage = 0.0f; + + @Test + public void testSwitchToZone() { + StoragePoolVO pool = new StoragePoolVO(POOL_ID, null, null, Storage.StoragePoolType.NetworkFilesystem, ZONE_ID, POD_ID, 0L, 0L, null, 0, null); + pool.setClusterId(CLUSTER_ID); + pool.setScope(ScopeType.CLUSTER); + CapacityVO capacity = new CapacityVO(ZONE_ID, POD_ID, CLUSTER_ID, capacityType, usedPercentage); + + Mockito.when(dataStoreDao.findById(pool.getId())).thenReturn(pool); + Mockito.when(capacityDao.findByHostIdType(pool.getId(), Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED)).thenReturn(capacity); + DataStore storeMock = Mockito.mock(DataStore.class); + Mockito.when(storeMock.getId()).thenReturn(POOL_ID); + + dataStoreHelper.switchToZone(storeMock, HypervisorType.KVM); + + Assert.assertEquals(pool.getScope(), ScopeType.ZONE); + Assert.assertEquals(pool.getPodId(), null); + Assert.assertEquals(pool.getClusterId(), null); + Assert.assertEquals(pool.getHypervisor(), HypervisorType.KVM); + Assert.assertEquals(capacity.getPodId(), null); + Assert.assertEquals(capacity.getClusterId(), null); + } + + @Test + public void testSwitchToCluster() { + StoragePoolVO pool = new StoragePoolVO(POOL_ID, null, null, Storage.StoragePoolType.NetworkFilesystem, ZONE_ID, null, 0L, 0L, null, 0, null); + pool.setScope(ScopeType.ZONE); + CapacityVO capacity = new CapacityVO(ZONE_ID, null, null, capacityType, usedPercentage); + ClusterScope clusterScope = new ClusterScope(CLUSTER_ID, POD_ID, ZONE_ID); + + Pair, Integer> hostPoolRecords = new Pair<>(null, 0); + Mockito.when(storagePoolHostDao.listByPoolIdNotInCluster(CLUSTER_ID, POOL_ID)).thenReturn(hostPoolRecords); + Mockito.when(dataStoreDao.findById(pool.getId())).thenReturn(pool); + Mockito.when(capacityDao.findByHostIdType(pool.getId(), Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED)).thenReturn(capacity); + DataStore storeMock = Mockito.mock(DataStore.class); + Mockito.when(storeMock.getId()).thenReturn(POOL_ID); + + dataStoreHelper.switchToCluster(storeMock, clusterScope); + + Mockito.verify(storagePoolHostDao, Mockito.never()).deleteStoragePoolHostDetails(Mockito.anyLong(), Mockito.anyLong()); + + Assert.assertEquals(pool.getScope(), ScopeType.CLUSTER); + Assert.assertEquals(pool.getPodId(), POD_ID); + Assert.assertEquals(pool.getClusterId(), CLUSTER_ID); + Assert.assertEquals(capacity.getPodId(), POD_ID); + Assert.assertEquals(capacity.getClusterId(), CLUSTER_ID); + } +} diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImpl.java new file mode 100644 index 00000000000..adc74a77d43 --- /dev/null +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImpl.java @@ -0,0 +1,106 @@ +// 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.datastore.lifecycle; + +import java.util.Arrays; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; +import org.apache.log4j.Logger; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.DeleteStoragePoolCommand; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolHostVO; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.utils.Pair; + +public class BasePrimaryDataStoreLifeCycleImpl { + private static final Logger s_logger = Logger.getLogger(BasePrimaryDataStoreLifeCycleImpl.class); + @Inject + AgentManager agentMgr; + @Inject + protected ResourceManager resourceMgr; + @Inject + StorageManager storageMgr; + @Inject + PrimaryDataStoreHelper dataStoreHelper; + @Inject + protected HostDao hostDao; + @Inject + protected StoragePoolHostDao storagePoolHostDao; + + private List getPoolHostsList(ClusterScope clusterScope, HypervisorType hypervisorType) { + List hosts; + if (hypervisorType != null) { + hosts = resourceMgr + .listAllHostsInOneZoneNotInClusterByHypervisor(hypervisorType, clusterScope.getZoneId(), clusterScope.getScopeId()); + } else { + List hypervisorTypes = Arrays.asList(HypervisorType.KVM, HypervisorType.VMware); + hosts = resourceMgr + .listAllHostsInOneZoneNotInClusterByHypervisors(hypervisorTypes, clusterScope.getZoneId(), clusterScope.getScopeId()); + } + return hosts; + } + + public void changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + List hosts = getPoolHostsList(clusterScope, hypervisorType); + s_logger.debug("Changing scope of the storage pool to Zone"); + if (hosts != null) { + for (HostVO host : hosts) { + try { + storageMgr.connectHostToSharedPool(host.getId(), store.getId()); + } catch (Exception e) { + s_logger.warn("Unable to establish a connection between " + host + " and " + store, e); + } + } + } + dataStoreHelper.switchToZone(store, hypervisorType); + } + + public void changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + Pair, Integer> hostPoolRecords = storagePoolHostDao.listByPoolIdNotInCluster(clusterScope.getScopeId(), store.getId()); + s_logger.debug("Changing scope of the storage pool to Cluster"); + if (hostPoolRecords.second() > 0) { + StoragePool pool = (StoragePool) store; + for (StoragePoolHostVO host : hostPoolRecords.first()) { + DeleteStoragePoolCommand deleteCmd = new DeleteStoragePoolCommand(pool); + final Answer answer = agentMgr.easySend(host.getHostId(), deleteCmd); + + if (answer != null) { + if (!answer.getResult()) { + s_logger.debug("Failed to delete storage pool: " + answer.getResult()); + } else if (HypervisorType.KVM != hypervisorType) { + break; + } + } + } + } + dataStoreHelper.switchToCluster(store, clusterScope); + } +} diff --git a/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImplTest.java b/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImplTest.java new file mode 100644 index 00000000000..355eb075129 --- /dev/null +++ b/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImplTest.java @@ -0,0 +1,127 @@ +// 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.datastore.lifecycle; + +import static org.mockito.ArgumentMatchers.eq; + +import java.util.Arrays; +import java.util.List; + +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; +import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.DeleteStoragePoolCommand; +import com.cloud.host.HostVO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.Storage; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePoolHostVO; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.utils.Pair; + +@RunWith(MockitoJUnitRunner.class) +public class BasePrimaryDataStoreLifeCycleImplTest { + + @Mock + private StoragePoolHostDao storagePoolHostDao; + + @Mock + private PrimaryDataStoreHelper dataStoreHelper; + + @Mock + private AgentManager agentManager; + + @Mock + private ResourceManager resourceManager; + + @Mock + private StorageManager storageManager; + + @Spy + @InjectMocks + private BasePrimaryDataStoreLifeCycleImpl dataStoreLifeCycle; + + private static final Long POOL_ID = 1L; + private static final Long CLUSTER_ID = 2L; + private static final Long POD_ID = 3L; + private static final Long ZONE_ID = 4L; + private static final Long HOST_ID = 5L; + + private static ClusterScope clusterScope; + private static PrimaryDataStoreImpl store; + + + @BeforeClass + public static void init() { + clusterScope = new ClusterScope(CLUSTER_ID, POD_ID, ZONE_ID); + StoragePoolVO pool = new StoragePoolVO(POOL_ID, null, null, Storage.StoragePoolType.NetworkFilesystem, 0L, 0L, 0L, 0L, null, 0, null); + store = new PrimaryDataStoreImpl(); + store.configure(pool, null, null); + } + + @Test + public void testChangeStoragePoolScopeToZone() throws Exception { + Mockito.when(resourceManager.listAllHostsInOneZoneNotInClusterByHypervisor(HypervisorType.KVM, ZONE_ID, CLUSTER_ID)).thenReturn(null); + + dataStoreLifeCycle.changeStoragePoolScopeToZone(store, clusterScope, HypervisorType.KVM); + + Mockito.verify(dataStoreHelper, Mockito.times(1)).switchToZone(store, HypervisorType.KVM); + + HostVO host = new HostVO(null); + ReflectionTestUtils.setField(host, "id", HOST_ID); + List hypervisorTypes = Arrays.asList(HypervisorType.KVM, HypervisorType.VMware); + Mockito.when(resourceManager.listAllHostsInOneZoneNotInClusterByHypervisors(hypervisorTypes, ZONE_ID, CLUSTER_ID)).thenReturn(Arrays.asList(host)); + Mockito.when(storageManager.connectHostToSharedPool(HOST_ID, POOL_ID)).thenReturn(true); + + dataStoreLifeCycle.changeStoragePoolScopeToZone(store, clusterScope, null); + + Mockito.verify(dataStoreHelper, Mockito.times(1)).switchToZone(store, null); + } + + @Test + public void testChangeStoragePoolScopeToCluster() { + Pair, Integer> hostPoolRecords = new Pair<>(null, 0); + Mockito.when(storagePoolHostDao.listByPoolIdNotInCluster(CLUSTER_ID, POOL_ID)).thenReturn(hostPoolRecords); + Mockito.doNothing().when(dataStoreHelper).switchToCluster(store, clusterScope); + + dataStoreLifeCycle.changeStoragePoolScopeToCluster(store, clusterScope, HypervisorType.KVM); + + hostPoolRecords.set(Arrays.asList(new StoragePoolHostVO(POOL_ID, HOST_ID, null)), 1); + Answer answer = new Answer(null, false, null); + Mockito.when(storagePoolHostDao.listByPoolIdNotInCluster(CLUSTER_ID, POOL_ID)).thenReturn(hostPoolRecords); + Mockito.when(agentManager.easySend(eq(HOST_ID), Mockito.any(DeleteStoragePoolCommand.class))).thenReturn(answer); + + dataStoreLifeCycle.changeStoragePoolScopeToCluster(store, clusterScope, HypervisorType.KVM); + + Mockito.verify(dataStoreHelper, Mockito.times(2)).switchToCluster(store, clusterScope); + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareStorageClientCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareStorageClientCommandWrapper.java new file mode 100644 index 00000000000..79afd4696b0 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareStorageClientCommandWrapper.java @@ -0,0 +1,52 @@ +// +// 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 com.cloud.hypervisor.kvm.resource.wrapper; + +import java.util.Map; + +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.PrepareStorageClientAnswer; +import com.cloud.agent.api.PrepareStorageClientCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.Ternary; + +@ResourceWrapper(handles = PrepareStorageClientCommand.class) +public class LibvirtPrepareStorageClientCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(LibvirtPrepareStorageClientCommandWrapper.class); + + @Override + public Answer execute(PrepareStorageClientCommand cmd, LibvirtComputingResource libvirtComputingResource) { + final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + Ternary, String> prepareStorageClientResult = storagePoolMgr.prepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid(), cmd.getDetails()); + if (!prepareStorageClientResult.first()) { + String msg = prepareStorageClientResult.third(); + s_logger.debug("Unable to prepare storage client, due to: " + msg); + return new PrepareStorageClientAnswer(cmd, false, msg); + } + Map details = prepareStorageClientResult.second(); + return new PrepareStorageClientAnswer(cmd, true, details); + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnprepareStorageClientCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnprepareStorageClientCommandWrapper.java new file mode 100644 index 00000000000..f98782fe748 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnprepareStorageClientCommandWrapper.java @@ -0,0 +1,49 @@ +// +// 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 com.cloud.hypervisor.kvm.resource.wrapper; + +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.UnprepareStorageClientAnswer; +import com.cloud.agent.api.UnprepareStorageClientCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.Pair; + +@ResourceWrapper(handles = UnprepareStorageClientCommand.class) +public class LibvirtUnprepareStorageClientCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(LibvirtUnprepareStorageClientCommandWrapper.class); + + @Override + public Answer execute(UnprepareStorageClientCommand cmd, LibvirtComputingResource libvirtComputingResource) { + final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + Pair unprepareStorageClientResult = storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid()); + if (!unprepareStorageClientResult.first()) { + String msg = unprepareStorageClientResult.second(); + s_logger.debug("Couldn't unprepare storage client, due to: " + msg); + return new UnprepareStorageClientAnswer(cmd, false, msg); + } + return new UnprepareStorageClientAnswer(cmd, true); + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java index 27f70b71ab4..3c8026c7ffd 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java @@ -45,6 +45,8 @@ import com.cloud.storage.Storage; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageLayer; import com.cloud.storage.Volume; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; @@ -475,4 +477,13 @@ public class KVMStoragePoolManager { return adaptor.createTemplateFromDirectDownloadFile(templateFilePath, destTemplatePath, destPool, format, timeout); } + public Ternary, String> prepareStorageClient(StoragePoolType type, String uuid, Map details) { + StorageAdaptor adaptor = getStorageAdaptor(type); + return adaptor.prepareStorageClient(type, uuid, details); + } + + public Pair unprepareStorageClient(StoragePoolType type, String uuid) { + StorageAdaptor adaptor = getStorageAdaptor(type); + return adaptor.unprepareStorageClient(type, uuid); + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java index 1b660671b41..b33f49404ad 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient; import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil; import org.apache.cloudstack.utils.cryptsetup.CryptSetup; import org.apache.cloudstack.utils.cryptsetup.CryptSetupException; @@ -43,6 +44,8 @@ import org.libvirt.LibvirtException; import com.cloud.storage.Storage; import com.cloud.storage.StorageManager; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.OutputInterpreter; import com.cloud.utils.script.Script; @@ -564,6 +567,67 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor { qemu.resize(options, objects, usableSizeBytes); } + public Ternary, String> prepareStorageClient(Storage.StoragePoolType type, String uuid, Map details) { + if (!ScaleIOUtil.isSDCServiceInstalled()) { + logger.debug("SDC service not installed on host, preparing the SDC client not possible"); + return new Ternary<>(false, null, "SDC service not installed on host"); + } + + if (!ScaleIOUtil.isSDCServiceEnabled()) { + logger.debug("SDC service not enabled on host, enabling it"); + if (!ScaleIOUtil.enableSDCService()) { + return new Ternary<>(false, null, "SDC service not enabled on host"); + } + } + + if (!ScaleIOUtil.isSDCServiceActive()) { + if (!ScaleIOUtil.startSDCService()) { + return new Ternary<>(false, null, "Couldn't start SDC service on host"); + } + } else if (!ScaleIOUtil.restartSDCService()) { + return new Ternary<>(false, null, "Couldn't restart SDC service on host"); + } + + return new Ternary<>( true, getSDCDetails(details), "Prepared client successfully"); + } + + public Pair unprepareStorageClient(Storage.StoragePoolType type, String uuid) { + if (!ScaleIOUtil.isSDCServiceInstalled()) { + logger.debug("SDC service not installed on host, no need to unprepare the SDC client"); + return new Pair<>(true, "SDC service not installed on host, no need to unprepare the SDC client"); + } + + if (!ScaleIOUtil.isSDCServiceEnabled()) { + logger.debug("SDC service not enabled on host, no need to unprepare the SDC client"); + return new Pair<>(true, "SDC service not enabled on host, no need to unprepare the SDC client"); + } + + if (!ScaleIOUtil.stopSDCService()) { + return new Pair<>(false, "Couldn't stop SDC service on host"); + } + + return new Pair<>(true, "Unprepared SDC client successfully"); + } + + private Map getSDCDetails(Map details) { + Map sdcDetails = new HashMap(); + if (details == null || !details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)) { + return sdcDetails; + } + + String storageSystemId = details.get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID); + String sdcId = ScaleIOUtil.getSdcId(storageSystemId); + if (sdcId != null) { + sdcDetails.put(ScaleIOGatewayClient.SDC_ID, sdcId); + } else { + String sdcGuId = ScaleIOUtil.getSdcGuid(); + if (sdcGuId != null) { + sdcDetails.put(ScaleIOGatewayClient.SDC_GUID, sdcGuId); + } + } + return sdcDetails; + } + /** * Calculates usable size from raw size, assuming qcow2 requires 192k/1GB for metadata * We also remove 128MiB for encryption/fragmentation/safety factor. diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java index 6d58dccb5d1..9a27d44ff92 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.hypervisor.kvm.storage; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -23,6 +24,8 @@ import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; import com.cloud.storage.Storage; import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; public interface StorageAdaptor { @@ -114,4 +117,25 @@ public interface StorageAdaptor { default boolean supportsPhysicalDiskCopy(StoragePoolType type) { return StoragePoolType.PowerFlex == type; } + + /** + * Prepares the storage client. + * @param type type of the storage pool + * @param uuid uuid of the storage pool + * @param details any details of the storage pool that are required for client preparation + * @return status, client details, & message in case failed + */ + default Ternary, String> prepareStorageClient(StoragePoolType type, String uuid, Map details) { + return new Ternary<>(true, new HashMap<>(), ""); + } + + /** + * Unprepares the storage client. + * @param type type of the storage pool + * @param uuid uuid of the storage pool + * @return status, & message in case failed + */ + default Pair unprepareStorageClient(StoragePoolType type, String uuid) { + return new Pair<>(true, ""); + } } diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareStorageClientCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareStorageClientCommandWrapperTest.java new file mode 100644 index 00000000000..e7dffeece71 --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareStorageClientCommandWrapperTest.java @@ -0,0 +1,87 @@ +/* + * Licensed 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 com.cloud.hypervisor.kvm.resource.wrapper; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.PrepareStorageClientAnswer; +import com.cloud.agent.api.PrepareStorageClientCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.storage.Storage; +import com.cloud.utils.Ternary; + +@RunWith(MockitoJUnitRunner.class) +public class LibvirtPrepareStorageClientCommandWrapperTest { + + @Spy + LibvirtPrepareStorageClientCommandWrapper libvirtPrepareStorageClientCommandWrapperSpy = Mockito.spy(LibvirtPrepareStorageClientCommandWrapper.class); + + @Mock + LibvirtComputingResource libvirtComputingResourceMock; + + private final static String poolUuid = "345fc603-2d7e-47d2-b719-a0110b3732e6"; + private final static String systemId = "218ce1797566a00f"; + private final static String sdcId = "301b852c00000003"; + + @Test + public void testPrepareStorageClientSuccess() { + Map details = new HashMap<>(); + details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId); + PrepareStorageClientCommand cmd = Mockito.mock(PrepareStorageClientCommand.class); + Mockito.when(cmd.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex); + Mockito.when(cmd.getPoolUuid()).thenReturn(poolUuid); + Mockito.when(cmd.getDetails()).thenReturn(details); + + KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); + Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr); + details.put(ScaleIOGatewayClient.SDC_ID, sdcId); + Mockito.when(storagePoolMgr.prepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid(), cmd.getDetails())).thenReturn(new Ternary<>(true, details, "")); + + PrepareStorageClientAnswer result = (PrepareStorageClientAnswer) libvirtPrepareStorageClientCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock); + + Assert.assertTrue(result.getResult()); + Assert.assertEquals(sdcId, result.getDetailsMap().get(ScaleIOGatewayClient.SDC_ID)); + } + + @Test + public void testPrepareStorageClientFailure() { + Map details = new HashMap<>(); + details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId); + PrepareStorageClientCommand cmd = Mockito.mock(PrepareStorageClientCommand.class); + Mockito.when(cmd.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex); + Mockito.when(cmd.getPoolUuid()).thenReturn(poolUuid); + Mockito.when(cmd.getDetails()).thenReturn(details); + + KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); + Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr); + Mockito.when(storagePoolMgr.prepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid(), cmd.getDetails())).thenReturn(new Ternary<>(false, new HashMap<>() , "Prepare storage client failed")); + + PrepareStorageClientAnswer result = (PrepareStorageClientAnswer) libvirtPrepareStorageClientCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock); + + Assert.assertFalse(result.getResult()); + Assert.assertEquals("Prepare storage client failed", result.getDetails()); + } +} diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnprepareStorageClientCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnprepareStorageClientCommandWrapperTest.java new file mode 100644 index 00000000000..7409b286f32 --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnprepareStorageClientCommandWrapperTest.java @@ -0,0 +1,73 @@ +/* + * Licensed 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 com.cloud.hypervisor.kvm.resource.wrapper; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.UnprepareStorageClientAnswer; +import com.cloud.agent.api.UnprepareStorageClientCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.storage.Storage; +import com.cloud.utils.Pair; + +@RunWith(MockitoJUnitRunner.class) +public class LibvirtUnprepareStorageClientCommandWrapperTest { + + @Spy + LibvirtUnprepareStorageClientCommandWrapper libvirtUnprepareStorageClientCommandWrapperSpy = Mockito.spy(LibvirtUnprepareStorageClientCommandWrapper.class); + + @Mock + LibvirtComputingResource libvirtComputingResourceMock; + + private final static String poolUuid = "345fc603-2d7e-47d2-b719-a0110b3732e6"; + + @Test + public void testUnprepareStorageClientSuccess() { + UnprepareStorageClientCommand cmd = Mockito.mock(UnprepareStorageClientCommand.class); + Mockito.when(cmd.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex); + Mockito.when(cmd.getPoolUuid()).thenReturn(poolUuid); + + KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); + Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr); + Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid())).thenReturn(new Pair<>(true, "")); + + UnprepareStorageClientAnswer result = (UnprepareStorageClientAnswer) libvirtUnprepareStorageClientCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock); + + Assert.assertTrue(result.getResult()); + } + + @Test + public void testUnprepareStorageClientFailure() { + UnprepareStorageClientCommand cmd = Mockito.mock(UnprepareStorageClientCommand.class); + Mockito.when(cmd.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex); + Mockito.when(cmd.getPoolUuid()).thenReturn(poolUuid); + + KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); + Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr); + Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid())).thenReturn(new Pair<>(false, "Unprepare storage client failed")); + + UnprepareStorageClientAnswer result = (UnprepareStorageClientAnswer) libvirtUnprepareStorageClientCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock); + + Assert.assertFalse(result.getResult()); + Assert.assertEquals("Unprepare storage client failed", result.getDetails()); + } +} diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java index 25fab1a6ff8..07aea0cfbee 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java @@ -17,13 +17,50 @@ package com.cloud.hypervisor.kvm.storage; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient; +import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import com.cloud.storage.Storage; +import com.cloud.storage.StorageLayer; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; +import com.cloud.utils.script.Script; + @RunWith(MockitoJUnitRunner.class) public class ScaleIOStorageAdaptorTest { + + @Mock + StorageLayer storageLayer; + ScaleIOStorageAdaptor scaleIOStorageAdaptor; + + private final static String poolUuid = "345fc603-2d7e-47d2-b719-a0110b3732e6"; + private static MockedStatic + +