Skip to content

Commit 2c61e76

Browse files
piyush5netappSrivastava, Piyush
andauthored
Feature/cstackex 22: Shared NFS pool and volume creation - Approach 1 (#22)
* NFS Cloudstack volume and export policy utils * Licencse add in files * accessgroup create recode * creatacessgroup for NFS impl * storage pool mounting on host * storage pool mounting on host 1 * vm restart issue * vm restart issue 1 * vm restart issue 2 * vm instance creation test1 * vm instance creation test2 * vm instance creation test4 * vm instance creation test5 * vm instance creation test6 * vm instance creation test7 * vm instance creation test6 * vm instance creation test7 * "Update code as per coding principle" * latest all * removed unrelated files * more fixes * lint fix * lint fix 1 * lint fix 2 * testing and review comments fix * pull latest main 2 * lint fix * lint fix 2 * testing fix 3 --------- Co-authored-by: Srivastava, Piyush <Piyush.Srivastava@netapp.com>
1 parent b23ac40 commit 2c61e76

File tree

17 files changed

+798
-239
lines changed

17 files changed

+798
-239
lines changed

plugins/storage/volume/ontap/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@
3131
<spring-cloud.version>2021.0.7</spring-cloud.version>
3232
<openfeign.version>11.0</openfeign.version>
3333
<json.version>20230227</json.version>
34-
<jackson-databind.version>2.13.4</jackson-databind.version>
3534
<httpclient.version>4.5.14</httpclient.version>
3635
<swagger-annotations.version>1.6.2</swagger-annotations.version>
3736
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
3837
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
38+
<jackson-databind.version>2.13.4</jackson-databind.version>
3939
</properties>
4040
<dependencyManagement>
4141
<dependencies>

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
*/
1919
package org.apache.cloudstack.storage.driver;
2020

21+
import com.cloud.agent.api.Answer;
22+
import com.cloud.agent.api.to.DataObjectType;
2123
import com.cloud.agent.api.to.DataStoreTO;
2224
import com.cloud.agent.api.to.DataTO;
25+
import com.cloud.exception.InvalidParameterValueException;
2326
import com.cloud.host.Host;
2427
import com.cloud.storage.Storage;
2528
import com.cloud.storage.StoragePool;
@@ -40,11 +43,12 @@
4043
import org.apache.cloudstack.storage.command.CommandResult;
4144
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
4245
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
43-
import org.apache.cloudstack.storage.feign.model.OntapStorage;
44-
import org.apache.cloudstack.storage.provider.StorageProviderFactory;
46+
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
4547
import org.apache.cloudstack.storage.service.StorageStrategy;
48+
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
4649
import org.apache.cloudstack.storage.service.model.ProtocolType;
4750
import org.apache.cloudstack.storage.utils.Constants;
51+
import org.apache.cloudstack.storage.utils.Utility;
4852
import org.apache.logging.log4j.LogManager;
4953
import org.apache.logging.log4j.Logger;
5054

@@ -58,13 +62,15 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
5862

5963
@Inject private StoragePoolDetailsDao storagePoolDetailsDao;
6064
@Inject private PrimaryDataStoreDao storagePoolDao;
65+
6166
@Override
6267
public Map<String, String> getCapabilities() {
6368
s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called");
6469
Map<String, String> mapCapabilities = new HashMap<>();
65-
66-
mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
67-
mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
70+
// RAW managed initial implementation: snapshot features not yet supported
71+
// TODO Set it to false once we start supporting snapshot feature
72+
mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.FALSE.toString());
73+
mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.FALSE.toString());
6874

6975
return mapCapabilities;
7076
}
@@ -75,18 +81,93 @@ public DataTO getTO(DataObject data) {
7581
}
7682

7783
@Override
78-
public DataStoreTO getStoreTO(DataStore store) {
79-
return null;
80-
}
84+
public DataStoreTO getStoreTO(DataStore store) { return null; }
8185

