diff --git a/api/src/main/java/com/cloud/agent/api/StoragePoolInfo.java b/api/src/main/java/com/cloud/agent/api/StoragePoolInfo.java index d923694a854d..eb7e8813ecda 100644 --- a/api/src/main/java/com/cloud/agent/api/StoragePoolInfo.java +++ b/api/src/main/java/com/cloud/agent/api/StoragePoolInfo.java @@ -52,6 +52,13 @@ public StoragePoolInfo(String uuid, String host, String hostPath, String localPa this.details = details; } + public StoragePoolInfo(String uuid, String host, String hostPath, String localPath, StoragePoolType poolType, long capacityBytes, long availableBytes, + Map details, String name) { + this(uuid, host, hostPath, localPath, poolType, capacityBytes, availableBytes); + this.details = details; + this.name = name; + } + public long getCapacityBytes() { return capacityBytes; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java index 75fe339b7106..b8668f61ca46 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java @@ -119,6 +119,10 @@ public Long getId() { return id; } + public void setId(Long id) { + this.id = id; + } + public String getHostName() { return hostName; } 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 209aaac279c4..6923353b3bf5 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 @@ -24,6 +24,7 @@ import org.apache.cloudstack.api.BaseListCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; @@ -63,16 +64,25 @@ public class ListStoragePoolsCmd extends BaseListCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "the ID of the storage pool") private Long id; - @Parameter(name = ApiConstants.SCOPE, type = CommandType.STRING, entityType = StoragePoolResponse.class, description = "the ID of the storage pool") + @Parameter(name = ApiConstants.SCOPE, type = CommandType.STRING, entityType = StoragePoolResponse.class, description = "the scope of the storage pool") private String scope; + @Parameter(name = ApiConstants.STATUS, type = CommandType.STRING, description = "the status of the storage pool") private String status; + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "host ID of the storage pools") + private Long hostId; + + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// + public Long getHostId() { + return hostId; + } + public Long getClusterId() { return clusterId; } @@ -81,6 +91,10 @@ public String getIpAddress() { return ipAddress; } + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + public String getStoragePoolName() { return storagePoolName; } @@ -108,6 +122,15 @@ public Long getId() { public void setId(Long id) { this.id = id; } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -123,8 +146,4 @@ public void execute() { response.setResponseName(getCommandName()); this.setResponseObject(response); } - - public String getScope() { - return scope; - } } diff --git a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java index 213e5620553f..6e178c119e3f 100644 --- a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java +++ b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java @@ -27,6 +27,7 @@ import com.cloud.alert.AlertManager; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.StorageConflictException; +import com.cloud.exception.StorageUnavailableException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; @@ -44,7 +45,6 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.NumbersUtil; -import com.cloud.utils.UriUtils; import com.cloud.utils.db.DB; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachineManager; @@ -67,10 +67,6 @@ import org.apache.log4j.Logger; import javax.inject.Inject; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -138,64 +134,26 @@ public DataStore initialize(Map dsInfos) { PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters(); - UriUtils.UriInfo uriInfo = UriUtils.getUriInfo(url); - - String scheme = uriInfo.getScheme(); - String storageHost = uriInfo.getStorageHost(); - String storagePath = uriInfo.getStoragePath(); - try { - if (scheme == null) { - throw new InvalidParameterValueException("scheme is null " + url + ", add nfs:// (or cifs://) as a prefix"); - } else if (scheme.equalsIgnoreCase("nfs")) { - if (storageHost == null || storagePath == null || storageHost.trim().isEmpty() || storagePath.trim().isEmpty()) { - throw new InvalidParameterValueException("host or path is null, should be nfs://hostname/path"); - } - } else if (scheme.equalsIgnoreCase("cifs")) { - // Don't validate against a URI encoded URI. - URI cifsUri = new URI(url); - String warnMsg = UriUtils.getCifsUriParametersProblems(cifsUri); - if (warnMsg != null) { - throw new InvalidParameterValueException(warnMsg); - } - } else if (scheme.equalsIgnoreCase("sharedMountPoint")) { - if (storagePath == null) { - throw new InvalidParameterValueException("host or path is null, should be sharedmountpoint://localhost/path"); - } - } else if (scheme.equalsIgnoreCase("rbd")) { - if (storagePath == null) { - throw new InvalidParameterValueException("host or path is null, should be rbd://hostname/pool"); - } - } else if (scheme.equalsIgnoreCase("gluster")) { - if (storageHost == null || storagePath == null || storageHost.trim().isEmpty() || storagePath.trim().isEmpty()) { - throw new InvalidParameterValueException("host or path is null, should be gluster://hostname/volume"); - } - } - } catch (URISyntaxException e) { - throw new InvalidParameterValueException(url + " is not a valid uri"); - } - String tags = (String)dsInfos.get("tags"); Map details = (Map)dsInfos.get("details"); parameters.setTags(tags); parameters.setDetails(details); - String hostPath = null; - try { - hostPath = URLDecoder.decode(storagePath, "UTF-8"); - } catch (UnsupportedEncodingException e) { - s_logger.error("[ignored] we are on a platform not supporting \"UTF-8\"!?!", e); - } - if (hostPath == null) { // if decoding fails, use getPath() anyway - hostPath = storagePath; - } + String scheme = dsInfos.get("scheme").toString(); + String storageHost = dsInfos.get("host").toString(); + String hostPath = dsInfos.get("hostPath").toString(); + String uri = String.format("%s://%s%s", scheme, storageHost, hostPath); + Object localStorage = dsInfos.get("localStorage"); - if (localStorage != null) { - hostPath = hostPath.replaceFirst("/", ""); + if (localStorage != null) { + hostPath = hostPath.contains("//") ? hostPath.replaceFirst("/", "") : hostPath; hostPath = hostPath.replace("+", " "); } - String userInfo = uriInfo.getUserInfo(); - int port = uriInfo.getPort(); + + String userInfo = dsInfos.get("userInfo") != null ? dsInfos.get("userInfo").toString() : null; + int port = dsInfos.get("port") != null ? Integer.parseInt(dsInfos.get("port").toString()) : -1; + if (s_logger.isDebugEnabled()) { s_logger.debug("createPool Params @ scheme - " + scheme + " storageHost - " + storageHost + " hostPath - " + hostPath + " port - " + port); } @@ -312,8 +270,8 @@ public DataStore initialize(Map dsInfos) { parameters.setPort(0); parameters.setPath(hostPath); } else { - s_logger.warn("Unable to figure out the scheme for URI: " + uriInfo); - throw new IllegalArgumentException("Unable to figure out the scheme for URI: " + uriInfo); + s_logger.warn("Unable to figure out the scheme for URI: " + scheme); + throw new IllegalArgumentException("Unable to figure out the scheme for URI: " + scheme); } } @@ -321,7 +279,7 @@ public DataStore initialize(Map dsInfos) { List pools = primaryDataStoreDao.listPoolByHostPath(storageHost, hostPath); if (!pools.isEmpty() && !scheme.equalsIgnoreCase("sharedmountpoint")) { Long oldPodId = pools.get(0).getPodId(); - throw new CloudRuntimeException("Storage pool " + uriInfo + " already in use by another pod (id=" + oldPodId + ")"); + throw new CloudRuntimeException("Storage pool " + hostPath + " already in use by another pod (id=" + oldPodId + ")"); } } @@ -550,7 +508,16 @@ private HypervisorType getHypervisorType(long hostId) { @Override public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) { - dataStoreHelper.attachHost(store, scope, existingInfo); + DataStore dataStore = dataStoreHelper.attachHost(store, scope, existingInfo); + if(existingInfo.getCapacityBytes() == 0){ + try { + storageMgr.connectHostToSharedPool(scope.getScopeId(), dataStore.getId()); + } catch (StorageUnavailableException ex) { + s_logger.error("Storage unavailable ",ex); + } catch (StorageConflictException ex) { + s_logger.error("Storage already exists ",ex); + } + } return true; } 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 c99aa2932341..8a415845045e 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -2784,10 +2784,29 @@ private Pair, Integer> searchForAsyncJobsInternal(ListAsync @Override public ListResponse searchForStoragePools(ListStoragePoolsCmd cmd) { - Pair, Integer> result = searchForStoragePoolsInternal(cmd); - ListResponse response = new ListResponse(); + Pair, Integer> result = cmd.getHostId() != null ? searchForLocalStorages(cmd) : searchForStoragePoolsInternal(cmd); + return createStoragesPoolResponse(result); + } + + private Pair, Integer> searchForLocalStorages(ListStoragePoolsCmd cmd) { + long id = cmd.getHostId(); + String scope = ScopeType.HOST.toString(); + Pair, Integer> localStorages; + + ListHostsCmd listHostsCmd = new ListHostsCmd(); + listHostsCmd.setId(id); + Pair, Integer> hosts = searchForServersInternal(listHostsCmd); + + cmd.setScope(scope); + localStorages = searchForStoragePoolsInternal(cmd); - List poolResponses = ViewResponseHelper.createStoragePoolResponse(result.first().toArray(new StoragePoolJoinVO[result.first().size()])); + return localStorages; + } + + private ListResponse createStoragesPoolResponse(Pair, Integer> storagePools) { + ListResponse response = new ListResponse<>(); + + List poolResponses = ViewResponseHelper.createStoragePoolResponse(storagePools.first().toArray(new StoragePoolJoinVO[storagePools.first().size()])); for (StoragePoolResponse poolResponse : poolResponses) { DataStore store = dataStoreManager.getPrimaryDataStore(poolResponse.getId()); if (store != null) { @@ -2807,7 +2826,7 @@ public ListResponse searchForStoragePools(ListStoragePoolsC } } - response.setResponses(poolResponses, result.second()); + response.setResponses(poolResponses, storagePools.second()); return response; } diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 82a6c159565a..494855b253d8 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -237,6 +237,9 @@ import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.VMInstanceDao; import com.google.common.collect.Sets; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + @Component public class StorageManagerImpl extends ManagerBase implements StorageManager, ClusterManagerListener, Configurable { @@ -653,6 +656,41 @@ public boolean stop() { return true; } + private DataStore createLocalStorage(Map poolInfos) throws ConnectionException{ + Object existingUuid = poolInfos.get("uuid"); + if( existingUuid == null ){ + poolInfos.put("uuid", UUID.randomUUID().toString()); + } + String hostAddress = poolInfos.get("host").toString(); + Host host = _hostDao.findByName(hostAddress); + + if( host == null ) { + host = _hostDao.findByIp(hostAddress); + + if( host == null ) { + host = _hostDao.findByPublicIp(hostAddress); + + if( host == null ) { + throw new InvalidParameterValueException(String.format("host %s not found",hostAddress)); + } + } + } + + long capacityBytes = poolInfos.get("capacityBytes") != null ? Long.parseLong(poolInfos.get("capacityBytes").toString()) : 0; + + StoragePoolInfo pInfo = new StoragePoolInfo(poolInfos.get("uuid").toString(), + host.getPrivateIpAddress(), + poolInfos.get("hostPath").toString(), + poolInfos.get("hostPath").toString(), + StoragePoolType.Filesystem, + capacityBytes, + 0, + (Map)poolInfos.get("details"), + poolInfos.get("name").toString()); + + return createLocalStorage(host, pInfo); + } + @DB @Override public DataStore createLocalStorage(Host host, StoragePoolInfo pInfo) throws ConnectionException { @@ -698,17 +736,19 @@ public DataStore createLocalStorage(Host host, StoragePoolInfo pInfo) throws Con DataStoreLifeCycle lifeCycle = provider.getDataStoreLifeCycle(); if (pool == null) { Map params = new HashMap(); - String name = createLocalStoragePoolName(host, pInfo); + String name = pInfo.getName() != null ? pInfo.getName() : createLocalStoragePoolName(host, pInfo); params.put("zoneId", host.getDataCenterId()); params.put("clusterId", host.getClusterId()); params.put("podId", host.getPodId()); params.put("hypervisorType", host.getHypervisorType()); - params.put("url", pInfo.getPoolType().toString() + "://" + pInfo.getHost() + "/" + pInfo.getHostPath()); params.put("name", name); params.put("localStorage", true); params.put("details", pInfo.getDetails()); params.put("uuid", pInfo.getUuid()); params.put("providerName", provider.getName()); + params.put("scheme", pInfo.getPoolType().toString()); + params.put("host", pInfo.getHost()); + params.put("hostPath", pInfo.getHostPath()); store = lifeCycle.initialize(params); } else { @@ -740,6 +780,7 @@ protected String createLocalStoragePoolName(Host host, StoragePoolInfo storagePo @Override public PrimaryDataStoreInfo createPool(CreateStoragePoolCmd cmd) throws ResourceInUseException, IllegalArgumentException, UnknownHostException, ResourceUnavailableException { String providerName = cmd.getStorageProviderName(); + Map uriParams = extractUriParamsAsMap(cmd.getUrl()); DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(providerName); if (storeProvider == null) { @@ -753,7 +794,7 @@ public PrimaryDataStoreInfo createPool(CreateStoragePoolCmd cmd) throws Resource Long podId = cmd.getPodId(); Long zoneId = cmd.getZoneId(); - ScopeType scopeType = ScopeType.CLUSTER; + ScopeType scopeType = uriParams.get("scheme").toString().equals("file") ? ScopeType.HOST : ScopeType.CLUSTER; String scope = cmd.getScope(); if (scope != null) { try { @@ -819,11 +860,16 @@ public PrimaryDataStoreInfo createPool(CreateStoragePoolCmd cmd) throws Resource params.put("managed", cmd.isManaged()); params.put("capacityBytes", cmd.getCapacityBytes()); params.put("capacityIops", cmd.getCapacityIops()); + params.putAll(uriParams); DataStoreLifeCycle lifeCycle = storeProvider.getDataStoreLifeCycle(); DataStore store = null; try { - store = lifeCycle.initialize(params); + if (params.get("scheme").toString().equals("file")) { + store = createLocalStorage(params); + } else { + store = lifeCycle.initialize(params); + } if (scopeType == ScopeType.CLUSTER) { ClusterScope clusterScope = new ClusterScope(clusterId, podId, zoneId); lifeCycle.attachCluster(store, clusterScope); @@ -848,6 +894,62 @@ public PrimaryDataStoreInfo createPool(CreateStoragePoolCmd cmd) throws Resource return (PrimaryDataStoreInfo)_dataStoreMgr.getDataStore(store.getId(), DataStoreRole.Primary); } + private Map extractUriParamsAsMap(String url){ + Map uriParams = new HashMap<>(); + UriUtils.UriInfo uriInfo = UriUtils.getUriInfo(url); + + String scheme = uriInfo.getScheme(); + String storageHost = uriInfo.getStorageHost(); + String storagePath = uriInfo.getStoragePath(); + try { + if (scheme == null) { + throw new InvalidParameterValueException("scheme is null " + url + ", add nfs:// (or cifs://) as a prefix"); + } else if (scheme.equalsIgnoreCase("nfs")) { + if (storageHost == null || storagePath == null || storageHost.trim().isEmpty() || storagePath.trim().isEmpty()) { + throw new InvalidParameterValueException("host or path is null, should be nfs://hostname/path"); + } + } else if (scheme.equalsIgnoreCase("cifs")) { + // Don't validate against a URI encoded URI. + URI cifsUri = new URI(url); + String warnMsg = UriUtils.getCifsUriParametersProblems(cifsUri); + if (warnMsg != null) { + throw new InvalidParameterValueException(warnMsg); + } + } else if (scheme.equalsIgnoreCase("sharedMountPoint")) { + if (storagePath == null) { + throw new InvalidParameterValueException("host or path is null, should be sharedmountpoint://localhost/path"); + } + } else if (scheme.equalsIgnoreCase("rbd")) { + if (storagePath == null) { + throw new InvalidParameterValueException("host or path is null, should be rbd://hostname/pool"); + } + } else if (scheme.equalsIgnoreCase("gluster")) { + if (storageHost == null || storagePath == null || storageHost.trim().isEmpty() || storagePath.trim().isEmpty()) { + throw new InvalidParameterValueException("host or path is null, should be gluster://hostname/volume"); + } + } + } catch (URISyntaxException e) { + throw new InvalidParameterValueException(url + " is not a valid uri"); + } + + String hostPath = null; + try { + hostPath = URLDecoder.decode(storagePath, "UTF-8"); + } catch (UnsupportedEncodingException e) { + s_logger.error("[ignored] we are on a platform not supporting \"UTF-8\"!?!", e); + } + if (hostPath == null) { // if decoding fails, use getPath() anyway + hostPath = storagePath; + } + + uriParams.put("scheme", scheme); + uriParams.put("host", storageHost); + uriParams.put("hostPath", hostPath); + uriParams.put("userInfo", uriInfo.getUserInfo()); + uriParams.put("port", uriInfo.getPort() + ""); + return uriParams; + } + private Map extractApiParamAsMap(Map ds) { Map details = new HashMap(); if (ds != null) { diff --git a/ui/src/views/infra/AddPrimaryStorage.vue b/ui/src/views/infra/AddPrimaryStorage.vue index b30b0341ca4b..91c0dcbf42e4 100644 --- a/ui/src/views/infra/AddPrimaryStorage.vue +++ b/ui/src/views/infra/AddPrimaryStorage.vue @@ -32,6 +32,7 @@ {{ $t('label.clusterid') }} {{ $t('label.zoneid') }} + {{ $t('label.hostid') }}
@@ -168,7 +170,7 @@
-
+