Skip to content

Commit 1573d0c

Browse files
Locharla, SandeepLocharla, Sandeep
authored andcommitted
CSTACKEX-7: ONTAP Primary storage pool
1 parent c585299 commit 1573d0c

File tree

13 files changed

+747
-14
lines changed

13 files changed

+747
-14
lines changed

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public interface SvmFeignClient {
3434

3535
//this method to get all svms and also filtered svms based on query params as a part of URL
3636
@RequestMapping(method = RequestMethod.GET)
37-
OntapResponse<Svm> getSvmResponse(URI baseURL, @RequestHeader("Authorization") String header);
37+
OntapResponse<Svm> getSvms(URI baseURL, @RequestHeader("Authorization") String header);
3838

3939
@RequestMapping(method = RequestMethod.GET, value = "/{uuid}")
4040
Svm getSvmByUUID(URI baseURL, @RequestHeader("Authorization") String header);

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,23 @@
3030
import org.springframework.web.bind.annotation.RequestBody;
3131
import org.springframework.web.bind.annotation.RequestMethod;
3232

33+
import java.net.URI;
34+
3335

3436
@Lazy
3537
@FeignClient(name = "VolumeClient", url = "https://{clusterIP}/api/storage/volumes", configuration = FeignConfiguration.class)
3638
public interface VolumeFeignClient {
3739

3840
@RequestMapping(method = RequestMethod.DELETE, value="/{uuid}")
39-
void deleteVolume(@RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid);
41+
void deleteVolume(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid);
4042

4143
@RequestMapping(method = RequestMethod.POST)
42-
JobResponse createVolumeWithJob(@RequestHeader("Authorization") String authHeader, @RequestBody Volume volumeRequest);
44+
JobResponse createVolumeWithJob(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestBody Volume volumeRequest);
4345

4446
@RequestMapping(method = RequestMethod.GET, value="/{uuid}")
45-
Volume getVolumeByUUID(@RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid);
47+
Volume getVolumeByUUID(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid);
4648

4749
@RequestMapping(method = RequestMethod.PATCH)
48-
JobResponse updateVolumeRebalancing(@RequestHeader("accept") String acceptHeader, @PathVariable("uuid") String uuid, @RequestBody Volume volumeRequest);
50+
JobResponse updateVolumeRebalancing(URI baseURL, @RequestHeader("accept") String acceptHeader, @PathVariable("uuid") String uuid, @RequestBody Volume volumeRequest);
4951

5052
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.cloudstack.storage.feign.model;
21+
22+
public class OntapStorage {
23+
public static String Username;
24+
public static String Password;
25+
public static String ManagementLIF;
26+
public static String Svm;
27+
public static String Protocol;
28+
public static Boolean IsDisaggregated;
29+
30+
public OntapStorage(String username, String password, String managementLIF, String svm, String protocol, Boolean isDisaggregated) {
31+
Username = username;
32+
Password = password;
33+
ManagementLIF = managementLIF;
34+
Svm = svm;
35+
Protocol = protocol;
36+
IsDisaggregated = isDisaggregated;
37+
}
38+
39+
public String getUsername() {
40+
return Username;
41+
}
42+
43+
public void setUsername(String username) {
44+
Username = username;
45+
}
46+
47+
public String getPassword() {
48+
return Password;
49+
}
50+
51+
public void setPassword(String password) {
52+
Password = password;
53+
}
54+
55+
public String getManagementLIF() {
56+
return ManagementLIF;
57+
}
58+
59+
public void setManagementLIF(String managementLIF) {
60+
ManagementLIF = managementLIF;
61+
}
62+
63+
public String getSVM() {
64+
return Svm;
65+
}
66+
67+
public void setSVM(String svm) {
68+
Svm = svm;
69+
}
70+
71+
public String getProtocol() {
72+
return Protocol;
73+
}
74+
75+
public void setProtocol(String protocol) {
76+
Protocol = protocol;
77+
}
78+
79+
public Boolean getIsDisaggregated() {
80+
return IsDisaggregated;
81+
}
82+
83+
public void setIsDisaggregated(Boolean isDisaggregated) {
84+
IsDisaggregated = isDisaggregated;
85+
}
86+
}

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import java.util.List;
2525

2626
/**
27-
* OnTapResponse
27+
* OntapResponse
2828
*/
2929
@JsonInclude(JsonInclude.Include.NON_NULL)
3030
public class OntapResponse<T> {
@@ -34,11 +34,11 @@ public class OntapResponse<T> {
3434
@JsonProperty("records")
3535
private List<T> records;
3636

37-
public OntapResponse () {
37+
public OntapResponse() {
3838
// Default constructor
3939
}
4040

41-
public OntapResponse (List<T> records) {
41+
public OntapResponse(List<T> records) {
4242
this.records = records;
4343
this.numRecords = (records != null) ? records.size() : 0;
4444
}

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java

Lines changed: 153 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,42 @@
2121

2222

2323
import com.cloud.agent.api.StoragePoolInfo;
24+
import com.cloud.dc.ClusterVO;
25+
import com.cloud.dc.dao.ClusterDao;
26+
import com.cloud.host.HostVO;
2427
import com.cloud.hypervisor.Hypervisor;
28+
import com.cloud.resource.ResourceManager;
29+
import com.cloud.storage.Storage;
30+
import com.cloud.storage.StorageManager;
2531
import com.cloud.storage.StoragePool;
32+
import com.cloud.utils.exception.CloudRuntimeException;
33+
import com.google.common.base.Preconditions;
2634
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
2735
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
2836
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
37+
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
2938
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
39+
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters;
3040
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
41+
import org.apache.cloudstack.storage.datastore.lifecycle.BasePrimaryDataStoreLifeCycleImpl;
42+
import org.apache.cloudstack.storage.feign.model.OntapStorage;
43+
import org.apache.cloudstack.storage.provider.StorageProviderFactory;
44+
import org.apache.cloudstack.storage.service.StorageStrategy;
45+
import org.apache.cloudstack.storage.utils.Constants;
46+
import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
3147
import org.apache.logging.log4j.LogManager;
3248
import org.apache.logging.log4j.Logger;
33-
import java.util.Map;
3449

35-
public class OntapPrimaryDatastoreLifecycle implements PrimaryDataStoreLifeCycle {
50+
import javax.inject.Inject;
51+
import java.util.List;
52+
import java.util.Map;
53+
import java.util.UUID;
3654

55+
public class OntapPrimaryDatastoreLifecycle extends BasePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle {
56+
@Inject private ClusterDao _clusterDao;
57+
@Inject private StorageManager _storageMgr;
58+
@Inject private ResourceManager _resourceMgr;
59+
@Inject private PrimaryDataStoreHelper _dataStoreHelper;
3760
private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class);
3861

3962
/**
@@ -43,14 +66,125 @@ public class OntapPrimaryDatastoreLifecycle implements PrimaryDataStoreLifeCycle
4366
*/
4467
@Override
4568
public DataStore initialize(Map<String, Object> dsInfos) {
69+
if (dsInfos == null) {
70+
throw new CloudRuntimeException("Datastore info map is null, cannot create primary storage");
71+
}
72+
String url = dsInfos.get("url").toString(); // TODO: Decide on whether should the customer enter just the Management LIF IP or https://ManagementLIF
73+
Long zoneId = (Long) dsInfos.get("zoneId");
74+
Long podId = (Long)dsInfos.get("podId");
75+
Long clusterId = (Long)dsInfos.get("clusterId");
76+
String storagePoolName = dsInfos.get("name").toString();
77+
String providerName = dsInfos.get("providerName").toString();
78+
String tags = dsInfos.get("tags").toString();
79+
Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
80+
String scheme = dsInfos.get("scheme").toString();
81+
82+
s_logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName +
83+
", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId + ", protocol: " + scheme);
84+
85+
// Additional details requested for ONTAP primary storage pool creation
86+
@SuppressWarnings("unchecked")
87+
Map<String, String> details = (Map<String, String>)dsInfos.get("details");
88+
// Validations
89+
if (podId != null && clusterId == null) {
90+
s_logger.error("Cluster Id is null, cannot create primary storage");
91+
return null;
92+
} else if (podId == null && clusterId != null) {
93+
s_logger.error("Pod Id is null, cannot create primary storage");
94+
return null;
95+
}
96+
97+
if (podId == null && clusterId == null) {
98+
if (zoneId != null) {
99+
s_logger.info("Both Pod Id and Cluster Id are null, Primary storage pool will be associated with a Zone");
100+
} else {
101+
throw new CloudRuntimeException("Pod Id, Cluster Id and Zone Id are all null, cannot create primary storage");
102+
}
103+
}
104+
105+
if (storagePoolName == null || storagePoolName.isEmpty()) {
106+
throw new CloudRuntimeException("Storage pool name is null or empty, cannot create primary storage");
107+
}
108+
109+
if (providerName == null || providerName.isEmpty()) {
110+
throw new CloudRuntimeException("Provider name is null or empty, cannot create primary storage");
111+
}
112+
113+
PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
114+
if (clusterId != null) {
115+
ClusterVO clusterVO = _clusterDao.findById(clusterId);
116+
Preconditions.checkNotNull(clusterVO, "Unable to locate the specified cluster");
117+
if (clusterVO.getHypervisorType() != Hypervisor.HypervisorType.KVM) {
118+
throw new CloudRuntimeException("ONTAP primary storage is not supported for KVM hypervisor");
119+
}
120+
parameters.setHypervisorType(clusterVO.getHypervisorType());
121+
}
122+
123+
// TODO: While testing need to check what does this actually do and if the fields corresponding to each protocol should also be set
124+
// TODO: scheme could be 'custom' in our case and we might have to ask 'protocol' separately to the user
125+
String protocol = details.get(Constants.PROTOCOL);
126+
switch (protocol.toLowerCase()) {
127+
case Constants.NFS:
128+
parameters.setType(Storage.StoragePoolType.NetworkFilesystem);
129+
break;
130+
case Constants.ISCSI:
131+
parameters.setType(Storage.StoragePoolType.Iscsi);
132+
break;
133+
default:
134+
throw new CloudRuntimeException("Unsupported protocol: " + scheme + ", cannot create primary storage");
135+
}
46136

47-
return null;
137+
details.put(Constants.MANAGEMENTLIF, url);
48138

139+
// Validate the ONTAP details
140+
if(details.get(Constants.ISDISAGGREGATED) == null || details.get(Constants.ISDISAGGREGATED).isEmpty()) {
141+
details.put(Constants.ISDISAGGREGATED, "false");
142+
}
143+
144+
OntapStorage ontapStorage = new OntapStorage(details.get(Constants.USERNAME), details.get(Constants.PASSWORD),
145+
details.get(Constants.MANAGEMENTLIF), details.get(Constants.SVMNAME), details.get(Constants.PROTOCOL),
146+
Boolean.parseBoolean(details.get(Constants.ISDISAGGREGATED)));
147+
StorageProviderFactory storageProviderManager = new StorageProviderFactory(ontapStorage);
148+
StorageStrategy storageStrategy = storageProviderManager.getStrategy();
149+
boolean isValid = storageStrategy.connect();
150+
if (isValid) {
151+
// String volumeName = storagePoolName + "_vol"; //TODO: Figure out a better naming convention
152+
storageStrategy.createVolume(storagePoolName, Long.parseLong((details.get("size")))); // TODO: size should be in bytes, so see if conversion is needed
153+
} else {
154+
throw new CloudRuntimeException("ONTAP details validation failed, cannot create primary storage");
155+
}
156+
157+
parameters.setTags(tags);
158+
parameters.setIsTagARule(isTagARule);
159+
parameters.setDetails(details);
160+
parameters.setUuid(UUID.randomUUID().toString());
161+
parameters.setZoneId(zoneId);
162+
parameters.setPodId(podId);
163+
parameters.setClusterId(clusterId);
164+
parameters.setName(storagePoolName);
165+
parameters.setProviderName(providerName);
166+
parameters.setManaged(true);
167+
168+
return _dataStoreHelper.createPrimaryDataStore(parameters);
49169
}
50170

51171
@Override
52-
public boolean attachCluster(DataStore store, ClusterScope scope) {
53-
return false;
172+
public boolean attachCluster(DataStore dataStore, ClusterScope scope) {
173+
logger.debug("In attachCluster for ONTAP primary storage");
174+
PrimaryDataStoreInfo primarystore = (PrimaryDataStoreInfo)dataStore;
175+
List<HostVO> hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInClusterForStorageConnection(primarystore);
176+
177+
logger.debug(String.format("Attaching the pool to each of the hosts %s in the cluster: %s", hostsToConnect, primarystore.getClusterId()));
178+
for (HostVO host : hostsToConnect) {
179+
// TODO: Fetch the host IQN and add to the initiator group on ONTAP cluster
180+
try {
181+
_storageMgr.connectHostToSharedPool(host, dataStore.getId());
182+
} catch (Exception e) {
183+
logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e);
184+
}
185+
}
186+
_dataStoreHelper.attachCluster(dataStore);
187+
return true;
54188
}
55189

56190
@Override
@@ -60,7 +194,20 @@ public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo exis
60194

61195
@Override
62196
public boolean attachZone(DataStore dataStore, ZoneScope scope, Hypervisor.HypervisorType hypervisorType) {
63-
return false;
197+
logger.debug("In attachZone for ONTAP primary storage");
198+
List<HostVO> hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInZoneForStorageConnection(dataStore, scope.getScopeId(), Hypervisor.HypervisorType.KVM);
199+
200+
logger.debug(String.format("In createPool. Attaching the pool to each of the hosts in %s.", hostsToConnect));
201+
for (HostVO host : hostsToConnect) {
202+
// TODO: Fetch the host IQN and add to the initiator group on ONTAP cluster
203+
try {
204+
_storageMgr.connectHostToSharedPool(host, dataStore.getId());
205+
} catch (Exception e) {
206+
logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e);
207+
}
208+
}
209+
_dataStoreHelper.attachZone(dataStore);
210+
return true;
64211
}
65212

66213
@Override

0 commit comments

Comments
 (0)