8286
@Override
8387
public void createAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
88+
CreateCmdResult createCmdResult = null;
89+
String path = null;
90+
String errMsg = null;
91+
if (dataStore == null) {
92+
throw new InvalidParameterValueException("createAsync: dataStore should not be null");
93+
}
94+
if (dataObject == null) {
95+
throw new InvalidParameterValueException("createAsync: dataObject should not be null");
96+
}
97+
if (callback == null) {
98+
throw new InvalidParameterValueException("createAsync: callback should not be null");
99+
}
100+
try {
101+
s_logger.info("createAsync: Started for data store [{}] and data object [{}] of type [{}]",
102+
dataStore, dataObject, dataObject.getType());
103+
if (dataObject.getType() == DataObjectType.VOLUME) {
104+
VolumeInfo volumeInfo = (VolumeInfo) dataObject;
105+
path = createCloudStackVolumeForTypeVolume(dataStore, volumeInfo);
106+
createCmdResult = new CreateCmdResult(path, new Answer(null, true, null));
107+
} else {
108+
errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to createAsync";
109+
s_logger.error(errMsg);
110+
throw new CloudRuntimeException(errMsg);
111+
}
112+
} catch (Exception e) {
113+
errMsg = e.getMessage();
114+
s_logger.error("createAsync: Failed for dataObject [{}]: {}", dataObject, errMsg);
115+
createCmdResult = new CreateCmdResult(null, new Answer(null, false, errMsg));
116+
createCmdResult.setResult(e.toString());
117+
} finally {
118+
if (createCmdResult != null && createCmdResult.isSuccess()) {
119+
s_logger.info("createAsync: Volume created successfully. Path: {}", path);
120+
}
121+
callback.complete(createCmdResult);
122+
}
123+
}
84124

125+
private String createCloudStackVolumeForTypeVolume(DataStore dataStore, VolumeInfo volumeObject) {
126+
StoragePoolVO storagePool = storagePoolDao.findById(dataStore.getId());
127+
if(storagePool == null) {
128+
s_logger.error("createCloudStackVolume : Storage Pool not found for id: " + dataStore.getId());
129+
throw new CloudRuntimeException("createCloudStackVolume : Storage Pool not found for id: " + dataStore.getId());
130+
}
131+
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(dataStore.getId());
132+
StorageStrategy storageStrategy = Utility.getStrategyByStoragePoolDetails(details);
133+
s_logger.info("createCloudStackVolumeForTypeVolume: Connection to Ontap SVM [{}] successful, preparing CloudStackVolumeRequest", details.get(Constants.SVM_NAME));
134+
CloudStackVolume cloudStackVolumeRequest = Utility.createCloudStackVolumeRequestByProtocol(storagePool, details, volumeObject);
135+
CloudStackVolume cloudStackVolume = storageStrategy.createCloudStackVolume(cloudStackVolumeRequest);
136+
if (ProtocolType.ISCSI.name().equalsIgnoreCase(details.get(Constants.PROTOCOL)) && cloudStackVolume.getLun() != null && cloudStackVolume.getLun().getName() != null) {
137+
return cloudStackVolume.getLun().getName();
138+
} else if (ProtocolType.NFS3.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
139+
return volumeObject.getUuid(); // return the volume UUID for agent as path for mounting
140+
} else {
141+
String errMsg = "createCloudStackVolumeForTypeVolume: Volume creation failed. Lun or Lun Path is null for dataObject: " + volumeObject;
142+
s_logger.error(errMsg);
143+
throw new CloudRuntimeException(errMsg);
144+
}
85145
}
86146

87147
@Override
88148
public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
89-
149+
CommandResult commandResult = new CommandResult();
150+
try {
151+
if (store == null || data == null) {
152+
throw new CloudRuntimeException("deleteAsync: store or data is null");
153+
}
154+
if (data.getType() == DataObjectType.VOLUME) {
155+
StoragePoolVO storagePool = storagePoolDao.findById(store.getId());
156+
if(storagePool == null) {
157+
s_logger.error("deleteAsync : Storage Pool not found for id: " + store.getId());
158+
throw new CloudRuntimeException("deleteAsync : Storage Pool not found for id: " + store.getId());
159+
}
160+
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(store.getId());
161+
if (ProtocolType.NFS3.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
162+
// ManagedNFS qcow2 backing file deletion handled by KVM host/libvirt; nothing to do via ONTAP REST.
163+
s_logger.info("deleteAsync: ManagedNFS volume {} no-op ONTAP deletion", data.getId());
164+
}
165+
}
166+
} catch (Exception e) {
167+
commandResult.setResult(e.getMessage());
168+
} finally {
169+
callback.complete(commandResult);
170+
}
90171
}
91172

92173
@Override
@@ -121,7 +202,6 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore
121202

122203
@Override
123204
public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) {
124-
125205
}
126206

127207
@Override
@@ -161,7 +241,7 @@ public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, Qual
161241

162242
@Override
163243
public boolean canProvideStorageStats() {
164-
return true;
244+
return false;
165245
}
166246

