1919import java .util .List ;
2020import java .util .Map ;
2121
22- import org .apache .cloudstack .utils .qemu .QemuImg ;
23- import org .joda .time .Duration ;
24-
2522import com .cloud .agent .api .to .HostTO ;
23+ import com .cloud .agent .properties .AgentProperties ;
24+ import com .cloud .agent .properties .AgentPropertiesFileHandler ;
2625import com .cloud .hypervisor .kvm .resource .KVMHABase .HAStoragePool ;
2726import com .cloud .storage .Storage ;
2827import com .cloud .utils .exception .CloudRuntimeException ;
2928import com .cloud .utils .script .OutputInterpreter ;
3029import com .cloud .utils .script .Script ;
30+ import com .google .gson .JsonArray ;
31+ import com .google .gson .JsonElement ;
32+ import com .google .gson .JsonIOException ;
33+ import com .google .gson .JsonObject ;
34+ import com .google .gson .JsonParser ;
35+ import com .google .gson .JsonSyntaxException ;
36+ import org .apache .cloudstack .utils .qemu .QemuImg ;
3137import org .apache .log4j .Logger ;
38+ import org .joda .time .Duration ;
3239
3340public class LinstorStoragePool implements KVMStoragePool {
3441 private static final Logger s_logger = Logger .getLogger (LinstorStoragePool .class );
@@ -38,6 +45,7 @@ public class LinstorStoragePool implements KVMStoragePool {
3845 private final Storage .StoragePoolType _storagePoolType ;
3946 private final StorageAdaptor _storageAdaptor ;
4047 private final String _resourceGroup ;
48+ private final String localNodeName ;
4149
4250 public LinstorStoragePool (String uuid , String host , int port , String resourceGroup ,
4351 Storage .StoragePoolType storagePoolType , StorageAdaptor storageAdaptor ) {
@@ -47,6 +55,7 @@ public LinstorStoragePool(String uuid, String host, int port, String resourceGro
4755 _storagePoolType = storagePoolType ;
4856 _storageAdaptor = storageAdaptor ;
4957 _resourceGroup = resourceGroup ;
58+ localNodeName = getHostname ();
5059 }
5160
5261 @ Override
@@ -205,22 +214,34 @@ public String getResourceGroup() {
205214
206215 @ Override
207216 public boolean isPoolSupportHA () {
208- return false ;
217+ return true ;
209218 }
210219
211220 @ Override
212221 public String getHearthBeatPath () {
213- return null ;
222+ String kvmScriptsDir = AgentPropertiesFileHandler .getPropertyValue (AgentProperties .KVM_SCRIPTS_DIR );
223+ return Script .findScript (kvmScriptsDir , "kvmspheartbeat.sh" );
214224 }
215225
216226 @ Override
217- public String createHeartBeatCommand (HAStoragePool primaryStoragePool , String hostPrivateIp ,
227+ public String createHeartBeatCommand (HAStoragePool pool , String hostPrivateIp ,
218228 boolean hostValidation ) {
219- return null ;
229+ s_logger .trace (String .format ("Linstor.createHeartBeatCommand: %s, %s, %b" , pool .getPoolIp (), hostPrivateIp , hostValidation ));
230+ boolean isStorageNodeUp = checkingHeartBeat (pool , null );
231+ if (!isStorageNodeUp && !hostValidation ) {
232+ //restart the host
233+ s_logger .debug (String .format ("The host [%s] will be restarted because the health check failed for the storage pool [%s]" , hostPrivateIp , pool .getPool ().getType ()));
234+ Script cmd = new Script (pool .getPool ().getHearthBeatPath (), Duration .millis (HeartBeatUpdateTimeout ), s_logger );
235+ cmd .add ("-c" );
236+ cmd .execute ();
237+ return "Down" ;
238+ }
239+ return isStorageNodeUp ? null : "Down" ;
220240 }
221241
222242 @ Override
223243 public String getStorageNodeId () {
244+ // only called by storpool
224245 return null ;
225246 }
226247
@@ -237,11 +258,88 @@ static String getHostname() {
237258
238259 @ Override
239260 public Boolean checkingHeartBeat (HAStoragePool pool , HostTO host ) {
240- return null ;
261+ String hostName ;
262+ if (host == null ) {
263+ hostName = localNodeName ;
264+ } else {
265+ hostName = host .getParent ();
266+ if (hostName == null ) {
267+ s_logger .error ("No hostname set in host.getParent()" );
268+ return false ;
269+ }
270+ }
271+
272+ return checkHostUpToDateAndConnected (hostName );
273+ }
274+
275+ private String executeDrbdSetupStatus (OutputInterpreter .AllLinesParser parser ) {
276+ Script sc = new Script ("drbdsetup" , Duration .millis (HeartBeatUpdateTimeout ), s_logger );
277+ sc .add ("status" );
278+ sc .add ("--json" );
279+ return sc .execute (parser );
280+ }
281+
282+ private boolean checkDrbdSetupStatusOutput (String output , String otherNodeName ) {
283+ JsonParser jsonParser = new JsonParser ();
284+ JsonArray jResources = (JsonArray ) jsonParser .parse (output );
285+ for (JsonElement jElem : jResources ) {
286+ JsonObject jRes = (JsonObject ) jElem ;
287+ JsonArray jConnections = jRes .getAsJsonArray ("connections" );
288+ for (JsonElement jConElem : jConnections ) {
289+ JsonObject jConn = (JsonObject ) jConElem ;
290+ if (jConn .getAsJsonPrimitive ("name" ).getAsString ().equals (otherNodeName )
291+ && jConn .getAsJsonPrimitive ("connection-state" ).getAsString ().equalsIgnoreCase ("Connected" )) {
292+ return true ;
293+ }
294+ }
295+ }
296+ s_logger .warn (String .format ("checkDrbdSetupStatusOutput: no resource connected to %s." , otherNodeName ));
297+ return false ;
298+ }
299+
300+ private String executeDrbdEventsNow (OutputInterpreter .AllLinesParser parser ) {
301+ Script sc = new Script ("drbdsetup" , Duration .millis (HeartBeatUpdateTimeout ), s_logger );
302+ sc .add ("events2" );
303+ sc .add ("--now" );
304+ return sc .execute (parser );
305+ }
306+
307+ private boolean checkDrbdEventsNowOutput (String output ) {
308+ boolean healthy = output .lines ().noneMatch (line -> line .matches (".*role:Primary .* promotion_score:0.*" ));
309+ if (!healthy ) {
310+ s_logger .warn ("checkDrbdEventsNowOutput: primary resource with promotion score==0; HA false" );
311+ }
312+ return healthy ;
313+ }
314+
315+ private boolean checkHostUpToDateAndConnected (String hostName ) {
316+ s_logger .trace (String .format ("checkHostUpToDateAndConnected: %s/%s" , localNodeName , hostName ));
317+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
318+
319+ if (localNodeName .equalsIgnoreCase (hostName )) {
320+ String res = executeDrbdEventsNow (parser );
321+ if (res != null ) {
322+ return false ;
323+ }
324+ return checkDrbdEventsNowOutput (parser .getLines ());
325+ } else {
326+ // check drbd connections
327+ String res = executeDrbdSetupStatus (parser );
328+ if (res != null ) {
329+ return false ;
330+ }
331+ try {
332+ return checkDrbdSetupStatusOutput (parser .getLines (), hostName );
333+ } catch (JsonIOException | JsonSyntaxException e ) {
334+ s_logger .error ("Error parsing drbdsetup status --json" , e );
335+ }
336+ }
337+ return false ;
241338 }
242339
243340 @ Override
244341 public Boolean vmActivityCheck (HAStoragePool pool , HostTO host , Duration activityScriptTimeout , String volumeUUIDListString , String vmActivityCheckPath , long duration ) {
245- return null ;
342+ s_logger .trace (String .format ("Linstor.vmActivityCheck: %s, %s" , pool .getPoolIp (), host .getPrivateNetwork ().getIp ()));
343+ return checkingHeartBeat (pool , host );
246344 }
247345}
0 commit comments