This is an automated email from the ASF dual-hosted git repository. sureshanaparti pushed a commit to branch 4.19 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.19 by this push: new 2ca1b474bd6 PowerFlex/ScaleIO SDC client connection improvements (#9268) 2ca1b474bd6 is described below commit 2ca1b474bd6ccaafb91c90cf91dc7d3e71519148 Author: Suresh Kumar Anaparti <sureshkumar.anapa...@gmail.com> AuthorDate: Sat Jun 29 10:01:50 2024 +0530 PowerFlex/ScaleIO SDC client connection improvements (#9268) * Mitigation for non-scalable Powerflex/ScaleIO clients - Added ScaleIOSDCManager to manage SDC connections, checks clients limit, prepare and unprepare SDC on the hosts. - Added commands for prepare and unprepare storage clients to prepare/start and stop SDC service respectively on the hosts. - Introduced config 'storage.pool.connected.clients.limit' at storage level for client limits, currently support for Powerflex only. * tests issue fixed * refactor / improvements * lock with powerflex systemid while checking connections limit * updated powerflex systemid lock to hold till sdc preparation * Added custom stats support for storage pool, through listStoragePools API * code improvements, and unit tests * unit tests fixes * Update config 'storage.pool.connected.clients.limit' to dynamic, and some improvements * Stop SDC on host after migration if no volumes mapped to host * Wait for SDC to connect after scini service start, and some log improvements * Do not throw exception (log it) when SDC is not connected while revoking access for the powerflex volume * some log improvements --- .../org/apache/cloudstack/api/ApiConstants.java | 1 + .../command/admin/storage/ListStoragePoolsCmd.java | 7 +- .../api/response/StoragePoolResponse.java | 12 + .../agent/api/PrepareStorageClientAnswer.java | 43 +++ .../agent/api/PrepareStorageClientCommand.java | 56 ++++ .../agent/api/UnprepareStorageClientAnswer.java | 34 ++ .../agent/api/UnprepareStorageClientCommand.java | 48 +++ .../api/storage/PrimaryDataStoreDriver.java | 26 ++ .../java/com/cloud/storage/StorageManager.java | 21 +- .../LibvirtPrepareStorageClientCommandWrapper.java | 52 ++++ ...ibvirtUnprepareStorageClientCommandWrapper.java | 49 +++ .../kvm/storage/KVMStoragePoolManager.java | 11 + .../kvm/storage/ScaleIOStorageAdaptor.java | 64 ++++ .../hypervisor/kvm/storage/StorageAdaptor.java | 24 ++ ...virtPrepareStorageClientCommandWrapperTest.java | 87 ++++++ ...rtUnprepareStorageClientCommandWrapperTest.java | 73 +++++ .../kvm/storage/ScaleIOStorageAdaptorTest.java | 191 ++++++++++++ .../datastore/client/ScaleIOGatewayClient.java | 2 + .../datastore/client/ScaleIOGatewayClientImpl.java | 26 ++ .../driver/ScaleIOPrimaryDataStoreDriver.java | 102 +++--- .../ScaleIOPrimaryDataStoreLifeCycle.java | 30 +- .../datastore/manager/ScaleIOSDCManager.java | 47 +++ .../datastore/manager/ScaleIOSDCManagerImpl.java | 346 +++++++++++++++++++++ .../datastore/provider/ScaleIOHostListener.java | 66 ++-- .../storage/datastore/util/ScaleIOUtil.java | 45 +++ .../spring-storage-volume-scaleio-context.xml | 2 + .../ScaleIOPrimaryDataStoreLifeCycleTest.java | 5 +- server/src/main/java/com/cloud/api/ApiDBUtils.java | 4 +- .../main/java/com/cloud/api/ApiResponseHelper.java | 2 +- .../java/com/cloud/api/query/QueryManagerImpl.java | 6 +- .../com/cloud/api/query/ViewResponseHelper.java | 4 +- .../cloud/api/query/dao/StoragePoolJoinDao.java | 2 +- .../api/query/dao/StoragePoolJoinDaoImpl.java | 11 +- .../deploy/DeploymentPlanningManagerImpl.java | 9 + .../java/com/cloud/storage/StorageManagerImpl.java | 41 +++ 35 files changed, 1431 insertions(+), 118 deletions(-) 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 050464a13a6..2324b861830 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -439,6 +439,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/ListStoragePoolsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java index 6923353b3bf..3da99de050b 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 @@ -74,7 +74,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 /////////////////////// ///////////////////////////////////////////////////// @@ -131,6 +132,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/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<String, String> 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<String, String> getCustomStats() { + return customStats; + } + + public void setCustomStats(Map<String, String> customStats) { + this.customStats = customStats; + } + public String getTags() { return tags; } 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<String, String> detailsMap; + + public PrepareStorageClientAnswer() { + super(); + } + + public PrepareStorageClientAnswer(Command command, boolean success, Map<String, String> detailsMap) { + super(command, success, ""); + this.detailsMap = detailsMap; + } + + public PrepareStorageClientAnswer(Command command, boolean success, String details) { + super(command, success, details); + } + + public Map<String, String> 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<String, String> details; + + public PrepareStorageClientCommand() { + } + + public PrepareStorageClientCommand(StoragePoolType poolType, String poolUuid, Map<String, String> 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<String, String> 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 2c7d3c60278..0e70c7b528d 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<String, String> 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/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index 5e97cc9edfe..86ef02bb9bc 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<Integer> 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<String> 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<String, String> 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 @@ -286,6 +299,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/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<PrepareStorageClientCommand, Answer, LibvirtComputingResource> { + + private static final Logger s_logger = Logger.getLogger(LibvirtPrepareStorageClientCommandWrapper.class); + + @Override + public Answer execute(PrepareStorageClientCommand cmd, LibvirtComputingResource libvirtComputingResource) { + final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + Ternary<Boolean, Map<String, String>, 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<String, String> 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<UnprepareStorageClientCommand, Answer, LibvirtComputingResource> { + + private static final Logger s_logger = Logger.getLogger(LibvirtUnprepareStorageClientCommandWrapper.class); + + @Override + public Answer execute(UnprepareStorageClientCommand cmd, LibvirtComputingResource libvirtComputingResource) { + final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + Pair<Boolean, String> 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 b1842f38da2..4f25cfa08d5 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 @@ -42,6 +42,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; @@ -447,4 +449,13 @@ public class KVMStoragePoolManager { return adaptor.createTemplateFromDirectDownloadFile(templateFilePath, destTemplatePath, destPool, format, timeout); } + public Ternary<Boolean, Map<String, String>, String> prepareStorageClient(StoragePoolType type, String uuid, Map<String, String> details) { + StorageAdaptor adaptor = getStorageAdaptor(type); + return adaptor.prepareStorageClient(type, uuid, details); + } + + public Pair<Boolean, String> 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 7a98e3fb11f..60986f198a8 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.StorageLayer; 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; @@ -561,6 +564,67 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor { qemu.resize(options, objects, usableSizeBytes); } + public Ternary<Boolean, Map<String, String>, String> prepareStorageClient(Storage.StoragePoolType type, String uuid, Map<String, String> 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<Boolean, String> 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<String, String> getSDCDetails(Map<String, String> details) { + Map<String, String> sdcDetails = new HashMap<String, String>(); + 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 5cfdf6d1a8b..80e73e01a86 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 { @@ -105,4 +108,25 @@ public interface StorageAdaptor { * @param timeout */ KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, String destTemplatePath, KVMStoragePool destPool, Storage.ImageFormat format, int timeout); + + /** + * 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<Boolean, Map<String, String>, String> prepareStorageClient(StoragePoolType type, String uuid, Map<String, String> 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<Boolean, String> 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<String, String> 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<String, String> 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..7db4f114e8c 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<Script> mockedScript; + + @Before + public void setUp() { + mockedScript = Mockito.mockStatic(Script.class); + scaleIOStorageAdaptor = Mockito.spy(new ScaleIOStorageAdaptor(storageLayer)); + } + + @After + public void tearDown() { + mockedScript.close(); + } + @Test public void getUsableBytesFromRawBytesTest() { Assert.assertEquals("Overhead calculated for 8Gi size", 8454111232L, ScaleIOStorageAdaptor.getUsableBytesFromRawBytes(8L << 30)); @@ -31,4 +68,158 @@ public class ScaleIOStorageAdaptorTest { Assert.assertEquals("Overhead calculated for 500Gi size", 536636342272L, ScaleIOStorageAdaptor.getUsableBytesFromRawBytes(500L << 30)); Assert.assertEquals("Unsupported small size", 0, ScaleIOStorageAdaptor.getUsableBytesFromRawBytes(1L)); } + + @Test + public void testPrepareStorageClient_SDCServiceNotInstalled() { + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(4); + + Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>()); + + Assert.assertFalse(result.first()); + Assert.assertNull(result.second()); + Assert.assertEquals("SDC service not installed on host", result.third()); + } + + @Test + public void testPrepareStorageClient_SDCServiceNotEnabled() { + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(1); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl enable scini"))).thenReturn(1); + + Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>()); + + Assert.assertFalse(result.first()); + Assert.assertNull(result.second()); + Assert.assertEquals("SDC service not enabled on host", result.third()); + } + + @Test + public void testPrepareStorageClient_SDCServiceNotRestarted() { + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-active scini"))).thenReturn(0); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(1); + + Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>()); + + Assert.assertFalse(result.first()); + Assert.assertNull(result.second()); + Assert.assertEquals("Couldn't restart SDC service on host", result.third()); + } + + @Test + public void testPrepareStorageClient_SDCServiceRestarted() { + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-active scini"))).thenReturn(0); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(0); + + Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>()); + + Assert.assertTrue(result.first()); + Assert.assertNotNull(result.second()); + Assert.assertTrue(result.second().isEmpty()); + } + + @Test + public void testPrepareStorageClient_SDCServiceNotStarted() { + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-active scini"))).thenReturn(1); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl start scini"))).thenReturn(1); + + Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>()); + + Assert.assertFalse(result.first()); + Assert.assertNull(result.second()); + Assert.assertEquals("Couldn't start SDC service on host", result.third()); + } + + @Test + public void testPrepareStorageClient_SDCServiceStartedReturnSDCId() { + Map<String, String> details = new HashMap<>(); + String systemId = "218ce1797566a00f"; + details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId); + + try (MockedStatic<ScaleIOUtil> ignored = Mockito.mockStatic(ScaleIOUtil.class)) { + when(ScaleIOUtil.isSDCServiceInstalled()).thenReturn(true); + when(ScaleIOUtil.isSDCServiceEnabled()).thenReturn(true); + when(ScaleIOUtil.isSDCServiceActive()).thenReturn(false); + when(ScaleIOUtil.startSDCService()).thenReturn(true); + String sdcId = "301b852c00000003"; + when(ScaleIOUtil.getSdcId(systemId)).thenReturn(sdcId); + + Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, details); + + Assert.assertTrue(result.first()); + Assert.assertNotNull(result.second()); + Assert.assertEquals(sdcId, result.second().get(ScaleIOGatewayClient.SDC_ID)); + } + } + + @Test + public void testPrepareStorageClient_SDCServiceStartedReturnSDCGuid() { + Map<String, String> details = new HashMap<>(); + String systemId = "218ce1797566a00f"; + details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId); + + String sdcGuid = "B0E3BFB8-C20B-43BF-93C8-13339E85AA50"; + try (MockedStatic<ScaleIOUtil> ignored = Mockito.mockStatic(ScaleIOUtil.class)) { + when(ScaleIOUtil.isSDCServiceInstalled()).thenReturn(true); + when(ScaleIOUtil.isSDCServiceEnabled()).thenReturn(true); + when(ScaleIOUtil.isSDCServiceActive()).thenReturn(false); + when(ScaleIOUtil.startSDCService()).thenReturn(true); + when(ScaleIOUtil.getSdcId(systemId)).thenReturn(null); + when(ScaleIOUtil.getSdcGuid()).thenReturn(sdcGuid); + + Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, details); + Assert.assertTrue(result.first()); + Assert.assertNotNull(result.second()); + Assert.assertEquals(sdcGuid, result.second().get(ScaleIOGatewayClient.SDC_GUID)); + } + } + + @Test + public void testUnprepareStorageClient_SDCServiceNotInstalled() { + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(4); + + Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid); + + Assert.assertTrue(result.first()); + Assert.assertEquals("SDC service not installed on host, no need to unprepare the SDC client", result.second()); + } + + @Test + public void testUnprepareStorageClient_SDCServiceNotEnabled() { + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(1); + + Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid); + + Assert.assertTrue(result.first()); + Assert.assertEquals("SDC service not enabled on host, no need to unprepare the SDC client", result.second()); + } + + @Test + public void testUnprepareStorageClient_SDCServiceNotStopped() { + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl stop scini"))).thenReturn(1); + + Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid); + + Assert.assertFalse(result.first()); + Assert.assertEquals("Couldn't stop SDC service on host", result.second()); + } + + @Test + public void testUnprepareStorageClient_SDCServiceStopped() { + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0); + when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl stop scini"))).thenReturn(0); + + Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid); + + Assert.assertTrue(result.first()); + } } diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java index 73b69bdef4f..fd2b93bc674 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java @@ -79,6 +79,7 @@ public interface ScaleIOGatewayClient { VolumeStatistics getVolumeStatistics(String volumeId); String getSystemId(String protectionDomainId); List<Volume> listVolumesInStoragePool(String poolId); + List<Volume> listVolumesMappedToSdc(String sdcId); // SDC APIs List<Sdc> listSdcs(); @@ -86,6 +87,7 @@ public interface ScaleIOGatewayClient { String getSdcIdByGuid(String sdcGuid); Sdc getSdcByIp(String ipAddress); Sdc getConnectedSdcByIp(String ipAddress); + int getConnectedSdcsCount(); boolean haveConnectedSdcs(); boolean isSdcConnected(String sdcId); boolean isSdcConnectedByIP(String ipAddress); diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java index fa428313943..2c044d8a0ce 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java @@ -1003,6 +1003,17 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { return new ArrayList<>(); } + @Override + public List<Volume> listVolumesMappedToSdc(String sdcId) { + Preconditions.checkArgument(StringUtils.isNotEmpty(sdcId), "SDC id cannot be null"); + + Volume[] volumes = get("/instances/Sdc::" + sdcId + "/relationships/Volume", Volume[].class); + if (volumes != null) { + return Arrays.asList(volumes); + } + return new ArrayList<>(); + } + /////////////////////////////////////////////// //////////////// SDC APIs ///////////////////// /////////////////////////////////////////////// @@ -1061,6 +1072,21 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { return null; } + @Override + public int getConnectedSdcsCount() { + List<Sdc> sdcs = listSdcs(); + int connectedSdcsCount = 0; + if(sdcs != null) { + for (Sdc sdc : sdcs) { + if (MDM_CONNECTED_STATE.equalsIgnoreCase(sdc.getMdmConnectionState())) { + connectedSdcsCount++; + } + } + } + + return connectedSdcsCount; + } + @Override public boolean haveConnectedSdcs() { List<Sdc> sdcs = listSdcs(); diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java index 529bd25ec8b..dec6ca00ab0 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java @@ -56,6 +56,8 @@ import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManager; +import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManagerImpl; import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil; import org.apache.cloudstack.storage.to.SnapshotObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; @@ -99,6 +101,7 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDetailsDao; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; +import com.cloud.utils.component.ComponentContext; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -141,9 +144,10 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { private VolumeService volumeService; @Inject private VolumeOrchestrationService volumeMgr; + private ScaleIOSDCManager sdcManager; public ScaleIOPrimaryDataStoreDriver() { - + sdcManager = new ScaleIOSDCManagerImpl(); } public ScaleIOGatewayClient getScaleIOClient(final Long storagePoolId) throws Exception { @@ -151,7 +155,8 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { } private boolean setVolumeLimitsOnSDC(VolumeVO volume, Host host, DataStore dataStore, Long iopsLimit, Long bandwidthLimitInKbps) throws Exception { - final String sdcId = getConnectedSdc(dataStore.getId(), host.getId()); + sdcManager = ComponentContext.inject(sdcManager); + final String sdcId = sdcManager.prepareSDC(host, dataStore); if (StringUtils.isBlank(sdcId)) { alertHostSdcDisconnection(host); throw new CloudRuntimeException("Unable to grant access to volume: " + volume.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress()); @@ -187,6 +192,13 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { @Override public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) { try { + sdcManager = ComponentContext.inject(sdcManager); + final String sdcId = sdcManager.prepareSDC(host, dataStore); + if (StringUtils.isBlank(sdcId)) { + alertHostSdcDisconnection(host); + throw new CloudRuntimeException(String.format("Unable to grant access to %s: %s, no Sdc connected with host ip: %s", dataObject.getType(), dataObject.getId(), host.getPrivateIpAddress())); + } + if (DataObjectType.VOLUME.equals(dataObject.getType())) { final VolumeVO volume = volumeDao.findById(dataObject.getId()); LOGGER.debug("Granting access for PowerFlex volume: " + volume.getPath()); @@ -194,25 +206,11 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { } else if (DataObjectType.TEMPLATE.equals(dataObject.getType())) { final VMTemplateStoragePoolVO templatePoolRef = vmTemplatePoolDao.findByPoolTemplate(dataStore.getId(), dataObject.getId(), null); LOGGER.debug("Granting access for PowerFlex template volume: " + templatePoolRef.getInstallPath()); - - final String sdcId = getConnectedSdc(dataStore.getId(), host.getId()); - if (StringUtils.isBlank(sdcId)) { - alertHostSdcDisconnection(host); - throw new CloudRuntimeException("Unable to grant access to template: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress()); - } - final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId()); return client.mapVolumeToSdc(ScaleIOUtil.getVolumePath(templatePoolRef.getInstallPath()), sdcId); } else if (DataObjectType.SNAPSHOT.equals(dataObject.getType())) { SnapshotInfo snapshot = (SnapshotInfo) dataObject; LOGGER.debug("Granting access for PowerFlex volume snapshot: " + snapshot.getPath()); - - final String sdcId = getConnectedSdc(dataStore.getId(), host.getId()); - if (StringUtils.isBlank(sdcId)) { - alertHostSdcDisconnection(host); - throw new CloudRuntimeException("Unable to grant access to snapshot: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress()); - } - final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId()); return client.mapVolumeToSdc(ScaleIOUtil.getVolumePath(snapshot.getPath()), sdcId); } @@ -236,40 +234,29 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { } try { + final String sdcId = getConnectedSdc(dataStore.getId(), host.getId()); + if (StringUtils.isBlank(sdcId)) { + LOGGER.warn(String.format("Unable to revoke access for %s: %s, no Sdc connected with host ip: %s", dataObject.getType(), dataObject.getId(), host.getPrivateIpAddress())); + return; + } + final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId()); if (DataObjectType.VOLUME.equals(dataObject.getType())) { final VolumeVO volume = volumeDao.findById(dataObject.getId()); LOGGER.debug("Revoking access for PowerFlex volume: " + volume.getPath()); - - final String sdcId = getConnectedSdc(dataStore.getId(), host.getId()); - if (StringUtils.isBlank(sdcId)) { - throw new CloudRuntimeException("Unable to revoke access for volume: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress()); - } - - final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId()); client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(volume.getPath()), sdcId); } else if (DataObjectType.TEMPLATE.equals(dataObject.getType())) { final VMTemplateStoragePoolVO templatePoolRef = vmTemplatePoolDao.findByPoolTemplate(dataStore.getId(), dataObject.getId(), null); LOGGER.debug("Revoking access for PowerFlex template volume: " + templatePoolRef.getInstallPath()); - - final String sdcId = getConnectedSdc(dataStore.getId(), host.getId()); - if (StringUtils.isBlank(sdcId)) { - throw new CloudRuntimeException("Unable to revoke access for template: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress()); - } - - final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId()); client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(templatePoolRef.getInstallPath()), sdcId); } else if (DataObjectType.SNAPSHOT.equals(dataObject.getType())) { SnapshotInfo snapshot = (SnapshotInfo) dataObject; LOGGER.debug("Revoking access for PowerFlex volume snapshot: " + snapshot.getPath()); - - final String sdcId = getConnectedSdc(dataStore.getId(), host.getId()); - if (StringUtils.isBlank(sdcId)) { - throw new CloudRuntimeException("Unable to revoke access for snapshot: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress()); - } - - final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId()); client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(snapshot.getPath()), sdcId); } + if (client.listVolumesMappedToSdc(sdcId).isEmpty()) { + sdcManager = ComponentContext.inject(sdcManager); + sdcManager.stopSDC(host, dataStore); + } } catch (Exception e) { LOGGER.warn("Failed to revoke access due to: " + e.getMessage(), e); } @@ -286,11 +273,16 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { final String sdcId = getConnectedSdc(dataStore.getId(), host.getId()); if (StringUtils.isBlank(sdcId)) { - throw new CloudRuntimeException("Unable to revoke access for volume: " + volumePath + ", no Sdc connected with host ip: " + host.getPrivateIpAddress()); + LOGGER.warn(String.format("Unable to revoke access for volume: %s, no Sdc connected with host ip: %s", volumePath, host.getPrivateIpAddress())); + return; } final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId()); client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(volumePath), sdcId); + if (client.listVolumesMappedToSdc(sdcId).isEmpty()) { + sdcManager = ComponentContext.inject(sdcManager); + sdcManager.stopSDC(host, dataStore); + } } catch (Exception e) { LOGGER.warn("Failed to revoke access due to: " + e.getMessage(), e); } @@ -1363,6 +1355,28 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { return true; } + @Override + public boolean poolProvidesCustomStorageStats() { + return true; + } + + @Override + public Map<String, String> getCustomStorageStats(StoragePool pool) { + Preconditions.checkArgument(pool != null, "pool cannot be null"); + Map<String, String> customStats = new HashMap<>(); + + try { + final ScaleIOGatewayClient client = getScaleIOClient(pool.getId()); + int connectedSdcsCount = client.getConnectedSdcsCount(); + customStats.put(ScaleIOUtil.CONNECTED_SDC_COUNT_STAT, String.valueOf(connectedSdcsCount)); + } catch (Exception e) { + String errMsg = "Unable to get custom storage stats for the pool: " + pool.getId() + " due to " + e.getMessage(); + LOGGER.error(errMsg); + } + + return customStats; + } + @Override public Pair<Long, Long> getStorageStats(StoragePool storagePool) { Preconditions.checkArgument(storagePool != null, "storagePool cannot be null"); @@ -1375,7 +1389,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { Long usedBytes = poolStatistics.getNetUsedCapacityInBytes(); return new Pair<Long, Long>(capacityBytes, usedBytes); } - } catch (Exception e) { + } catch (Exception e) { String errMsg = "Unable to get storage stats for the pool: " + storagePool.getId() + " due to " + e.getMessage(); LOGGER.warn(errMsg); throw new CloudRuntimeException(errMsg, e); @@ -1430,6 +1444,16 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { } } + @Override + public boolean canHostPrepareStoragePoolAccess(Host host, StoragePool pool) { + if (host == null || pool == null) { + return false; + } + + sdcManager = ComponentContext.inject(sdcManager); + return sdcManager.areSDCConnectionsWithinLimit(pool.getId()); + } + private void alertHostSdcDisconnection(Host host) { if (host == null) { return; diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java index 17150699923..2d7aca11f84 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java @@ -260,8 +260,6 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc throw new CloudRuntimeException("Unsupported hypervisor type: " + cluster.getHypervisorType().toString()); } - checkConnectedSdcs(dataStore.getId()); - PrimaryDataStoreInfo primaryDataStoreInfo = (PrimaryDataStoreInfo) dataStore; List<HostVO> hostsInCluster = resourceManager.listAllUpAndEnabledHosts(Host.Type.Routing, primaryDataStoreInfo.getClusterId(), primaryDataStoreInfo.getPodId(), primaryDataStoreInfo.getDataCenterId()); @@ -278,14 +276,12 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc poolHosts.add(host); } } catch (Exception e) { - LOGGER.warn("Unable to establish a connection between " + host + " and " + primaryDataStoreInfo, e); + LOGGER.warn("Unable to establish a connection between host: " + host + " and pool: " + dataStore + "on the cluster: " + primaryDataStoreInfo.getClusterId(), e); } } if (poolHosts.isEmpty()) { LOGGER.warn("No host can access storage pool '" + primaryDataStoreInfo + "' on cluster '" + primaryDataStoreInfo.getClusterId() + "'."); - primaryDataStoreDao.expunge(primaryDataStoreInfo.getId()); - throw new CloudRuntimeException("Failed to create storage pool in the cluster: " + primaryDataStoreInfo.getClusterId() + " as it is not accessible to hosts"); } dataStoreHelper.attachCluster(dataStore); @@ -303,8 +299,6 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc throw new CloudRuntimeException("Unsupported hypervisor type: " + hypervisorType.toString()); } - checkConnectedSdcs(dataStore.getId()); - LOGGER.debug("Attaching the pool to each of the hosts in the zone: " + scope.getScopeId()); List<HostVO> hosts = resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(hypervisorType, scope.getScopeId()); List<HostVO> poolHosts = new ArrayList<HostVO>(); @@ -314,35 +308,17 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc poolHosts.add(host); } } catch (Exception e) { - LOGGER.warn("Unable to establish a connection between " + host + " and " + dataStore, e); + LOGGER.warn("Unable to establish a connection between host: " + host + " and pool: " + dataStore + "in the zone: " + scope.getScopeId(), e); } } if (poolHosts.isEmpty()) { - LOGGER.warn("No host can access storage pool " + dataStore + " in this zone."); - primaryDataStoreDao.expunge(dataStore.getId()); - throw new CloudRuntimeException("Failed to create storage pool as it is not accessible to hosts."); + LOGGER.warn("No host can access storage pool " + dataStore + " in the zone: " + scope.getScopeId()); } dataStoreHelper.attachZone(dataStore); return true; } - private void checkConnectedSdcs(Long dataStoreId) { - boolean haveConnectedSdcs = false; - try { - ScaleIOGatewayClient client = ScaleIOGatewayClientConnectionPool.getInstance().getClient(dataStoreId, storagePoolDetailsDao); - haveConnectedSdcs = client.haveConnectedSdcs(); - } catch (NoSuchAlgorithmException | KeyManagementException | URISyntaxException e) { - LOGGER.error(String.format("Failed to create storage pool for datastore: %s", dataStoreId), e); - throw new CloudRuntimeException(String.format("Failed to establish connection with PowerFlex Gateway to create storage pool for datastore: %s", dataStoreId)); - } - - if (!haveConnectedSdcs) { - LOGGER.debug(String.format("No connected SDCs found for the PowerFlex storage pool of datastore: %s", dataStoreId)); - throw new CloudRuntimeException(String.format("Failed to create storage pool as connected SDCs not found for datastore: %s", dataStoreId)); - } - } - @Override public boolean maintain(DataStore store) { storagePoolAutomation.maintain(store); diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManager.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManager.java new file mode 100644 index 00000000000..696643cb17a --- /dev/null +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManager.java @@ -0,0 +1,47 @@ +// 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.manager; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; + +import com.cloud.host.Host; + +public interface ScaleIOSDCManager { + /** + * Checks SDC connections limit. + * @param storagePoolId the storage pool id + * @return true if SDC connections are within limit + */ + boolean areSDCConnectionsWithinLimit(Long storagePoolId); + + /** + * Prepares/starts the SDC on the host. + * @param host the host + * @param dataStore the datastore + * @return SDC Id of the host + */ + String prepareSDC(Host host, DataStore dataStore); + + /** + * Stops the SDC on the host. + * @param host the host + * @param dataStore the datastore + * @return true if SDC stopped on the host + */ + boolean stopSDC(Host host, DataStore dataStore); +} diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManagerImpl.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManagerImpl.java new file mode 100644 index 00000000000..b121a1da66f --- /dev/null +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManagerImpl.java @@ -0,0 +1,346 @@ +// 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.manager; + +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient; +import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnectionPool; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.PrepareStorageClientAnswer; +import com.cloud.agent.api.PrepareStorageClientCommand; +import com.cloud.agent.api.UnprepareStorageClientCommand; +import com.cloud.configuration.Config; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.Host; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePoolHostVO; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.db.GlobalLock; +import com.cloud.utils.exception.CloudRuntimeException; + +@Component +public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager { + private static final Logger LOGGER = Logger.getLogger(ScaleIOSDCManagerImpl.class); + + @Inject + AgentManager agentManager; + @Inject + StoragePoolHostDao storagePoolHostDao; + @Inject + StoragePoolDetailsDao storagePoolDetailsDao; + @Inject + ConfigurationDao configDao; + + private static final String POWERFLEX_SDC_HOSTID_SYSTEMID_LOCK_FORMAT = "PowerFlexSDC-HostId:%s-SystemId:%s"; + private static final String POWERFLEX_SDC_SYSTEMID_LOCK_FORMAT = "PowerFlexSDC-SystemId:%s"; + + public ScaleIOSDCManagerImpl() { + + } + + @Override + public boolean areSDCConnectionsWithinLimit(Long storagePoolId) { + try { + int connectedClientsLimit = StorageManager.STORAGE_POOL_CONNECTED_CLIENTS_LIMIT.valueIn(storagePoolId); + if (connectedClientsLimit <= 0) { + return true; + } + + int connectedSdcsCount = getScaleIOClient(storagePoolId).getConnectedSdcsCount(); + if (connectedSdcsCount < connectedClientsLimit) { + LOGGER.debug(String.format("Current connected SDCs count: %d - SDC connections are within the limit (%d) on PowerFlex Storage with pool id: %d", connectedSdcsCount, connectedClientsLimit, storagePoolId)); + return true; + } + LOGGER.debug(String.format("Current connected SDCs count: %d - SDC connections limit (%d) reached on PowerFlex Storage with pool id: %d", connectedSdcsCount, connectedClientsLimit, storagePoolId)); + return false; + } catch (Exception e) { + String errMsg = "Unable to check SDC connections for the PowerFlex storage pool with id: " + storagePoolId + " due to " + e.getMessage(); + LOGGER.warn(errMsg, e); + return false; + } + } + + @Override + public String prepareSDC(Host host, DataStore dataStore) { + String systemId = storagePoolDetailsDao.findDetail(dataStore.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue(); + if (systemId == null) { + throw new CloudRuntimeException("Unable to prepare SDC, failed to get the system id for PowerFlex storage pool: " + dataStore.getName()); + } + + GlobalLock hostIdStorageSystemIdLock = null; + GlobalLock storageSystemIdLock = null; + try { + String hostIdStorageSystemIdLockString = String.format(POWERFLEX_SDC_HOSTID_SYSTEMID_LOCK_FORMAT, host.getId(), systemId); + hostIdStorageSystemIdLock = GlobalLock.getInternLock(hostIdStorageSystemIdLockString); + if (hostIdStorageSystemIdLock == null) { + throw new CloudRuntimeException("Unable to prepare SDC, couldn't get global lock on " + hostIdStorageSystemIdLockString); + } + + int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600); + if (!hostIdStorageSystemIdLock.lock(storagePoolMaxWaitSeconds)) { + LOGGER.debug("Unable to prepare SDC, couldn't lock on " + hostIdStorageSystemIdLockString); + throw new CloudRuntimeException("Unable to prepare SDC, couldn't lock on " + hostIdStorageSystemIdLockString); + } + + long poolId = dataStore.getId(); + long hostId = host.getId(); + String sdcId = getConnectedSdc(poolId, hostId); + if (StringUtils.isNotBlank(sdcId)) { + LOGGER.debug(String.format("SDC %s already connected for the pool: %d on host: %d, no need to prepare/start it", sdcId, poolId, hostId)); + return sdcId; + } + + String storageSystemIdLockString = String.format(POWERFLEX_SDC_SYSTEMID_LOCK_FORMAT, systemId); + storageSystemIdLock = GlobalLock.getInternLock(storageSystemIdLockString); + if (storageSystemIdLock == null) { + LOGGER.error("Unable to prepare SDC, couldn't get global lock on: " + storageSystemIdLockString); + throw new CloudRuntimeException("Unable to prepare SDC, couldn't get global lock on " + storageSystemIdLockString); + } + + if (!storageSystemIdLock.lock(storagePoolMaxWaitSeconds)) { + LOGGER.error("Unable to prepare SDC, couldn't lock on " + storageSystemIdLockString); + throw new CloudRuntimeException("Unable to prepare SDC, couldn't lock on " + storageSystemIdLockString); + } + + if (!areSDCConnectionsWithinLimit(poolId)) { + String errorMsg = String.format("Unable to check SDC connections or the connections limit reached for Powerflex storage (System ID: %s)", systemId); + LOGGER.error(errorMsg); + throw new CloudRuntimeException(errorMsg); + } + + sdcId = prepareSDCOnHost(host, dataStore, systemId); + StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(poolId, hostId); + + if (StringUtils.isBlank(sdcId)) { + if (storagePoolHost != null) { + storagePoolHostDao.deleteStoragePoolHostDetails(hostId, poolId); + } + } else { + if (storagePoolHost == null) { + storagePoolHost = new StoragePoolHostVO(poolId, hostId, sdcId); + storagePoolHostDao.persist(storagePoolHost); + } else { + storagePoolHost.setLocalPath(sdcId); + storagePoolHostDao.update(storagePoolHost.getId(), storagePoolHost); + } + } + + int waitTimeInSecs = 15; // Wait for 15 secs (usual tests with SDC service start took 10-15 secs) + if (hostSdcConnected(sdcId, poolId, waitTimeInSecs)) { + return sdcId; + } + return null; + } finally { + if (storageSystemIdLock != null) { + storageSystemIdLock.unlock(); + storageSystemIdLock.releaseRef(); + } + if (hostIdStorageSystemIdLock != null) { + hostIdStorageSystemIdLock.unlock(); + hostIdStorageSystemIdLock.releaseRef(); + } + } + } + + private String prepareSDCOnHost(Host host, DataStore dataStore, String systemId) { + LOGGER.debug(String.format("Preparing SDC on the host %s (%s)", host.getId(), host.getName())); + Map<String,String> details = new HashMap<>(); + details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId); + PrepareStorageClientCommand cmd = new PrepareStorageClientCommand(((PrimaryDataStore) dataStore).getPoolType(), dataStore.getUuid(), details); + int timeoutSeconds = 60; + cmd.setWait(timeoutSeconds); + + PrepareStorageClientAnswer prepareStorageClientAnswer; + try { + prepareStorageClientAnswer = (PrepareStorageClientAnswer) agentManager.send(host.getId(), cmd); + } catch (AgentUnavailableException | OperationTimedoutException e) { + String err = String.format("Failed to prepare SDC on the host %s, due to: %s", host.getName(), e.getMessage()); + LOGGER.error(err); + throw new CloudRuntimeException(err); + } + + if (prepareStorageClientAnswer == null) { + String err = String.format("Unable to prepare SDC on the host %s", host.getName()); + LOGGER.error(err); + throw new CloudRuntimeException(err); + } + + if (!prepareStorageClientAnswer.getResult()) { + String err = String.format("Unable to prepare SDC on the host %s, due to: %s", host.getName(), prepareStorageClientAnswer.getDetails()); + LOGGER.error(err); + throw new CloudRuntimeException(err); + } + + Map<String,String> poolDetails = prepareStorageClientAnswer.getDetailsMap(); + if (MapUtils.isEmpty(poolDetails)) { + LOGGER.warn(String.format("PowerFlex storage SDC details not found on the host: %s, try (re)install SDC and restart agent", host.getId())); + return null; + } + + String sdcId = null; + if (poolDetails.containsKey(ScaleIOGatewayClient.SDC_ID)) { + sdcId = poolDetails.get(ScaleIOGatewayClient.SDC_ID); + } else if (poolDetails.containsKey(ScaleIOGatewayClient.SDC_GUID)) { + String sdcGuid = poolDetails.get(ScaleIOGatewayClient.SDC_GUID); + sdcId = getHostSdcId(sdcGuid, dataStore.getId()); + } + + if (StringUtils.isBlank(sdcId)) { + LOGGER.warn(String.format("Couldn't retrieve PowerFlex storage SDC details from the host: %s, try (re)install SDC and restart agent", host.getId())); + return null; + } + + return sdcId; + } + + @Override + public boolean stopSDC(Host host, DataStore dataStore) { + String systemId = storagePoolDetailsDao.findDetail(dataStore.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue(); + if (systemId == null) { + throw new CloudRuntimeException("Unable to unprepare SDC, failed to get the system id for PowerFlex storage pool: " + dataStore.getName()); + } + + GlobalLock lock = null; + try { + String hostIdStorageSystemIdLockString = String.format(POWERFLEX_SDC_HOSTID_SYSTEMID_LOCK_FORMAT, host.getId(), systemId); + lock = GlobalLock.getInternLock(hostIdStorageSystemIdLockString); + if (lock == null) { + throw new CloudRuntimeException("Unable to unprepare SDC, couldn't get global lock on " + hostIdStorageSystemIdLockString); + } + + int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600); + if (!lock.lock(storagePoolMaxWaitSeconds)) { + LOGGER.debug("Unable to unprepare SDC, couldn't lock on " + hostIdStorageSystemIdLockString); + throw new CloudRuntimeException("Unable to unprepare SDC, couldn't lock on " + hostIdStorageSystemIdLockString); + } + + long poolId = dataStore.getId(); + long hostId = host.getId(); + String sdcId = getConnectedSdc(poolId, hostId); + if (StringUtils.isBlank(sdcId)) { + LOGGER.debug("SDC not connected, no need to unprepare it"); + return true; + } + + return unprepareSDCOnHost(host, dataStore); + } finally { + if (lock != null) { + lock.unlock(); + lock.releaseRef(); + } + } + } + + private boolean unprepareSDCOnHost(Host host, DataStore dataStore) { + LOGGER.debug(String.format("Unpreparing SDC on the host %s (%s)", host.getId(), host.getName())); + UnprepareStorageClientCommand cmd = new UnprepareStorageClientCommand(((PrimaryDataStore) dataStore).getPoolType(), dataStore.getUuid()); + int timeoutSeconds = 60; + cmd.setWait(timeoutSeconds); + + Answer unprepareStorageClientAnswer; + try { + unprepareStorageClientAnswer = agentManager.send(host.getId(), cmd); + } catch (AgentUnavailableException | OperationTimedoutException e) { + String err = String.format("Failed to unprepare SDC on the host %s due to: %s", host.getName(), e.getMessage()); + LOGGER.error(err); + return false; + } + + if (!unprepareStorageClientAnswer.getResult()) { + String err = String.format("Unable to unprepare SDC on the the host %s due to: %s", host.getName(), unprepareStorageClientAnswer.getDetails()); + LOGGER.error(err); + return false; + } + return true; + } + + private String getHostSdcId(String sdcGuid, long poolId) { + try { + LOGGER.debug(String.format("Try to get host SDC Id for pool: %s, with SDC guid %s", poolId, sdcGuid)); + ScaleIOGatewayClient client = getScaleIOClient(poolId); + return client.getSdcIdByGuid(sdcGuid); + } catch (Exception e) { + LOGGER.error(String.format("Failed to get host SDC Id for pool: %s", poolId), e); + throw new CloudRuntimeException(String.format("Failed to establish connection with PowerFlex Gateway to get host SDC Id for pool: %s", poolId)); + } + } + + private String getConnectedSdc(long poolId, long hostId) { + try { + StoragePoolHostVO poolHostVO = storagePoolHostDao.findByPoolHost(poolId, hostId); + if (poolHostVO == null) { + return null; + } + + final ScaleIOGatewayClient client = getScaleIOClient(poolId); + if (client.isSdcConnected(poolHostVO.getLocalPath())) { + return poolHostVO.getLocalPath(); + } + } catch (Exception e) { + LOGGER.warn("Unable to get connected SDC for the host: " + hostId + " and storage pool: " + poolId + " due to " + e.getMessage(), e); + } + + return null; + } + + private boolean hostSdcConnected(String sdcId, long poolId, int waitTimeInSecs) { + LOGGER.debug(String.format("Waiting (for %d secs) for the SDC %s of the pool id: %d to connect", waitTimeInSecs, sdcId, poolId)); + int timeBetweenTries = 1000; // Try more frequently (every sec) and return early if connected + while (waitTimeInSecs > 0) { + if (isHostSdcConnected(sdcId, poolId)) { + return true; + } + waitTimeInSecs--; + try { + Thread.sleep(timeBetweenTries); + } catch (Exception ignore) { + } + } + return isHostSdcConnected(sdcId, poolId); + } + + private boolean isHostSdcConnected(String sdcId, long poolId) { + try { + final ScaleIOGatewayClient client = getScaleIOClient(poolId); + return client.isSdcConnected(sdcId); + } catch (Exception e) { + LOGGER.error("Failed to check host SDC connection", e); + throw new CloudRuntimeException("Failed to establish connection with PowerFlex Gateway to check host SDC connection"); + } + } + + private ScaleIOGatewayClient getScaleIOClient(final Long storagePoolId) throws Exception { + return ScaleIOGatewayClientConnectionPool.getInstance().getClient(storagePoolId, storagePoolDetailsDao); + } +} diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java index bb269e85a95..f812ed8cce2 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java @@ -69,12 +69,33 @@ public class ScaleIOHostListener implements HypervisorHostListener { public boolean hostConnect(long hostId, long poolId) { HostVO host = _hostDao.findById(hostId); if (host == null) { - s_logger.error("Failed to add host by HostListener as host was not found with id : " + hostId); + s_logger.error("Failed to connect host by HostListener as host was not found with id : " + hostId); return false; } StoragePool storagePool = (StoragePool)_dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary); + StoragePoolHostVO storagePoolHost = _storagePoolHostDao.findByPoolHost(poolId, hostId); + String sdcId = getSdcIdOfHost(host, storagePool); + if (StringUtils.isBlank(sdcId)) { + if (storagePoolHost != null) { + _storagePoolHostDao.deleteStoragePoolHostDetails(hostId, poolId); + } + } else { + if (storagePoolHost == null) { + storagePoolHost = new StoragePoolHostVO(poolId, hostId, sdcId); + _storagePoolHostDao.persist(storagePoolHost); + } else { + storagePoolHost.setLocalPath(sdcId); + _storagePoolHostDao.update(storagePoolHost.getId(), storagePoolHost); + } + s_logger.info("Connection established between storage pool: " + storagePool + " and host: " + hostId); + } + return true; + } + private String getSdcIdOfHost(HostVO host, StoragePool storagePool) { + long hostId = host.getId(); + long poolId = storagePool.getId(); String systemId = _storagePoolDetailsDao.findDetail(poolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue(); if (systemId == null) { throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool " + storagePool.getName()); @@ -86,10 +107,10 @@ public class ScaleIOHostListener implements HypervisorHostListener { ModifyStoragePoolAnswer answer = sendModifyStoragePoolCommand(cmd, storagePool, hostId); Map<String,String> poolDetails = answer.getPoolInfo().getDetails(); if (MapUtils.isEmpty(poolDetails)) { - String msg = "SDC details not found on the host: " + hostId + ", (re)install SDC and restart agent"; + String msg = "PowerFlex storage SDC details not found on the host: " + hostId + ", (re)install SDC and restart agent"; s_logger.warn(msg); _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC not found on host: " + host.getUuid(), msg); - return false; + return null; } String sdcId = null; @@ -101,30 +122,13 @@ public class ScaleIOHostListener implements HypervisorHostListener { } if (StringUtils.isBlank(sdcId)) { - String msg = "Couldn't retrieve SDC details from the host: " + hostId + ", (re)install SDC and restart agent"; + String msg = "Couldn't retrieve PowerFlex storage SDC details from the host: " + hostId + ", (re)install SDC and restart agent"; s_logger.warn(msg); _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC details not found on host: " + host.getUuid(), msg); - return false; - } - - if (!isHostSdcConnected(sdcId, poolId)) { - s_logger.warn("SDC not connected on the host: " + hostId); - String msg = "SDC not connected on the host: " + hostId + ", reconnect the SDC to MDM and restart agent"; - _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC disconnected on host: " + host.getUuid(), msg); - return false; + return null; } - StoragePoolHostVO storagePoolHost = _storagePoolHostDao.findByPoolHost(poolId, hostId); - if (storagePoolHost == null) { - storagePoolHost = new StoragePoolHostVO(poolId, hostId, sdcId); - _storagePoolHostDao.persist(storagePoolHost); - } else { - storagePoolHost.setLocalPath(sdcId); - _storagePoolHostDao.update(storagePoolHost.getId(), storagePoolHost); - } - - s_logger.info("Connection established between storage pool: " + storagePool + " and host: " + hostId); - return true; + return sdcId; } private String getHostSdcId(String sdcGuid, long poolId) { @@ -138,16 +142,6 @@ public class ScaleIOHostListener implements HypervisorHostListener { } } - private boolean isHostSdcConnected(String sdcId, long poolId) { - try { - ScaleIOGatewayClient client = ScaleIOGatewayClientConnectionPool.getInstance().getClient(poolId, _storagePoolDetailsDao); - return client.isSdcConnected(sdcId); - } catch (NoSuchAlgorithmException | KeyManagementException | URISyntaxException e) { - s_logger.error("Failed to check host sdc connection", e); - throw new CloudRuntimeException("Failed to establish connection with PowerFlex Gateway to check host sdc connection"); - } - } - private ModifyStoragePoolAnswer sendModifyStoragePoolCommand(ModifyStoragePoolCommand cmd, StoragePool storagePool, long hostId) { Answer answer = _agentMgr.easySend(hostId, cmd); @@ -156,15 +150,15 @@ public class ScaleIOHostListener implements HypervisorHostListener { } if (!answer.getResult()) { - String msg = "Unable to attach storage pool " + storagePool.getId() + " to host " + hostId; + String msg = "Unable to attach PowerFlex storage pool " + storagePool.getId() + " to host " + hostId; _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, storagePool.getDataCenterId(), storagePool.getPodId(), msg, msg); - throw new CloudRuntimeException("Unable to establish a connection from agent to storage pool " + storagePool.getId() + " due to " + answer.getDetails() + + throw new CloudRuntimeException("Unable to establish a connection from agent to PowerFlex storage pool " + storagePool.getId() + " due to " + answer.getDetails() + " (" + storagePool.getId() + ")"); } - assert (answer instanceof ModifyStoragePoolAnswer) : "ModifyStoragePoolAnswer expected ; Pool = " + storagePool.getId() + " Host = " + hostId; + assert (answer instanceof ModifyStoragePoolAnswer) : "ModifyStoragePoolAnswer expected ; PowerFlex Storage Pool = " + storagePool.getId() + " Host = " + hostId; return (ModifyStoragePoolAnswer) answer; } diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/util/ScaleIOUtil.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/util/ScaleIOUtil.java index 736a43df691..e7b06267a51 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/util/ScaleIOUtil.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/util/ScaleIOUtil.java @@ -49,6 +49,16 @@ public class ScaleIOUtil { private static final String RESCAN_CMD = "drv_cfg --rescan"; + private static final String SDC_SERVICE_STATUS_CMD = "systemctl status scini"; + private static final String SDC_SERVICE_START_CMD = "systemctl start scini"; + private static final String SDC_SERVICE_STOP_CMD = "systemctl stop scini"; + private static final String SDC_SERVICE_RESTART_CMD = "systemctl restart scini"; + + private static final String SDC_SERVICE_IS_ACTIVE_CMD = "systemctl is-active scini"; + private static final String SDC_SERVICE_IS_ENABLED_CMD = "systemctl is-enabled scini"; + private static final String SDC_SERVICE_ENABLE_CMD = "systemctl enable scini"; + + public static final String CONNECTED_SDC_COUNT_STAT = "ConnectedSDCCount"; /** * Cmd for querying volumes in SDC * Sample output for cmd: drv_cfg --query_vols: @@ -182,4 +192,39 @@ public class ScaleIOUtil { return String.format("%s:%s", volumePath, volumeName); } + + public static boolean isSDCServiceInstalled() { + int exitValue = Script.runSimpleBashScriptForExitValue(SDC_SERVICE_STATUS_CMD); + return exitValue != 4; + } + + public static boolean isSDCServiceActive() { + int exitValue = Script.runSimpleBashScriptForExitValue(SDC_SERVICE_IS_ACTIVE_CMD); + return exitValue == 0; + } + + public static boolean isSDCServiceEnabled() { + int exitValue = Script.runSimpleBashScriptForExitValue(SDC_SERVICE_IS_ENABLED_CMD); + return exitValue == 0; + } + + public static boolean enableSDCService() { + int exitValue = Script.runSimpleBashScriptForExitValue(SDC_SERVICE_ENABLE_CMD); + return exitValue == 0; + } + + public static boolean startSDCService() { + int exitValue = Script.runSimpleBashScriptForExitValue(SDC_SERVICE_START_CMD); + return exitValue == 0; + } + + public static boolean stopSDCService() { + int exitValue = Script.runSimpleBashScriptForExitValue(SDC_SERVICE_STOP_CMD); + return exitValue == 0; + } + + public static boolean restartSDCService() { + int exitValue = Script.runSimpleBashScriptForExitValue(SDC_SERVICE_RESTART_CMD); + return exitValue == 0; + } } diff --git a/plugins/storage/volume/scaleio/src/main/resources/META-INF/cloudstack/storage-volume-scaleio/spring-storage-volume-scaleio-context.xml b/plugins/storage/volume/scaleio/src/main/resources/META-INF/cloudstack/storage-volume-scaleio/spring-storage-volume-scaleio-context.xml index 8b86e212e29..55e74cddd6f 100755 --- a/plugins/storage/volume/scaleio/src/main/resources/META-INF/cloudstack/storage-volume-scaleio/spring-storage-volume-scaleio-context.xml +++ b/plugins/storage/volume/scaleio/src/main/resources/META-INF/cloudstack/storage-volume-scaleio/spring-storage-volume-scaleio-context.xml @@ -32,4 +32,6 @@ <bean id="scaleioDataStoreProvider" class="org.apache.cloudstack.storage.datastore.provider.ScaleIOPrimaryDatastoreProvider" /> + <bean id="scaleioSDCManager" class="org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManagerImpl" /> + </beans> diff --git a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java index 4a6e73a327d..e2f850be7ff 100644 --- a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java +++ b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; @@ -123,9 +124,9 @@ public class ScaleIOPrimaryDataStoreLifeCycleTest { ScaleIOGatewayClientImpl client = mock(ScaleIOGatewayClientImpl.class); ScaleIOGatewayClientConnectionPool pool = mock(ScaleIOGatewayClientConnectionPool.class); scaleIOGatewayClientConnectionPoolMocked.when(() -> ScaleIOGatewayClientConnectionPool.getInstance()).thenReturn(pool); - when(pool.getClient(1L, storagePoolDetailsDao)).thenReturn(client); + lenient().when(pool.getClient(1L, storagePoolDetailsDao)).thenReturn(client); - when(client.haveConnectedSdcs()).thenReturn(true); + lenient().when(client.haveConnectedSdcs()).thenReturn(true); final ZoneScope scope = new ZoneScope(1L); diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index c45e60f8dd5..0d9447d2dd5 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -2027,8 +2027,8 @@ public class ApiDBUtils { return s_volJoinDao.newVolumeView(vr); } - public static StoragePoolResponse newStoragePoolResponse(StoragePoolJoinVO vr) { - return s_poolJoinDao.newStoragePoolResponse(vr); + public static StoragePoolResponse newStoragePoolResponse(StoragePoolJoinVO vr, boolean customStats) { + return s_poolJoinDao.newStoragePoolResponse(vr, customStats); } public static StorageTagResponse newStorageTagResponse(StoragePoolTagVO vr) { diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index f482b595b4e..e801d1f9b31 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -1439,7 +1439,7 @@ public class ApiResponseHelper implements ResponseGenerator { @Override public StoragePoolResponse createStoragePoolResponse(StoragePool pool) { List<StoragePoolJoinVO> viewPools = ApiDBUtils.newStoragePoolView(pool); - List<StoragePoolResponse> listPools = ViewResponseHelper.createStoragePoolResponse(viewPools.toArray(new StoragePoolJoinVO[viewPools.size()])); + List<StoragePoolResponse> listPools = ViewResponseHelper.createStoragePoolResponse(false, viewPools.toArray(new StoragePoolJoinVO[viewPools.size()])); assert listPools != null && listPools.size() == 1 : "There should be one storage pool returned"; return listPools.get(0); } diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index a3a1a16b160..698c3d7fa33 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -2970,7 +2970,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q public ListResponse<StoragePoolResponse> searchForStoragePools(ListStoragePoolsCmd cmd) { Pair<List<StoragePoolJoinVO>, Integer> result = (ScopeType.HOST.name().equalsIgnoreCase(cmd.getScope()) && cmd.getHostId() != null) ? searchForLocalStorages(cmd) : searchForStoragePoolsInternal(cmd); - return createStoragesPoolResponse(result); + return createStoragesPoolResponse(result, cmd.getCustomStats()); } private Pair<List<StoragePoolJoinVO>, Integer> searchForLocalStorages(ListStoragePoolsCmd cmd) { @@ -2998,10 +2998,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q } } - private ListResponse<StoragePoolResponse> createStoragesPoolResponse(Pair<List<StoragePoolJoinVO>, Integer> storagePools) { + private ListResponse<StoragePoolResponse> createStoragesPoolResponse(Pair<List<StoragePoolJoinVO>, Integer> storagePools, boolean getCustomStats) { ListResponse<StoragePoolResponse> response = new ListResponse<>(); - List<StoragePoolResponse> poolResponses = ViewResponseHelper.createStoragePoolResponse(storagePools.first().toArray(new StoragePoolJoinVO[storagePools.first().size()])); + List<StoragePoolResponse> poolResponses = ViewResponseHelper.createStoragePoolResponse(getCustomStats, storagePools.first().toArray(new StoragePoolJoinVO[storagePools.first().size()])); Map<String, Long> poolUuidToIdMap = storagePools.first().stream().collect(Collectors.toMap(StoragePoolJoinVO::getUuid, StoragePoolJoinVO::getId, (a, b) -> a)); for (StoragePoolResponse poolResponse : poolResponses) { DataStore store = dataStoreManager.getPrimaryDataStore(poolResponse.getId()); diff --git a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java index 934de8a2558..730a6628491 100644 --- a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java +++ b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java @@ -312,14 +312,14 @@ public class ViewResponseHelper { return new ArrayList<VolumeResponse>(vrDataList.values()); } - public static List<StoragePoolResponse> createStoragePoolResponse(StoragePoolJoinVO... pools) { + public static List<StoragePoolResponse> createStoragePoolResponse(boolean customStats, StoragePoolJoinVO... pools) { LinkedHashMap<Long, StoragePoolResponse> vrDataList = new LinkedHashMap<>(); // Initialise the vrdatalist with the input data for (StoragePoolJoinVO vr : pools) { StoragePoolResponse vrData = vrDataList.get(vr.getId()); if (vrData == null) { // first time encountering this vm - vrData = ApiDBUtils.newStoragePoolResponse(vr); + vrData = ApiDBUtils.newStoragePoolResponse(vr, customStats); } else { // update tags vrData = ApiDBUtils.fillStoragePoolDetails(vrData, vr); diff --git a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDao.java index 26ee3f01789..6e0b59492c0 100644 --- a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDao.java +++ b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDao.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; public interface StoragePoolJoinDao extends GenericDao<StoragePoolJoinVO, Long> { - StoragePoolResponse newStoragePoolResponse(StoragePoolJoinVO host); + StoragePoolResponse newStoragePoolResponse(StoragePoolJoinVO host, boolean customStats); StoragePoolResponse setStoragePoolResponse(StoragePoolResponse response, StoragePoolJoinVO host); diff --git a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java index f3b832d1042..8c828ba2067 100644 --- a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java @@ -42,6 +42,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.collections.MapUtils; import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -49,6 +50,7 @@ import org.springframework.stereotype.Component; import javax.inject.Inject; import java.util.ArrayList; import java.util.List; +import java.util.Map; @Component public class StoragePoolJoinDaoImpl extends GenericDaoBase<StoragePoolJoinVO, Long> implements StoragePoolJoinDao { @@ -100,7 +102,7 @@ public class StoragePoolJoinDaoImpl extends GenericDaoBase<StoragePoolJoinVO, Lo } @Override - public StoragePoolResponse newStoragePoolResponse(StoragePoolJoinVO pool) { + public StoragePoolResponse newStoragePoolResponse(StoragePoolJoinVO pool, boolean customStats) { StoragePool storagePool = storagePoolDao.findById(pool.getId()); StoragePoolResponse poolResponse = new StoragePoolResponse(); poolResponse.setId(pool.getUuid()); @@ -147,6 +149,13 @@ public class StoragePoolJoinDaoImpl extends GenericDaoBase<StoragePoolJoinVO, Lo PrimaryDataStoreDriver driver = (PrimaryDataStoreDriver) store.getDriver(); long usedIops = driver.getUsedIops(storagePool); poolResponse.setAllocatedIops(usedIops); + + if (customStats && driver.poolProvidesCustomStorageStats()) { + Map<String, String> storageCustomStats = driver.getCustomStorageStats(storagePool); + if (MapUtils.isNotEmpty(storageCustomStats)) { + poolResponse.setCustomStats(storageCustomStats); + } + } } // TODO: StatsCollector does not persist data diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 14667c2d47d..1f1a9c22358 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -1618,6 +1618,15 @@ StateListener<State, VirtualMachine.Event, VirtualMachine>, Configurable { } s_logger.debug("Host: " + host.getId() + (hostCanAccessSPool ? " can" : " cannot") + " access pool: " + pool.getId()); + if (!hostCanAccessSPool) { + if (_storageMgr.canHostPrepareStoragePoolAccess(host, pool)) { + s_logger.debug("Host: " + host.getId() + " can prepare access to pool: " + pool.getId()); + hostCanAccessSPool = true; + } else { + s_logger.debug("Host: " + host.getId() + " cannot prepare access to pool: " + pool.getId()); + } + } + return hostCanAccessSPool; } diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index a5fd7842964..de3ec02dc7a 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -573,6 +573,31 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C return storeDriver instanceof PrimaryDataStoreDriver && ((PrimaryDataStoreDriver)storeDriver).canProvideStorageStats(); } + @Override + public boolean poolProvidesCustomStorageStats(StoragePool pool) { + DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName()); + DataStoreDriver storeDriver = storeProvider.getDataStoreDriver(); + return storeDriver instanceof PrimaryDataStoreDriver && ((PrimaryDataStoreDriver)storeDriver).poolProvidesCustomStorageStats(); + } + + @Override + public Map<String, String> getCustomStorageStats(StoragePool pool) { + if (pool == null) { + return null; + } + + if (!pool.isManaged()) { + return null; + } + + DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName()); + DataStoreDriver storeDriver = storeProvider.getDataStoreDriver(); + if (storeDriver instanceof PrimaryDataStoreDriver) { + return ((PrimaryDataStoreDriver)storeDriver).getCustomStorageStats(pool); + } + return null; + } + @Override public Answer getVolumeStats(StoragePool pool, Command cmd) { DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName()); @@ -2649,6 +2674,21 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C return false; } + @Override + public boolean canHostPrepareStoragePoolAccess(Host host, StoragePool pool) { + if (host == null || pool == null) { + return false; + } + + if (!pool.isManaged()) { + return true; + } + + DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName()); + DataStoreDriver storeDriver = storeProvider.getDataStoreDriver(); + return storeDriver instanceof PrimaryDataStoreDriver && ((PrimaryDataStoreDriver)storeDriver).canHostPrepareStoragePoolAccess(host, pool); + } + @Override @DB public Host getHost(long hostId) { @@ -3824,6 +3864,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C STORAGE_POOL_DISK_WAIT, STORAGE_POOL_CLIENT_TIMEOUT, STORAGE_POOL_CLIENT_MAX_CONNECTIONS, + STORAGE_POOL_CONNECTED_CLIENTS_LIMIT, STORAGE_POOL_IO_POLICY, PRIMARY_STORAGE_DOWNLOAD_WAIT, SecStorageMaxMigrateSessions,