/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.coregui.client.admin.storage;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_ADDRESS;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_ALERTS;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_AVAILABILITY;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_RESOURCE_ID;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_STATUS;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.types.SortDirection;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.HTMLFlow;
import com.smartgwt.client.widgets.grid.CellFormatter;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.cloud.StorageNode;
import org.rhq.core.domain.cloud.StorageNode.OperationMode;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.ImageManager;
import org.rhq.coregui.client.LinkManager;
import org.rhq.coregui.client.admin.AdministrationView;
import org.rhq.coregui.client.components.table.AuthorizedTableAction;
import org.rhq.coregui.client.components.table.TableActionEnablement;
import org.rhq.coregui.client.components.table.TableSection;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.util.Log;
import org.rhq.coregui.client.util.StringUtility;
import org.rhq.coregui.client.util.async.Command;
import org.rhq.coregui.client.util.async.CountDownLatch;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton.ButtonColor;
import org.rhq.coregui.client.util.message.Message;
/**
* Shows the table of all storage nodes.
*
* @author Jirka Kremser
*/
public class StorageNodeTableView extends TableSection<StorageNodeDatasource> {
public static final String VIEW_PATH = AdministrationView.VIEW_ID + "/"
+ AdministrationView.SECTION_TOPOLOGY_VIEW_ID + "/" + StorageNodeAdminView.VIEW_ID;
public StorageNodeTableView() {
super(null);
setHeight100();
setWidth100();
Criteria criteria = new Criteria();
String[] modes = new String[OperationMode.values().length];
int i = 0;
for (OperationMode value : OperationMode.values()) {
modes[i++] = value.name();
}
criteria.addCriteria(StorageNodeDatasource.FILTER_OPERATION_MODE, modes);
setInitialCriteria(criteria);
setDataSource(StorageNodeDatasource.instance());
}
@Override
protected void doOnDraw() {
super.doOnDraw();
// scheduleUnacknowledgedAlertsPollingJob(getListGrid());
}
@Override
protected void configureTable() {
super.configureTable();
List<ListGridField> fields = getDataSource().getListGridFields();
for (ListGridField field : fields) {
// adding the cell formatter for name field (clickable link)
if (field.getName().equals(FIELD_ADDRESS.propertyName())) {
field.setCellFormatter(new CellFormatter() {
@Override
public String format(Object value, ListGridRecord record, int rowNum, int colNum) {
if (value == null) {
return "";
}
String detailsUrl = "#" + VIEW_PATH + "/" + getId(record);
String formattedValue = StringUtility.escapeHtml(value.toString());
return LinkManager.getHref(detailsUrl, formattedValue);
}
});
} else if (field.getName().equals(FIELD_RESOURCE_ID.propertyName())) {
// adding the cell formatter for resource id field (clickable link)
field.setCellFormatter(new CellFormatter() {
@Override
public String format(Object value, ListGridRecord record, int rowNum, int colNum) {
if (value == null || value.toString().isEmpty()) {
return "";
}
String rawUrl = null;
try {
rawUrl = LinkManager.getResourceLink(record.getAttributeAsInt(FIELD_RESOURCE_ID
.propertyName()));
} catch (NumberFormatException nfe) {
rawUrl = MSG.common_label_none();
}
String formattedValue = StringUtility.escapeHtml(rawUrl);
String label = StringUtility.escapeHtml(MSG.view_adminTopology_storageNodes_link());
return LinkManager.getHref(formattedValue, label);
}
});
}
}
ListGrid listGrid = getListGrid();
listGrid.setAutoSaveEdits(false);
listGrid.setFields(fields.toArray(new ListGridField[fields.size()]));
listGrid.sort(FIELD_ADDRESS.propertyName(), SortDirection.ASCENDING);
listGrid.setHoverWidth(200);
showCommonActions();
}
@SuppressWarnings("unused")
private void scheduleUnacknowledgedAlertsPollingJob(final ListGrid listGrid) {
new Timer() {
public void run() {
Log.info("Running the job fetching the number of unack alerts for particular storage nodes...");
final ListGridRecord[] records = listGrid.getRecords();
List<Integer> storageNodeIds = new ArrayList<Integer>(records.length);
for (ListGridRecord record : records) {
storageNodeIds.add(record.getAttributeAsInt(FIELD_ID));
}
GWTServiceLookup.getStorageService().findNotAcknowledgedStorageNodeAlertsCounts(storageNodeIds,
new AsyncCallback<List<Integer>>() {
@Override
public void onSuccess(List<Integer> result) {
for (int i = 0; i < records.length; i++) {
int value = result.get(i);
int storageNodeId = records[i].getAttributeAsInt("id");
records[i].setAttribute(
FIELD_ALERTS.propertyName(),
StorageNodeAdminView.getAlertsString(
MSG.view_adminTopology_storageNodes_unackAlerts(), storageNodeId, value));
listGrid.setData(records);
}
schedule(15 * 1000);
}
@Override
public void onFailure(Throwable caught) {
schedule(60 * 1000);
}
});
}
}.schedule(15 * 1000);
Log.info("Polling job fetching the number of unack alerts for particular storage nodes has been scheduled");
}
@Override
protected ListGrid createListGrid() {
ListGrid listGrid = new ListGrid() {
@Override
protected Canvas getExpansionComponent(final ListGridRecord record) {
if (record.getAttribute(FIELD_RESOURCE_ID.propertyName()) == null) {
// no resource set
return new HTMLFlow(MSG.view_adminTopology_storageNodes_noLoad());
}
int id = record.getAttributeAsInt(FIELD_ID);
Log.debug("Expanding Storage Node [id=" + id + "] row");
StorageNodeLoadComponent component = new StorageNodeLoadComponent(id, null);
component.redraw();
return component;
}
};
listGrid.setCanExpandRecords(true);
return listGrid;
}
@Override
public Canvas getDetailsView(Integer id) {
HTMLFlow header = new HTMLFlow("");
setHeader(header);
return new StorageNodeDetailView(id, header);
}
private void showCommonActions() {
addInvokeOperationsAction();
addDeployAction();
addUndeployAction();
}
private void addUndeployAction() {
final ParametrizedMessage question = new ParametrizedMessage() {
@Override
public String getMessage(String... param) {
return MSG.view_adminTopology_storageNodes_msg_undeployConfirm(param[0]);
}
};
final ParametrizedMessage success = new ParametrizedMessage() {
@Override
public String getMessage(String... param) {
return MSG.view_adminTopology_storageNodes_msg_deployStart(param[0]);
}
};
final ParametrizedMessage failure = new ParametrizedMessage() {
@Override
public String getMessage(String... param) {
return MSG.view_adminTopology_storageNodes_msg_undeployFailed(param[0], param[1]);
}
};
addTableAction(MSG.view_adminTopology_storageNodes_run_undeploySelected(), null, ButtonColor.RED,
new AuthorizedTableAction(this, TableActionEnablement.SINGLE, Permission.MANAGE_SETTINGS) {
private String availabilityIcon = ImageManager.getAvailabilityIconFromAvailType(AvailabilityType.UP);
@Override
public boolean isEnabled(ListGridRecord[] selection) {
return StorageNodeTableView.this.isUndeployable(super.isEnabled(selection), selection, availabilityIcon);
}
@Override
public void executeAction(final ListGridRecord[] selections, Object actionValue) {
executeBulkAction(selections, actionValue, question, success, failure,
StorageNodeOperation.UNDEPLOY);
}
});
}
private void addDeployAction() {
final ParametrizedMessage question = new ParametrizedMessage() {
@Override
public String getMessage(String... param) {
return MSG.view_adminTopology_storageNodes_msg_deployConfirm(param[0]);
}
};
final ParametrizedMessage success = new ParametrizedMessage() {
@Override
public String getMessage(String... param) {
return MSG.view_adminTopology_storageNodes_msg_undeployStart(param[0]);
}
};
final ParametrizedMessage failure = new ParametrizedMessage() {
@Override
public String getMessage(String... param) {
return MSG.view_adminTopology_storageNodes_msg_deployFailed(param[0], param[1]);
}
};
addTableAction(MSG.view_adminTopology_storageNodes_run_deploySelected(), null, ButtonColor.BLUE,
new AuthorizedTableAction(this, TableActionEnablement.SINGLE, Permission.MANAGE_SETTINGS) {
@Override
public boolean isEnabled(ListGridRecord[] selection) {
return StorageNodeTableView.this.isDeployable(super.isEnabled(selection), selection);
}
@Override
public void executeAction(final ListGridRecord[] selections, Object actionValue) {
executeBulkAction(selections, actionValue, question, success, failure, StorageNodeOperation.DEPLOY);
}
});
}
private void addInvokeOperationsAction() {
Map<String, Object> operationsMap = new LinkedHashMap<String, Object>();
operationsMap.put(MSG.common_title_start(), "start");
operationsMap.put(MSG.view_adminTopology_storageNodes_run_shutdown(), "shutdown");
operationsMap.put(MSG.view_adminTopology_storageNodes_run_restart(), "restart");
operationsMap.put(MSG.view_adminTopology_storageNodes_run_disableDebug(), "stopRPCServer");
operationsMap.put(MSG.view_adminTopology_storageNodes_run_enableDebug(), "startRPCServer");
addTableAction(MSG.common_title_operation(), null, operationsMap, ButtonColor.GRAY, new AuthorizedTableAction(
this, TableActionEnablement.ANY, Permission.MANAGE_SETTINGS) {
@Override
public boolean isEnabled(ListGridRecord[] selection) {
return StorageNodeTableView.this.isEnabled(super.isEnabled(selection), selection);
}
@Override
public void executeAction(final ListGridRecord[] selections, Object actionValue) {
ParametrizedMessage question = new ParametrizedMessage() {
@Override
public String getMessage(String... param) {
return MSG.view_adminTopology_storageNodes_msg_commonOpConfirm(param[0], param[1]);
}
};
ParametrizedMessage success = new ParametrizedMessage() {
@Override
public String getMessage(String... param) {
return MSG.view_adminTopology_storageNodes_msg_commonOpFailed(param[0], param[1]);
}
};
ParametrizedMessage failure = new ParametrizedMessage() {
@Override
public String getMessage(String... param) {
return MSG.view_adminTopology_storageNodes_msg_commonOpStart(param[0], param[1]);
}
};
executeBulkAction(selections, actionValue, question, success, failure, StorageNodeOperation.OTHER);
}
});
}
private enum StorageNodeOperation {
DEPLOY, UNDEPLOY, OTHER
}
private interface ParametrizedMessage {
String getMessage(String... param);
}
private void executeBulkAction(final ListGridRecord[] selections, Object actionValue, ParametrizedMessage question,
final ParametrizedMessage success, final ParametrizedMessage failure, final StorageNodeOperation operationType) {
final String operationName = (String) actionValue;
final List<String> selectedAddresses = getSelectedAddresses(selections);
String areYouSureQuestion = operationType == StorageNodeOperation.OTHER ? question.getMessage(operationName,
selectedAddresses.toString()) : question.getMessage(selectedAddresses.toString());
SC.ask(areYouSureQuestion, new BooleanCallback() {
public void execute(Boolean confirmed) {
if (confirmed) {
final CountDownLatch latch = CountDownLatch.create(selections.length, new Command() {
@Override
public void execute() {
String msgString = null;
if (operationType == StorageNodeOperation.OTHER) {
msgString = success.getMessage(operationName, selectedAddresses.toString());
} else {
msgString = success.getMessage(selectedAddresses.toString());
}
Message msg = new Message(msgString, Message.Severity.Info);
CoreGUI.getMessageCenter().notify(msg);
refreshTableInfo();
}
});
boolean isStopStartOrRestart = Arrays.asList("start", "shutdown", "restart")
.contains(operationName);
for (ListGridRecord storageNodeRecord : selections) {
// NFE should never happen, because of the condition for table action enablement
int resourceId = storageNodeRecord.getAttributeAsInt(FIELD_RESOURCE_ID.propertyName());
if (isStopStartOrRestart) {
// start, stop or restart the storage node
GWTServiceLookup.getOperationService().scheduleResourceOperation(resourceId, operationName,
null, "Run by Storage Node Administrations UI", 0, new AsyncCallback<Void>() {
public void onSuccess(Void result) {
latch.countDown();
}
public void onFailure(Throwable caught) {
String msg = failure.getMessage(operationName,
selectedAddresses + " " + caught.getMessage());
CoreGUI.getErrorHandler().handleError(msg, caught);
latch.countDown();
refreshTableInfo();
}
});
} else {
if (operationType != StorageNodeOperation.OTHER) { // (un)deploy
AsyncCallback<Void> callback = new AsyncCallback<Void>() {
public void onSuccess(Void result) {
latch.countDown();
}
public void onFailure(Throwable caught) {
String msg = failure.getMessage(
selectedAddresses.toString(),
Arrays.asList(getSelectedIds(selections)).toString() + " "
+ caught.getMessage());
CoreGUI.getErrorHandler().handleError(msg, caught);
latch.countDown();
refreshTableInfo();
}
};
int storageNodeId = storageNodeRecord.getAttributeAsInt("id");
StorageNode node = new StorageNode(storageNodeId);
if (operationType == StorageNodeOperation.DEPLOY) {
GWTServiceLookup.getStorageService().deployStorageNode(node, callback);
} else {
GWTServiceLookup.getStorageService().undeployStorageNode(node, callback);
}
} else {
// invoke the operation on the storage service resource
GWTServiceLookup.getStorageService().invokeOperationOnStorageService(resourceId,
operationName, new AsyncCallback<Void>() {
public void onSuccess(Void result) {
latch.countDown();
}
public void onFailure(Throwable caught) {
String msg = failure.getMessage(operationName, selectedAddresses + " "
+ caught.getMessage());
CoreGUI.getErrorHandler().handleError(msg, caught);
latch.countDown();
refreshTableInfo();
}
});
}
}
}
} else {
refreshTableInfo();
}
}
});
}
private int[] getSelectedIds(ListGridRecord[] selections) {
if (selections == null) {
return new int[0];
}
int[] ids = new int[selections.length];
int i = 0;
for (ListGridRecord selection : selections) {
ids[i++] = selection.getAttributeAsInt(FIELD_ID);
}
return ids;
}
private List<String> getSelectedAddresses(ListGridRecord[] selections) {
if (selections == null) {
return new ArrayList<String>(0);
}
List<String> ids = new ArrayList<String>(selections.length);
for (ListGridRecord selection : selections) {
ids.add(selection.getAttributeAsString(FIELD_ADDRESS.propertyName()));
}
return ids;
}
private boolean isEnabled(boolean parentsOpinion, ListGridRecord[] selection) {
if (!parentsOpinion) {
return false;
}
for (ListGridRecord storageNodeRecord : selection) {
if (storageNodeRecord.getAttribute(FIELD_RESOURCE_ID.propertyName()) == null) {
return false;
}
}
return true;
}
private boolean isDeployable(boolean parentsOpinion, ListGridRecord[] selection) {
if (!parentsOpinion || !isEnabled(parentsOpinion, selection)) {
return false;
}
for (ListGridRecord storageNodeRecord : selection) {
if ("NORMAL".equals(storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))
|| "JOINING".equals(storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))
|| "LEAVING".equals(storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))) {
return false;
}
}
List<ListGridRecord> selectionList = Arrays.asList(selection);
ListGridRecord[] allRecords = getListGrid().getRecords();
for (ListGridRecord storageNodeRecord : allRecords) {
if (!selectionList.contains(storageNodeRecord)) {
if (StorageNode.Status.JOINING.toString().equals(
storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))
|| StorageNode.Status.LEAVING.toString().equals(
storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))) {
return false;
}
}
}
return true;
}
private boolean isUndeployable(boolean parentsOpinion, ListGridRecord[] selection, String availIcon) {
if (!parentsOpinion || !isEnabled(parentsOpinion, selection)) {
return false;
}
for (ListGridRecord storageNodeRecord : selection) {
if ("JOINING".equals(storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))
|| "LEAVING".equals(storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))) {
return false;
}
}
List<ListGridRecord> selectionList = Arrays.asList(selection);
ListGridRecord[] allRecords = getListGrid().getRecords();
int nodesInNormalCouner = 0;
for (ListGridRecord storageNodeRecord : allRecords) {
if (!selectionList.contains(storageNodeRecord)) {
if (StorageNode.Status.JOINING.toString().equals(
storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))
|| StorageNode.Status.LEAVING.toString().equals(
storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))) {
return false;
}
}
if (StorageNode.Status.NORMAL.toString().equals(
storageNodeRecord.getAttributeAsString(FIELD_STATUS.propertyName()))
&& availIcon.equals(storageNodeRecord.getAttributeAsObject(FIELD_AVAILABILITY.propertyName()))) {
nodesInNormalCouner++;
}
}
return nodesInNormalCouner > 1;
}
@Override
protected String getBasePath() {
return VIEW_PATH;
}
}