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 ;
27+ import com .cloud .utils .exception .CloudRuntimeException ;
28+ import com .cloud .utils .script .OutputInterpreter ;
29+ import 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 ;
37+ import org .apache .log4j .Logger ;
38+ import org .joda .time .Duration ;
2839
2940public class LinstorStoragePool implements KVMStoragePool {
41+ private static final Logger s_logger = Logger .getLogger (LinstorStoragePool .class );
3042 private final String _uuid ;
3143 private final String _sourceHost ;
3244 private final int _sourcePort ;
3345 private final Storage .StoragePoolType _storagePoolType ;
3446 private final StorageAdaptor _storageAdaptor ;
3547 private final String _resourceGroup ;
48+ private final String localNodeName ;
3649
3750 public LinstorStoragePool (String uuid , String host , int port , String resourceGroup ,
3851 Storage .StoragePoolType storagePoolType , StorageAdaptor storageAdaptor ) {
@@ -42,6 +55,7 @@ public LinstorStoragePool(String uuid, String host, int port, String resourceGro
4255 _storagePoolType = storagePoolType ;
4356 _storageAdaptor = storageAdaptor ;
4457 _resourceGroup = resourceGroup ;
58+ localNodeName = getHostname ();
4559 }
4660
4761 @ Override
@@ -200,33 +214,133 @@ public String getResourceGroup() {
200214
201215 @ Override
202216 public boolean isPoolSupportHA () {
203- return false ;
217+ return true ;
204218 }
205219
206220 @ Override
207221 public String getHearthBeatPath () {
208- return null ;
222+ String kvmScriptsDir = AgentPropertiesFileHandler .getPropertyValue (AgentProperties .KVM_SCRIPTS_DIR );
223+ return Script .findScript (kvmScriptsDir , "kvmspheartbeat.sh" );
209224 }
210225
211226 @ Override
212- public String createHeartBeatCommand (HAStoragePool primaryStoragePool , String hostPrivateIp ,
227+ public String createHeartBeatCommand (HAStoragePool pool , String hostPrivateIp ,
213228 boolean hostValidation ) {
214- 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" ;
215240 }
216241
217242 @ Override
218243 public String getStorageNodeId () {
244+ // only called by storpool
219245 return null ;
220246 }
221247
248+ static String getHostname () {
249+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
250+ Script sc = new Script ("hostname" , Duration .millis (10000L ), s_logger );
251+ String res = sc .execute (parser );
252+ if (res != null ) {
253+ throw new CloudRuntimeException (String .format ("Unable to run 'hostname' command: %s" , res ));
254+ }
255+ String response = parser .getLines ();
256+ return response .trim ();
257+ }
258+
222259 @ Override
223260 public Boolean checkingHeartBeat (HAStoragePool pool , HostTO host ) {
224- 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 ;
225338 }
226339
227340 @ Override
228341 public Boolean vmActivityCheck (HAStoragePool pool , HostTO host , Duration activityScriptTimeout , String volumeUUIDListString , String vmActivityCheckPath , long duration ) {
229- return null ;
342+ s_logger .trace (String .format ("Linstor.vmActivityCheck: %s, %s" , pool .getPoolIp (), host .getPrivateNetwork ().getIp ()));
343+ return checkingHeartBeat (pool , host );
230344 }
231345
232346}
0 commit comments