167247
@Override
@@ -171,7 +251,7 @@ public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
171251

172252
@Override
173253
public boolean canProvideVolumeStats() {
174-
return true;
254+
return false; // Not yet implemented for RAW managed NFS
175255
}
176256

177257
@Override
@@ -213,24 +293,4 @@ public boolean isStorageSupportHA(Storage.StoragePoolType type) {
213293
public void detachVolumeFromAllStorageNodes(Volume volume) {
214294

215295
}
216-
217-
private StorageStrategy getStrategyByStoragePoolDetails(Map<String, String> details) {
218-
if (details == null || details.isEmpty()) {
219-
s_logger.error("getStrategyByStoragePoolDetails: Storage pool details are null or empty");
220-
throw new CloudRuntimeException("getStrategyByStoragePoolDetails: Storage pool details are null or empty");
221-
}
222-
String protocol = details.get(Constants.PROTOCOL);
223-
OntapStorage ontapStorage = new OntapStorage(details.get(Constants.USERNAME), details.get(Constants.PASSWORD),
224-
details.get(Constants.MANAGEMENT_LIF), details.get(Constants.SVM_NAME), Long.parseLong(details.get(Constants.SIZE)), ProtocolType.valueOf(protocol),
225-
Boolean.parseBoolean(details.get(Constants.IS_DISAGGREGATED)));
226-
StorageStrategy storageStrategy = StorageProviderFactory.getStrategy(ontapStorage);
227-
boolean isValid = storageStrategy.connect();
228-
if (isValid) {
229-
s_logger.info("Connection to Ontap SVM [{}] successful", details.get(Constants.SVM_NAME));
230-
return storageStrategy;
231-
} else {
232-
s_logger.error("getStrategyByStoragePoolDetails: Connection to Ontap SVM [" + details.get(Constants.SVM_NAME) + "] failed");
233-
throw new CloudRuntimeException("getStrategyByStoragePoolDetails: Connection to Ontap SVM [" + details.get(Constants.SVM_NAME) + "] failed");
234-
}
235-
}
236296
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public Decoder createDecoder() {
137137
@Override
138138
public Object decode(Response response, Type type) throws IOException, DecodeException {
139139
if (response.body() == null) {
140+
logger.debug("Response body is null, returning null");
140141
return null;
141142
}
142143
String json = null;
@@ -145,8 +146,11 @@ public Object decode(Response response, Type type) throws IOException, DecodeExc
145146
logger.debug("Decoding JSON response: {}", json);
146147
return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type));
147148
} catch (IOException e) {
148-
logger.error("Error decoding JSON response. Status: {}, Raw body: {}", response.status(), json, e);
149+
logger.error("IOException during decoding. Status: {}, Raw body: {}", response.status(), json, e);
149150
throw new DecodeException(response.status(), "Error decoding JSON response", response.request(), e);
151+
} catch (Exception e) {
152+
logger.error("Unexpected error during decoding. Status: {}, Type: {}, Raw body: {}", response.status(), type, json, e);
153+
throw new DecodeException(response.status(), "Unexpected error during decoding", response.request(), e);
150154
}
151155
}
152156
};

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

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,65 +19,68 @@
1919

2020
package org.apache.cloudstack.storage.feign.client;
2121

22+
import feign.QueryMap;
2223
import org.apache.cloudstack.storage.feign.model.ExportPolicy;
2324
import org.apache.cloudstack.storage.feign.model.FileInfo;
2425
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
2526
import feign.Headers;
2627
import feign.Param;
2728
import feign.RequestLine;
2829

29-
//TODO: Proper URLs should be added in the RequestLine annotations below
30+
import java.util.Map;
31+
3032
public interface NASFeignClient {
3133

3234
// File Operations
33-
@RequestLine("GET /{volumeUuid}/files/{path}")
35+
@RequestLine("GET /api/storage/volumes/{volumeUuid}/files/{path}")
3436
@Headers({"Authorization: {authHeader}"})
3537
OntapResponse<FileInfo> getFileResponse(@Param("authHeader") String authHeader,
36-
@Param("volumeUuid") String volumeUUID,
37-
@Param("path") String filePath);
38+
@Param("volumeUuid") String volumeUUID,
39+
@Param("path") String filePath);
3840

