1919
2020package org .apache .cloudstack .storage .listener ;
2121
22+ import java .util .List ;
23+ import java .util .Map ;
24+
2225import javax .inject .Inject ;
2326
24- import com .cloud .agent .api .ModifyStoragePoolCommand ;
27+ import org .apache .cloudstack .engine .subsystem .api .storage .HypervisorHostListener ;
28+ import org .apache .cloudstack .storage .datastore .db .PrimaryDataStoreDao ;
29+ import org .apache .cloudstack .storage .datastore .db .StoragePoolDetailsDao ;
30+ import org .apache .cloudstack .storage .datastore .db .StoragePoolVO ;
31+ import org .apache .cloudstack .storage .service .StorageStrategy ;
32+ import org .apache .cloudstack .storage .service .model .AccessGroup ;
33+ import org .apache .cloudstack .storage .service .model .ProtocolType ;
34+ import org .apache .cloudstack .storage .utils .OntapStorageConstants ;
35+ import org .apache .cloudstack .storage .utils .OntapStorageUtils ;
36+ import org .apache .commons .lang3 .StringUtils ;
37+ import org .apache .logging .log4j .LogManager ;
38+ import org .apache .logging .log4j .Logger ;
39+
40+ import com .cloud .agent .AgentManager ;
41+ import com .cloud .agent .api .Answer ;
2542import com .cloud .agent .api .ModifyStoragePoolAnswer ;
43+ import com .cloud .agent .api .ModifyStoragePoolCommand ;
2644import com .cloud .agent .api .StoragePoolInfo ;
2745import com .cloud .alert .AlertManager ;
46+ import com .cloud .host .Host ;
47+ import com .cloud .host .HostVO ;
48+ import com .cloud .host .dao .HostDao ;
2849import com .cloud .hypervisor .Hypervisor ;
50+ import com .cloud .storage .StoragePool ;
2951import com .cloud .storage .StoragePoolHostVO ;
3052import com .cloud .storage .dao .StoragePoolHostDao ;
31- import org .apache .logging .log4j .Logger ;
32- import org .apache .logging .log4j .LogManager ;
33- import com .cloud .agent .AgentManager ;
34- import com .cloud .agent .api .Answer ;
35- import com .cloud .agent .api .DeleteStoragePoolCommand ;
36- import com .cloud .host .Host ;
37- import com .cloud .storage .StoragePool ;
3853import com .cloud .utils .exception .CloudRuntimeException ;
39- import org .apache .cloudstack .storage .datastore .db .PrimaryDataStoreDao ;
40- import org .apache .cloudstack .storage .datastore .db .StoragePoolVO ;
41- import org .apache .cloudstack .storage .datastore .db .StoragePoolDetailsDao ;
42- import org .apache .cloudstack .engine .subsystem .api .storage .HypervisorHostListener ;
43- import com .cloud .host .dao .HostDao ;
44-
45- import java .util .Map ;
4654
4755public class OntapHostListener implements HypervisorHostListener {
4856 protected Logger logger = LogManager .getLogger (getClass ());
@@ -63,26 +71,39 @@ public class OntapHostListener implements HypervisorHostListener {
6371
6472 @ Override
6573 public boolean hostConnect (long hostId , long poolId ) {
66- logger .info ("Connect to host " + hostId + " from pool " + poolId );
74+ logger .info ("hostConnect: Connecting host {} to pool {}" , hostId , poolId );
6775 Host host = _hostDao .findById (hostId );
6876 if (host == null ) {
69- logger .error ("host was not found with id : {}" , hostId );
77+ logger .error ("hostConnect: Host was not found with id: {}" , hostId );
7078 return false ;
7179 }
7280 if (!host .getHypervisorType ().equals (Hypervisor .HypervisorType .KVM )) {
73- logger .error ("ONTAP plugin does not support {} type host currently " , host .getHypervisorType ());
81+ logger .error ("hostConnect: ONTAP plugin does not support {} type host currently" , host .getHypervisorType ());
7482 return false ;
7583 }
7684
7785 StoragePool pool = _storagePoolDao .findById (poolId );
7886 if (pool == null ) {
79- logger .error ("Failed to connect host - storage pool not found with id: {}" , poolId );
87+ logger .error ("hostConnect: Failed to connect host - storage pool not found with id: {}" , poolId );
8088 return false ;
8189 }
82- logger .info ("Connecting host {} to ONTAP storage pool {}" , host .getName (), pool .getName ());
90+ logger .info ("hostConnect: Connecting host {} to ONTAP storage pool {}" , host .getName (), pool .getName ());
8391 try {
8492 // Load storage pool details from database to pass mount options and other config to agent
8593 Map <String , String > detailsMap = _storagePoolDetailsDao .listDetailsKeyPairs (poolId );
94+ if (detailsMap == null || detailsMap .isEmpty ()) {
95+ logger .error ("hostConnect: Failed to load storage pool details for pool id: {}" , poolId );
96+ return false ;
97+ }
98+
99+ if (detailsMap .get (OntapStorageConstants .PROTOCOL ) == null ) {
100+ logger .error ("hostConnect: Storage pool details missing required protocol type for pool id: {}" , poolId );
101+ return false ;
102+ }
103+
104+ // Update NFS export policy for this connected host when the pool protocol is NFS3.
105+ updateNfsExportPolicyForConnectedHostIfNeeded (poolId , hostId , host , detailsMap );
106+
86107 // Create the ModifyStoragePoolCommand to send to the agent
87108 // Note: Always send command even if database entry exists, because agent may have restarted
88109 // and lost in-memory pool registration. The command handler is idempotent.
@@ -118,19 +139,19 @@ public boolean hostConnect(long hostId, long poolId) {
118139 }
119140
120141 String localPath = poolInfo .getLocalPath ();
121- logger .info ("Storage pool {} successfully mounted at: {}" , pool .getName (), localPath );
142+ logger .info ("hostConnect: Storage pool {} successfully mounted at: {}" , pool .getName (), localPath );
122143
123144 // Update or create the storage_pool_host_ref entry with the correct local_path
124145 StoragePoolHostVO storagePoolHost = storagePoolHostDao .findByPoolHost (poolId , hostId );
125146
126147 if (storagePoolHost == null ) {
127148 storagePoolHost = new StoragePoolHostVO (poolId , hostId , localPath );
128149 storagePoolHostDao .persist (storagePoolHost );
129- logger .info ("Created storage_pool_host_ref entry for pool {} and host {}" , pool .getName (), host .getName ());
150+ logger .info ("hostConnect: Created storage_pool_host_ref entry for pool {} and host {}" , pool .getName (), host .getName ());
130151 } else {
131152 storagePoolHost .setLocalPath (localPath );
132153 storagePoolHostDao .update (storagePoolHost .getId (), storagePoolHost );
133- logger .info ("Updated storage_pool_host_ref entry with local_path: {}" , localPath );
154+ logger .info ("hostConnect: Updated storage_pool_host_ref entry with local_path: {}" , localPath );
134155 }
135156
136157 // Update pool capacity/usage information
@@ -139,62 +160,127 @@ public boolean hostConnect(long hostId, long poolId) {
139160 poolVO .setCapacityBytes (poolInfo .getCapacityBytes ());
140161 poolVO .setUsedBytes (poolInfo .getCapacityBytes () - poolInfo .getAvailableBytes ());
141162 _storagePoolDao .update (poolVO .getId (), poolVO );
142- logger .info ("Updated storage pool capacity: {} GB, used: {} GB" , poolInfo .getCapacityBytes () / (1024 * 1024 * 1024 ), (poolInfo .getCapacityBytes () - poolInfo .getAvailableBytes ()) / (1024 * 1024 * 1024 ));
163+ logger .info ("hostConnect: Updated storage pool capacity: {} GB, used: {} GB" , poolInfo .getCapacityBytes () / (1024 * 1024 * 1024 ), (poolInfo .getCapacityBytes () - poolInfo .getAvailableBytes ()) / (1024 * 1024 * 1024 ));
143164 }
144165
145166 } catch (Exception e ) {
146- logger .error ("Exception while connecting host {} to storage pool {}" , host .getName (), pool .getName (), e );
167+ logger .error ("hostConnect: Exception while connecting host {} to storage pool {}" , host .getName (), pool .getName (), e );
147168 // CRITICAL: Don't throw exception - it crashes the agent and causes restart loops
148169 // Return false to indicate failure without crashing
149170 return false ;
150171 }
151172 return true ;
152173 }
153174
154- @ Override
155- public boolean hostDisconnected (long hostId , long poolId ) {
156- logger .info ("Disconnect from host " + hostId + " from pool " + poolId );
175+ private void updateNfsExportPolicyForConnectedHostIfNeeded (long poolId , long hostId , Host host , Map <String , String > detailsMap ) {
176+ if (!ProtocolType .NFS3 .name ().equalsIgnoreCase (detailsMap .get (OntapStorageConstants .PROTOCOL ))) {
177+ return ;
178+ }
157179
158- Host hostToremove = _hostDao .findById (hostId );
159- if (hostToremove == null ) {
160- logger .error ("Failed to add host by HostListener as host was not found with id : {}" , hostId );
161- return false ;
180+ if (host == null ) {
181+ throw new CloudRuntimeException ("Host was not found with id: " + hostId );
162182 }
163183
164- StoragePool pool = _storagePoolDao .findById (poolId );
165- if (pool == null ) {
166- logger .error ("Failed to disconnect host - storage pool not found with id: {}" , poolId );
184+ if (!isNfs3EnabledOnHost (host )) {
185+ throw new CloudRuntimeException ("NFS protocol is not enabled on host with id: " + hostId );
186+ }
187+
188+ AccessGroup accessGroup = new AccessGroup ();
189+ accessGroup .setStoragePoolId (poolId );
190+ accessGroup .setHostsToConnect (List .of ((HostVO ) host ));
191+
192+ StorageStrategy strategy = OntapStorageUtils .getStrategyByStoragePoolDetails (detailsMap );
193+ strategy .updateAccessGroup (accessGroup );
194+ logger .info ("hostConnect: updateNfsExportPolicyForConnectedHostIfNeeded: Updated NFS export policy rules for host {} on storage pool {}" , host .getName (), poolId );
195+ }
196+
197+ private boolean isNfs3EnabledOnHost (Host host ) {
198+ if (host == null ) {
167199 return false ;
168200 }
169- logger .info ("Disconnecting host {} from ONTAP storage pool {}" , hostToremove .getName (), pool .getName ());
170201
171- try {
172- DeleteStoragePoolCommand cmd = new DeleteStoragePoolCommand (pool );
173- Answer answer = _agentMgr .easySend (hostId , cmd );
174- if (answer != null && answer .getResult ()) {
175- logger .info ("Successfully disconnected host {} from ONTAP storage pool {}" , hostToremove .getName (), pool .getName ());
176- return true ;
177- } else {
178- String errMsg = (answer != null ) ? answer .getDetails () : "Unknown error" ;
179- logger .warn ("Failed to disconnect host {} from storage pool {}. Error: {}" , hostToremove .getName (), pool .getName (), errMsg );
180- return false ;
181- }
182- } catch (Exception e ) {
183- logger .error ("Exception while disconnecting host {} from storage pool {}" , hostToremove .getName (), pool .getName (), e );
202+ String storageIp = host .getStorageIpAddress () != null ? host .getStorageIpAddress ().trim () : "" ;
203+ if (storageIp .isEmpty () && StringUtils .isBlank (host .getPrivateIpAddress ())) {
204+ logger .warn ("isNfs3EnabledOnHost: Host {} is not eligible for NFS3 protocol: both storage IP and private IP are empty" ,
205+ host .getId ());
184206 return false ;
185207 }
208+
209+ return true ;
186210 }
187211
188212 @ Override
189- public boolean hostAboutToBeRemoved (long hostId ) {
213+ public boolean hostDisconnected (long hostId , long poolId ) {
214+ logger .info ("hostDisconnected: Disconnecting host {} from pool {}" , hostId , poolId );
215+ // Note: This is not currently being called for NetApp ONTAP storage plugin.
190216 return false ;
191217 }
192218
219+ @ Override
220+ public boolean hostAboutToBeRemoved (long hostId ) {
221+ logger .info ("hostAboutToBeRemoved: Host {} is about to be removed" , hostId );
222+
223+ Host host = _hostDao .findById (hostId );
224+ if (host == null ) {
225+ logger .warn ("hostAboutToBeRemoved: Host not found with id: {}, considering it as no-op" , hostId );
226+ return true ;
227+ }
228+
229+ List <StoragePoolHostVO > poolHostRefs = storagePoolHostDao .listByHostId (hostId );
230+ if (poolHostRefs == null || poolHostRefs .isEmpty ()) {
231+ logger .debug ("hostAboutToBeRemoved: No storage pool associations found for host {}" , hostId );
232+ return true ;
233+ }
234+
235+ for (StoragePoolHostVO ref : poolHostRefs ) {
236+ StoragePoolVO pool = _storagePoolDao .findById (ref .getPoolId ());
237+ if (pool != null ) {
238+ removeHostFromOntapPoolIfNeeded (pool , host );
239+ }
240+ }
241+
242+ logger .info ("hostAboutToBeRemoved: Cleaned up ONTAP export policies for host {} about to be removed" , hostId );
243+ return true ;
244+ }
245+
193246 @ Override
194247 public boolean hostRemoved (long hostId , long clusterId ) {
195248 return false ;
196249 }
197250
251+ private void removeHostFromOntapPoolIfNeeded (StoragePoolVO pool , Host host ) {
252+ try {
253+ Map <String , String > detailsMap = _storagePoolDetailsDao .listDetailsKeyPairs (pool .getId ());
254+ if (detailsMap == null || detailsMap .isEmpty ()) {
255+ logger .debug ("hostAboutToBeRemoved: removeHostFromOntapPoolIfNeeded: No pool details found for pool id: {}" , pool .getId ());
256+ return ;
257+ }
258+
259+ // Skip non-NFS3 pools; Currently, for iSCSI type, iGroup rules are being handled as part of revokeAccess in OntapPrimaryDataStoreDriver, so no need to handle here.
260+ if (!ProtocolType .NFS3 .name ().equalsIgnoreCase (detailsMap .get (OntapStorageConstants .PROTOCOL ))) {
261+ return ;
262+ }
263+
264+ logger .info ("hostAboutToBeRemoved: removeHostFromOntapPoolIfNeeded: Removing export policy rule for host {} from storage pool {}" , host .getName (), pool .getName ());
265+ if (!isNfs3EnabledOnHost (host )) {
266+ logger .warn ("hostAboutToBeRemoved: removeHostFromOntapPoolIfNeeded: Skipping NFS export policy removal for host {} on pool {} as host is not NFS-enabled" ,
267+ host .getId (), pool .getId ());
268+ return ;
269+ }
270+ AccessGroup accessGroup = new AccessGroup ();
271+ accessGroup .setStoragePoolId (pool .getId ());
272+ accessGroup .setHostsToConnect (List .of ((HostVO ) host ));
273+ accessGroup .setHostRuleAction (AccessGroup .HostRuleAction .REMOVE );
274+
275+ StorageStrategy strategy = OntapStorageUtils .getStrategyByStoragePoolDetails (detailsMap );
276+ strategy .updateAccessGroup (accessGroup );
277+ logger .info ("hostAboutToBeRemoved: removeHostFromOntapPoolIfNeeded: Removed NFS export policy rules for removed host {} from storage pool {}" , host .getName (), pool .getName ());
278+ } catch (Exception e ) {
279+ logger .warn ("hostAboutToBeRemoved: removeHostFromOntapPoolIfNeeded: Failed to remove NFS export policy rule for host {} from pool {}: {}" , host .getId (), pool .getName (), e .getMessage ());
280+ // Continue processing other pools even if one fails
281+ }
282+ }
283+
198284 @ Override
199285 public boolean hostEnabled (long hostId ) {
200286 return false ;
0 commit comments