Skip to content

Commit aa14ab5

Browse files
Srivastava, PiyushSrivastava, Piyush
authored andcommitted
storage pool mounting on host
1 parent 09caf90 commit aa14ab5

File tree

8 files changed

+275
-120
lines changed

8 files changed

+275
-120
lines changed

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,63 +19,66 @@
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

30+
import java.util.Map;
31+
2932
public interface NASFeignClient {
3033

3134
// File Operations
32-
@RequestLine("GET /{volumeUuid}/files/{path}")
35+
@RequestLine("GET /api/storage/volumes/{volumeUuid}/files/{path}")
3336
@Headers({"Authorization: {authHeader}"})
3437
OntapResponse<FileInfo> getFileResponse(@Param("authHeader") String authHeader,
3538
@Param("volumeUuid") String volumeUUID,
3639
@Param("path") String filePath);
3740

38-
@RequestLine("DELETE /{volumeUuid}/files/{path}")
41+
@RequestLine("DELETE /api/storage/volumes/{volumeUuid}/files/{path}")
3942
@Headers({"Authorization: {authHeader}"})
4043
void deleteFile(@Param("authHeader") String authHeader,
4144
@Param("volumeUuid") String volumeUUID,
4245
@Param("path") String filePath);
4346

44-
@RequestLine("PATCH /{volumeUuid}/files/{path}")
47+
@RequestLine("PATCH /api/storage/volumes/{volumeUuid}/files/{path}")
4548
@Headers({"Authorization: {authHeader}"})
4649
void updateFile(@Param("authHeader") String authHeader,
4750
@Param("volumeUuid") String volumeUUID,
4851
@Param("path") String filePath,
4952
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,
5457
@Param("volumeUuid") String volumeUUID,
5558
@Param("path") String filePath,
5659
FileInfo file);
5760

5861
// Export Policy Operations
59-
@RequestLine("POST /")
62+
@RequestLine("POST /api/protocols/nfs/export-policies")
6063
@Headers({"Authorization: {authHeader}"})
6164
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-
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}"})
7073
ExportPolicy getExportPolicyById(@Param("authHeader") String authHeader,
7174
@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,
7679
@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,
8184
@Param("id") String id,

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,22 @@ public DataStore initialize(Map<String, Object> dsInfos) {
188188

189189
// Determine storage pool type and path based on protocol
190190
String path;
191+
String host = "";
191192
ProtocolType protocol = ProtocolType.valueOf(details.get(Constants.PROTOCOL));
192193
switch (protocol) {
193194
case NFS:
194195
parameters.setType(Storage.StoragePoolType.NetworkFilesystem);
195-
path = details.get(Constants.MANAGEMENT_LIF) + ":/" + storagePoolName;
196+
// Path should be just the NFS export path (junction path), NOT host:path
197+
// CloudStack will construct the full mount path as: hostAddress + ":" + path
198+
path = "/" + storagePoolName;
196199
s_logger.info("Setting NFS path for storage pool: " + path);
200+
host = "10.193.192.136"; // TODO hardcoded for now
197201
break;
198202
case ISCSI:
199203
parameters.setType(Storage.StoragePoolType.Iscsi);
200204
path = "iqn.1992-08.com.netapp:" + details.get(Constants.SVM_NAME) + "." + storagePoolName;
201205
s_logger.info("Setting iSCSI path for storage pool: " + path);
206+
parameters.setHost(details.get(Constants.MANAGEMENT_LIF));
202207
break;
203208
default:
204209
throw new CloudRuntimeException("Unsupported protocol: " + protocol + ", cannot create primary storage");
@@ -239,8 +244,8 @@ public DataStore initialize(Map<String, Object> dsInfos) {
239244
}
240245

241246
// Set parameters for primary data store
242-
parameters.setHost(details.get(Constants.MANAGEMENT_LIF));
243247
parameters.setPort(Constants.ONTAP_PORT);
248+
parameters.setHost(host);
244249
parameters.setPath(path);
245250
parameters.setTags(tags != null ? tags : "");
246251
parameters.setIsTagARule(isTagARule != null ? isTagARule : Boolean.FALSE);
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.storage.listener;
19+
20+
import javax.inject.Inject;
21+
22+
import com.cloud.agent.api.ModifyStoragePoolCommand;
23+
import com.cloud.alert.AlertManager;
24+
import com.cloud.storage.StoragePoolHostVO;
25+
import com.cloud.storage.dao.StoragePoolHostDao;
26+
import org.apache.logging.log4j.Logger;
27+
import org.apache.logging.log4j.LogManager;
28+
import com.cloud.agent.AgentManager;
29+
import com.cloud.agent.api.Answer;
30+
import com.cloud.agent.api.DeleteStoragePoolCommand;
31+
import com.cloud.host.Host;
32+
import com.cloud.storage.StoragePool;
33+
import com.cloud.utils.exception.CloudRuntimeException;
34+
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
35+
import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
36+
import com.cloud.host.dao.HostDao;
37+
38+
/**
39+
* HypervisorHostListener implementation for ONTAP storage.
40+
* Handles connecting/disconnecting hosts to/from ONTAP-backed storage pools.
41+
*/
42+
public class OntapHostListener implements HypervisorHostListener {
43+
protected Logger logger = LogManager.getLogger(getClass());
44+
45+
@Inject
46+
private AgentManager _agentMgr;
47+
@Inject
48+
private AlertManager _alertMgr;
49+
@Inject
50+
private PrimaryDataStoreDao _storagePoolDao;
51+
@Inject
52+
private HostDao _hostDao;
53+
@Inject private StoragePoolHostDao storagePoolHostDao;
54+
55+
56+
@Override
57+
public boolean hostConnect(long hostId, long poolId) {
58+
logger.info("Connect to host " + hostId + " from pool " + poolId);
59+
Host host = _hostDao.findById(hostId);
60+
if (host == null) {
61+
logger.error("Failed to add host by HostListener as host was not found with id : {}", hostId);
62+
return false;
63+
}
64+
65+
// TODO add host type check also since we support only KVM for now, host.getHypervisorType().equals(HypervisorType.KVM)
66+
StoragePool pool = _storagePoolDao.findById(poolId);
67+
logger.info("Connecting host {} to ONTAP storage pool {}", host.getName(), pool.getName());
68+
69+
70+
// incase host was not added by cloudstack , we will add it
71+
StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(poolId, hostId);
72+
73+
if (storagePoolHost == null) {
74+
storagePoolHost = new StoragePoolHostVO(poolId, hostId, "");
75+
76+
storagePoolHostDao.persist(storagePoolHost);
77+
}
78+
79+
// Validate pool type - ONTAP supports NFS and iSCSI
80+
// StoragePoolType poolType = pool.getPoolType();
81+
// // TODO add iscsi also here
82+
// if (poolType != StoragePoolType.NetworkFilesystem) {
83+
// logger.error("Unsupported pool type {} for ONTAP storage", poolType);
84+
// return false;
85+
// }
86+
87+
try {
88+
// Create the CreateStoragePoolCommand to send to the agent
89+
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, pool);
90+
91+
Answer answer = _agentMgr.easySend(hostId, cmd);
92+
93+
if (answer == null) {
94+
throw new CloudRuntimeException(String.format("Unable to get an answer to the modify storage pool command (%s)", pool));
95+
}
96+
97+
if (!answer.getResult()) {
98+
String msg = String.format("Unable to attach storage pool %s to host %d", pool, hostId);
99+
100+
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, pool.getDataCenterId(), pool.getPodId(), msg, msg);
101+
102+
throw new CloudRuntimeException(String.format(
103+
"Unable to establish a connection from agent to storage pool %s due to %s", pool, answer.getDetails()));
104+
}
105+
} catch (Exception e) {
106+
logger.error("Exception while connecting host {} to storage pool {}", host.getName(), pool.getName(), e);
107+
throw new CloudRuntimeException("Failed to connect host to storage pool: " + e.getMessage(), e);
108+
}
109+
return true;
110+
}
111+
112+
@Override
113+
public boolean hostDisconnected(Host host, StoragePool pool) {
114+
logger.info("Disconnect from host " + host.getId() + " from pool " + pool.getName());
115+
116+
Host hostToremove = _hostDao.findById(host.getId());
117+
if (hostToremove == null) {
118+
logger.error("Failed to add host by HostListener as host was not found with id : {}", host.getId());
119+
return false;
120+
}
121+
// TODO add storage pool get validation
122+
logger.info("Disconnecting host {} from ONTAP storage pool {}", host.getName(), pool.getName());
123+
124+
try {
125+
DeleteStoragePoolCommand cmd = new DeleteStoragePoolCommand(pool);
126+
long hostId = host.getId();
127+
Answer answer = _agentMgr.easySend(hostId, cmd);
128+
129+
if (answer != null && answer.getResult()) {
130+
logger.info("Successfully disconnected host {} from ONTAP storage pool {}", host.getName(), pool.getName());
131+
return true;
132+
} else {
133+
String errMsg = (answer != null) ? answer.getDetails() : "Unknown error";
134+
logger.warn("Failed to disconnect host {} from storage pool {}. Error: {}", host.getName(), pool.getName(), errMsg);
135+
return false;
136+
}
137+
} catch (Exception e) {
138+
logger.error("Exception while disconnecting host {} from storage pool {}", host.getName(), pool.getName(), e);
139+
return false;
140+
}
141+
}
142+
143+
@Override
144+
public boolean hostDisconnected(long hostId, long poolId) {
145+
return false;
146+
}
147+
148+
@Override
149+
public boolean hostAboutToBeRemoved(long hostId) {
150+
return false;
151+
}
152+
153+
@Override
154+
public boolean hostRemoved(long hostId, long clusterId) {
155+
return false;
156+
}
157+
158+
@Override
159+
public boolean hostEnabled(long hostId) {
160+
return false;
161+
}
162+
163+
@Override
164+
public boolean hostAdded(long hostId) {
165+
return false;
166+
}
167+
168+
}

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapHostListener.java

Lines changed: 0 additions & 58 deletions
This file was deleted.

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapPrimaryDatastoreProvider.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider;
2828
import org.apache.cloudstack.storage.driver.OntapPrimaryDatastoreDriver;
2929
import org.apache.cloudstack.storage.lifecycle.OntapPrimaryDatastoreLifecycle;
30+
import org.apache.cloudstack.storage.listener.OntapHostListener;
3031
import org.apache.logging.log4j.LogManager;
3132
import org.apache.logging.log4j.Logger;
3233
import org.springframework.stereotype.Component;
@@ -41,7 +42,7 @@ public class OntapPrimaryDatastoreProvider implements PrimaryDataStoreProvider {
4142
private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreProvider.class);
4243
private OntapPrimaryDatastoreDriver primaryDatastoreDriver;
4344
private OntapPrimaryDatastoreLifecycle primaryDatastoreLifecycle;
44-
// private HypervisorHostListener listener;
45+
private HypervisorHostListener listener;
4546

4647
public OntapPrimaryDatastoreProvider() {
4748
s_logger.info("OntapPrimaryDatastoreProvider initialized");
@@ -58,7 +59,7 @@ public DataStoreDriver getDataStoreDriver() {
5859

5960
@Override
6061
public HypervisorHostListener getHostListener() {
61-
return null;
62+
return listener;
6263
}
6364

6465
@Override
@@ -72,7 +73,7 @@ public boolean configure(Map<String, Object> params) {
7273
s_logger.trace("OntapPrimaryDatastoreProvider: configure: Called");
7374
primaryDatastoreDriver = ComponentContext.inject(OntapPrimaryDatastoreDriver.class);
7475
primaryDatastoreLifecycle = ComponentContext.inject(OntapPrimaryDatastoreLifecycle.class);
75-
// listener = ComponentContext.inject(OntapHostListener.class);
76+
listener = ComponentContext.inject(OntapHostListener.class);
7677

7778
return true;
7879
}

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.cloudstack.storage.feign.client.VolumeFeignClient;
2828
import org.apache.cloudstack.storage.feign.model.Aggregate;
2929
import org.apache.cloudstack.storage.feign.model.Job;
30+
import org.apache.cloudstack.storage.feign.model.Nas;
3031
import org.apache.cloudstack.storage.feign.model.OntapStorage;
3132
import org.apache.cloudstack.storage.feign.model.Svm;
3233
import org.apache.cloudstack.storage.feign.model.Volume;
@@ -150,10 +151,15 @@ public Volume createStorageVolume(String volumeName, Long size) {
150151
Svm svm = new Svm();
151152
svm.setName(svmName);
152153

154+
Nas nas = new Nas();
155+
nas.setPath("/" + volumeName);
156+
153157
volumeRequest.setName(volumeName);
154158
volumeRequest.setSvm(svm);
155159
volumeRequest.setAggregates(aggregates);
156160
volumeRequest.setSize(size);
161+
volumeRequest.setNas(nas); // be default if we don't set path , ONTAP create a volume with mount/junction path // TODO check if we need to append svm name or not
162+
// since storage pool also cannot be duplicate so junction path can also be not duplicate so /volumeName will always be unique
157163
// Make the POST API call to create the volume
158164
try {
159165
/*

0 commit comments

Comments
 (0)