39-
@RequestLine("DELETE /{volumeUuid}/files/{path}")
41+
@RequestLine("DELETE /api/storage/volumes/{volumeUuid}/files/{path}")
4042
@Headers({"Authorization: {authHeader}"})
4143
void deleteFile(@Param("authHeader") String authHeader,
42-
@Param("volumeUuid") String volumeUUID,
43-
@Param("path") String filePath);
44+
@Param("volumeUuid") String volumeUUID,
45+
@Param("path") String filePath);
4446

45-
@RequestLine("PATCH /{volumeUuid}/files/{path}")
47+
@RequestLine("PATCH /api/storage/volumes/{volumeUuid}/files/{path}")
4648
@Headers({"Authorization: {authHeader}"})
4749
void updateFile(@Param("authHeader") String authHeader,
48-
@Param("volumeUuid") String volumeUUID,
49-
@Param("path") String filePath, FileInfo fileInfo);
50+
@Param("volumeUuid") String volumeUUID,
51+
@Param("path") String filePath,
52+
FileInfo fileInfo);
5053

51-
@RequestLine("POST /{volumeUuid}/files/{path}")
54+
@RequestLine("POST /api/storage/volumes/{volumeUuid}/files/{path}")
5255
@Headers({"Authorization: {authHeader}"})
5356
void createFile(@Param("authHeader") String authHeader,
54-
@Param("volumeUuid") String volumeUUID,
55-
@Param("path") String filePath, FileInfo file);
57+
@Param("volumeUuid") String volumeUUID,
58+
@Param("path") String filePath,
59+
FileInfo file);
5660

5761
// Export Policy Operations
58-
@RequestLine("POST /")
59-
@Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
60-
ExportPolicy createExportPolicy(@Param("authHeader") String authHeader,
61-
@Param("returnRecords") boolean returnRecords,
62+
@RequestLine("POST /api/protocols/nfs/export-policies")
63+
@Headers({"Authorization: {authHeader}"})
64+
void createExportPolicy(@Param("authHeader") String authHeader,
6265
ExportPolicy exportPolicy);
6366

64-
@RequestLine("GET /")
67+
@RequestLine("GET /api/protocols/nfs/export-policies")
6568
@Headers({"Authorization: {authHeader}"})
66-
OntapResponse<ExportPolicy> getExportPolicyResponse(@Param("authHeader") String authHeader);
69+
OntapResponse<ExportPolicy> getExportPolicyResponse(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);
6770

68-
@RequestLine("GET /{id}")
71+
@RequestLine("GET /api/protocols/nfs/export-policies/{id}")
6972
@Headers({"Authorization: {authHeader}"})
70-
OntapResponse<ExportPolicy> getExportPolicyById(@Param("authHeader") String authHeader,
71-
@Param("id") String id);
73+
ExportPolicy getExportPolicyById(@Param("authHeader") String authHeader,
74+
@Param("id") String id);
7275

73-
@RequestLine("DELETE /{id}")
76+
@RequestLine("DELETE /api/protocols/nfs/export-policies/{id}")
7477
@Headers({"Authorization: {authHeader}"})
7578
void deleteExportPolicyById(@Param("authHeader") String authHeader,
76-
@Param("id") String id);
79+
@Param("id") String id);
7780

78-
@RequestLine("PATCH /{id}")
81+
@RequestLine("PATCH /api/protocols/nfs/export-policies/{id}")
7982
@Headers({"Authorization: {authHeader}"})
8083
OntapResponse<ExportPolicy> updateExportPolicy(@Param("authHeader") String authHeader,
81-
@Param("id") String id,
82-
ExportPolicy request);
84+
@Param("id") String id,
85+
ExportPolicy request);
8386
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
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+
*/
119
package org.apache.cloudstack.storage.feign.client;
220

321
import feign.Headers;

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ public interface VolumeFeignClient {
4646
@Headers({"Authorization: {authHeader}"})
4747
Volume getVolumeByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
4848

49+
@RequestLine("GET /api/storage/volumes")
50+
@Headers({"Authorization: {authHeader}"})
51+
OntapResponse<Volume> getVolume(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);
52+
4953
@RequestLine("PATCH /api/storage/volumes/{uuid}")
50-
@Headers({"Accept: {acceptHeader}", "Authorization: {authHeader}"})
51-
JobResponse updateVolumeRebalancing(@Param("acceptHeader") String acceptHeader, @Param("uuid") String uuid, Volume volumeRequest);
54+
@Headers({ "Authorization: {authHeader}"})
55+
JobResponse updateVolumeRebalancing(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Volume volumeRequest);
5256
}

0 commit comments

Comments
 (0)