modules/core/domain/src/main/java/org/rhq/core/domain/cloud/StorageClusterSettings.java | 26 modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java | 316 +++++++--- modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/core/StartupBean.java | 11 modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/scheduler/jobs/StorageClusterCredentialsJob.java | 45 + modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClientManagerBean.java | 108 ++- modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClusterSettingsManagerBean.java | 35 + modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/system/SystemManagerBean.java | 6 modules/enterprise/server/server-metrics/src/main/java/org/rhq/server/metrics/StorageSession.java | 18 8 files changed, 442 insertions(+), 123 deletions(-)
New commits: commit d5cd83d5008374b87e590197aad459cf09dbc737 Author: Stefan Negrea snegrea@redhat.com Date: Mon Oct 28 16:02:06 2013 -0500
[BZ 1016175] Update and simplifiy some verbiage based on feedback.
(cherry picked from commit dda907b836f7b127e9f4968a8237fce49ae9aa51)
diff --git a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java index 18079e1..066b9ab 100644 --- a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java +++ b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java @@ -148,8 +148,8 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre private void prepareForms() { setWidth100(); clusterForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'>" - + "<b>Cluster Settings</b></span><br/>On save, these setting will not be propagated to existing Storage" - + " Nodes. Please review the documentation on how update the CQL and Gossip ports for all Storage Nodes.</div>"); + + "<b>Cluster Settings</b></span><br/>Before changing these settings, storage nodes require updates to have equivalent port numbers. " + + "Port changes below will only be saved in the RHQ server configuration.</div>");
List<FormItem> items = buildHeaderItems(); IsIntegerValidator validator = new IsIntegerValidator(); @@ -184,7 +184,7 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre clusterForm.setFields(items.toArray(new FormItem[items.size()]));
deploymentForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'>" - + "<b>Deployment Settings</b></span><br/>Only applies to new installations.</div>"); + + "<b>New Deployment Settings</b></span><br/>Only applies to new installations.</div>"); FormItemBuilder.resetOddRow(); items = buildHeaderItems();
commit 9f460f107d825ac3df5b5c485affc9761935b559 Author: Stefan Negrea snegrea@redhat.com Date: Mon Oct 28 16:01:43 2013 -0500
[BZ 1016175] Workaround to SmartGWT bug: password validators were changing the focus after each user input making it unusable
diff --git a/modules/core/domain/src/main/java/org/rhq/core/domain/cloud/StorageClusterSettings.java b/modules/core/domain/src/main/java/org/rhq/core/domain/cloud/StorageClusterSettings.java index 52b17b5..a2251fa 100644 --- a/modules/core/domain/src/main/java/org/rhq/core/domain/cloud/StorageClusterSettings.java +++ b/modules/core/domain/src/main/java/org/rhq/core/domain/cloud/StorageClusterSettings.java @@ -88,6 +88,6 @@ public class StorageClusterSettings implements Serializable { @Override public String toString() { return "StorageClusterSettings[cqlPort=" + cqlPort + ", gossipPort=" + gossipPort + ", automaticDeployment=" - + automaticDeployment + ", username=" + username + ", passwordHash=********]"; + + automaticDeployment + ", username (read-only)=" + username + ", passwordHash=********]"; } } diff --git a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java index 6834c1b..18079e1 100644 --- a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java +++ b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java @@ -147,7 +147,8 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre
private void prepareForms() { setWidth100(); - clusterForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'><b>Cluster Settings</b></span><br/>On save, these setting will not be propagated to existing Storage" + clusterForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'>" + + "<b>Cluster Settings</b></span><br/>On save, these setting will not be propagated to existing Storage" + " Nodes. Please review the documentation on how update the CQL and Gossip ports for all Storage Nodes.</div>");
List<FormItem> items = buildHeaderItems(); @@ -182,7 +183,8 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre items.addAll(gossipPortItems); clusterForm.setFields(items.toArray(new FormItem[items.size()]));
- deploymentForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'><b>Deployment Settings</b></span><br/>Only applies to new installations.</div>"); + deploymentForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'>" + + "<b>Deployment Settings</b></span><br/>Only applies to new installations.</div>"); FormItemBuilder.resetOddRow(); items = buildHeaderItems();
@@ -206,7 +208,8 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre items.addAll(automaticDeploymentItems); deploymentForm.setFields(items.toArray(new FormItem[items.size()]));
- credentialsForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'><b>Cluster Credentials</b></span><br/>Password changes are propagated to the Storage Cluster.</div>"); + credentialsForm = buildForm("<div align='left'><span style='text-align: left; font-family: Arial, Verdana, sans-serif !important;'>" + + "<b>Cluster Credentials</b></span><br/>Password changes are propagated to the Storage Cluster.</div>"); FormItemBuilder.resetOddRow(); items = buildHeaderItems();
@@ -223,6 +226,9 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre
// password field StringLengthValidator passwordValidator1 = new StringLengthValidator(6, 100, false); + passwordValidator1.setErrorMessage("The password length must be at least 6 characters."); + // due to SmartGWT bug that changes focus after each input (https://code.google.com/p/smartgwt/issues/detail?id=309) + passwordValidator1.setValidateOnChange(false); builder = new FormItemBuilder(); List<FormItem> passwordItems = builder .withName(FIELD_PASSWORD) @@ -237,9 +243,14 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre
// password_verify field builder = new FormItemBuilder(); + passwordValidator1 = new StringLengthValidator(6, 100, false); + passwordValidator1.setErrorMessage("The password length must be at least 6 characters."); MatchesFieldValidator passwordValidator2 = new MatchesFieldValidator(); passwordValidator2.setOtherField(FIELD_PASSWORD); passwordValidator2.setErrorMessage("This should be the same string as in the Password field."); + // due to same bug in SmartGWT as above + passwordValidator1.setValidateOnChange(false); + passwordValidator2.setValidateOnChange(false); List<FormItem> passwordVerifyItems = builder.withName(FIELD_PASSWORD_VERIFY).withTitle("Verify Password") .withValue(settings.getPasswordHash()).withDescription("Validation (needs to match Password)") .withReadOnlySetTo(readOnly).withValidators(passwordValidator1, passwordValidator2)
commit c7650fa9381a4d3bd30ae69d1cc87ae54db10ec0 Author: Stefan Negrea snegrea@redhat.com Date: Mon Oct 28 16:00:59 2013 -0500
[BZ 1016175] Add support for changing the storage password
Add code to update the storage session when credentials get update in the system settings table. Created a quartz job to trigger the refresh every 2 minutes.
The process to refresh the session is as follows: 1) Get new credentials from the database 2) Create a new session with new credentials 3) Replace existing session with newly created one 4) Allow few minutes for existing session drainage 5) Shutdown old session
Other changes: - Add code to update Cassandra password via a direct CQL query using the existing open session. - Update log level and text for the storage cluster credentials update job. - Make the username read-only; adding more intuitive description to the other properties.
diff --git a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java index 11846df..6834c1b 100644 --- a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java +++ b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java @@ -59,7 +59,8 @@ import org.rhq.coregui.client.util.message.Message; */ public class ClusterConfigurationEditor extends EnhancedVLayout implements RefreshableView {
- private EnhancedDynamicForm form; + private EnhancedDynamicForm clusterForm; + private EnhancedDynamicForm deploymentForm; private EnhancedDynamicForm credentialsForm; private EnhancedIButton saveButton; private StorageClusterSettings settings; @@ -123,46 +124,67 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre return unsetHeader; }
- @Override - protected void onDraw() { - super.onDraw(); - refresh(); - } - - private void prepareForms() { - setWidth100(); - form = new EnhancedDynamicForm(); + private EnhancedDynamicForm buildForm(String groupTitle) { + EnhancedDynamicForm form = new EnhancedDynamicForm(); form.setHiliteRequiredFields(true); form.setNumCols(3); form.setCellPadding(5); form.setColWidths(190, 220, "*"); form.setIsGroup(true); - form.setGroupTitle("Cluster Wide Settings"); + form.setGroupTitle(groupTitle); form.setBorder("1px solid #AAA"); form.setWidth100(); form.setOverflow(Overflow.VISIBLE); - form.setExtraSpace(10); + form.setExtraSpace(15); + return form; + } + + @Override + protected void onDraw() { + super.onDraw(); + refresh(); + } + + private void prepareForms() { + setWidth100(); + clusterForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'><b>Cluster Settings</b></span><br/>On save, these setting will not be propagated to existing Storage" + + " Nodes. Please review the documentation on how update the CQL and Gossip ports for all Storage Nodes.</div>");
List<FormItem> items = buildHeaderItems(); IsIntegerValidator validator = new IsIntegerValidator(); - + // cql port field FormItemBuilder builder = new FormItemBuilder(); - List<FormItem> cqlPortItems = builder.withName(FIELD_CQL_PORT).withTitle("CQL Port") + List<FormItem> cqlPortItems = builder + .withName(FIELD_CQL_PORT) + .withTitle("CQL Port") .withValue(String.valueOf(settings.getCqlPort())) - .withDescription("The port on which the Storage Nodes listens for CQL client connections.") - .withValidators(validator).build(); + .withDescription( + "Port on which the Storage Nodes listen for CQL client connections. On save this setting" + + " will not be propagated to existing Storage Nodes. Please review the documentation on how update" + + " the CQL port for all Storage Nodes. <b>Warning:</b> if this setting does not match the configured" + + " Storage Cluster CQL port, the server will not be able to communicate with the Storage Cluster" + + " and will go into maintenance mode.").withValidators(validator).build(); items.addAll(cqlPortItems);
// gossip port field builder = new FormItemBuilder(); - List<FormItem> gossipPortItems = builder.withName(FIELD_GOSSIP_PORT).withTitle("Gossip Port") + List<FormItem> gossipPortItems = builder + .withName(FIELD_GOSSIP_PORT) + .withTitle("Gossip Port") .withValue(String.valueOf(settings.getGossipPort())) - .withDescription("The port used for internode communication in the storage cluster.") - .withValidators(validator).build(); + .withDescription( + "The port used for internode communication in the Storage Cluster. On save this setting" + + " will not be propagated to existing Storage Nodes. Please review the documentation on how update" + + " the Gossip port for all Storage Nodes. <b>Warning:</b> if this setting does not match the" + + " configured Storage Cluster Gossip port, any new Storage Nodes will be able to communicate" + + " and be part of the existing Storage Cluster.").withValidators(validator).build(); items.addAll(gossipPortItems); - - + clusterForm.setFields(items.toArray(new FormItem[items.size()])); + + deploymentForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'><b>Deployment Settings</b></span><br/>Only applies to new installations.</div>"); + FormItemBuilder.resetOddRow(); + items = buildHeaderItems();
// automatic deployment field builder = new FormItemBuilder(); @@ -171,8 +193,9 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre .withTitle("Automatic Deployment") .withValue(Boolean.toString(settings.getAutomaticDeployment())) .withDescription( - "If this is set, the newly installed storage nodes will be automatically deployed to the storage cluster.") - .withReadOnlySetTo(readOnly).build((FormItem) GWT.create(RadioGroupItem.class)); + "If this is set, the newly installed storage nodes will be automatically deployed to the storage cluster." + + " It only applies to new installations.").withReadOnlySetTo(readOnly) + .build((FormItem) GWT.create(RadioGroupItem.class)); RadioGroupItem autoDeployRadio = (RadioGroupItem) automaticDeploymentItems.get(1); autoDeployRadio.setVertical(false); LinkedHashMap<String, String> values = new LinkedHashMap<String, String>(2); @@ -181,58 +204,57 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre autoDeployRadio.setValueMap(values); autoDeployRadio.setValue(settings.getAutomaticDeployment()); items.addAll(automaticDeploymentItems); - - form.setFields(items.toArray(new FormItem[items.size()])); - - credentialsForm = new EnhancedDynamicForm(); - credentialsForm.setHiliteRequiredFields(true); - credentialsForm.setNumCols(3); - credentialsForm.setCellPadding(5); - credentialsForm.setColWidths(190, 220, "*"); - credentialsForm.setIsGroup(true); - credentialsForm.setGroupTitle("Storage Cluster Credentials"); - credentialsForm.setBorder("1px solid #AAA"); - credentialsForm.setWidth100(); - credentialsForm.setOverflow(Overflow.VISIBLE); - + deploymentForm.setFields(items.toArray(new FormItem[items.size()])); + + credentialsForm = buildForm("<div align='left'><span style='font-family: Arial, Verdana, sans-serif !important;'><b>Cluster Credentials</b></span><br/>Password changes are propagated to the Storage Cluster.</div>"); FormItemBuilder.resetOddRow(); items = buildHeaderItems();
// username field StringLengthValidator usernameValidator = new StringLengthValidator(4, 100, false); builder = new FormItemBuilder(); - List<FormItem> usernameItems = builder.withName(FIELD_USERNAME).withTitle("Username") - .withValue(settings.getUsername()).withDescription("Username").withReadOnlySetTo(readOnly) - .withValidators(usernameValidator).build(); + List<FormItem> usernameItems = builder + .withName(FIELD_USERNAME) + .withTitle("Username") + .withDescription( + "Username for Storage Node. This property is read-only because changes to the username are not allowed.") + .withValue(settings.getUsername()).withReadOnlySetTo(true).withValidators(usernameValidator).build(); items.addAll(usernameItems);
// password field StringLengthValidator passwordValidator1 = new StringLengthValidator(6, 100, false); builder = new FormItemBuilder(); - List<FormItem> passwordItems = builder.withName(FIELD_PASSWORD).withTitle("Password") - .withValue(settings.getPasswordHash()).withDescription("Password").withReadOnlySetTo(readOnly) - .withValidators(passwordValidator1).build((FormItem) GWT.create(PasswordItem.class)); + List<FormItem> passwordItems = builder + .withName(FIELD_PASSWORD) + .withTitle("Password") + .withDescription( + "Password for all Storage Node CQL authentication. Changing will get propagated to the all deployed" + + " Storage Nodes and appliad to newly installed nodes. All HA servers will have Storage Cluster" + + " sessions refreshed automatically to use the new password.") + .withValue(settings.getPasswordHash()).withReadOnlySetTo(readOnly).withValidators(passwordValidator1) + .build((FormItem) GWT.create(PasswordItem.class)); items.addAll(passwordItems);
// password_verify field builder = new FormItemBuilder(); MatchesFieldValidator passwordValidator2 = new MatchesFieldValidator(); passwordValidator2.setOtherField(FIELD_PASSWORD); - passwordValidator2.setErrorMessage("This should be the same string as the Password."); + passwordValidator2.setErrorMessage("This should be the same string as in the Password field."); List<FormItem> passwordVerifyItems = builder.withName(FIELD_PASSWORD_VERIFY).withTitle("Verify Password") - .withValue(settings.getPasswordHash()).withDescription("This should be the same string as the Password.") + .withValue(settings.getPasswordHash()).withDescription("Validation (needs to match Password)") .withReadOnlySetTo(readOnly).withValidators(passwordValidator1, passwordValidator2) .build((FormItem) GWT.create(PasswordItem.class)); - + items.addAll(passwordVerifyItems); credentialsForm.setFields(items.toArray(new FormItem[items.size()])); - + LayoutSpacer spacer = new LayoutSpacer(); spacer.setWidth100();
ToolStrip toolStrip = buildToolStrip(); - setMembers(form, credentialsForm, spacer, toolStrip); - form.validate(); + setMembers(clusterForm, deploymentForm, credentialsForm, spacer, toolStrip); + clusterForm.validate(); + deploymentForm.validate(); credentialsForm.validate(); markForRedraw(); } @@ -246,7 +268,7 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre saveButton = new EnhancedIButton(MSG.common_button_save()); saveButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent clickEvent) { - if (form.validate() && credentialsForm.validate()) { + if (clusterForm.validate() && deploymentForm.validate() && credentialsForm.validate()) { SC.ask( "Changing the cluster wide configuration will eventually affect all the storage nodes. Do you want to continue?", new BooleanCallback() { @@ -271,10 +293,12 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre }
private StorageClusterSettings updateSettings() { - settings.setCqlPort(Integer.parseInt(form.getValueAsString(FIELD_CQL_PORT))); - settings.setGossipPort(Integer.parseInt(form.getValueAsString(FIELD_GOSSIP_PORT))); - settings.setAutomaticDeployment(Boolean.parseBoolean(form.getValueAsString(FIELD_AUTOMATIC_DEPLOYMENT))); - + settings.setCqlPort(Integer.parseInt(clusterForm.getValueAsString(FIELD_CQL_PORT))); + settings.setGossipPort(Integer.parseInt(clusterForm.getValueAsString(FIELD_GOSSIP_PORT))); + + settings.setAutomaticDeployment(Boolean.parseBoolean(deploymentForm + .getValueAsString(FIELD_AUTOMATIC_DEPLOYMENT))); + // set the credentials only if there was a change String wannabeUsername = credentialsForm.getValueAsString(FIELD_USERNAME); settings.setUsername(wannabeUsername.equals(settings.getUsername()) ? null : wannabeUsername); @@ -282,7 +306,7 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre settings.setPasswordHash(wannabePassword.equals(settings.getPasswordHash()) ? null : wannabePassword); return settings; } - + private static class FormItemBuilder { private String name; private String title; @@ -290,47 +314,47 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre private String description; private Validator[] validators; private boolean readOnly; - + private static boolean oddRow = true; - + public static void resetOddRow() { oddRow = true; } - + public FormItemBuilder withName(String name) { this.name = name; return this; } - + public FormItemBuilder withTitle(String title) { this.title = title; return this; } - + public FormItemBuilder withValue(String value) { this.value = value; return this; } - + public FormItemBuilder withDescription(String description) { this.description = description; return this; } - + public FormItemBuilder withValidators(Validator... validators) { this.validators = validators; return this; } - + public FormItemBuilder withReadOnlySetTo(boolean readOnly) { this.readOnly = readOnly; return this; } - + public List<FormItem> build() { return build(new TextItem()); } - + // GWT doesn't support reflection by default, therefore this "hack" public List<FormItem> build(FormItem valueItem) { List<FormItem> fields = new ArrayList<FormItem>(); @@ -365,6 +389,6 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre oddRow = !oddRow; return fields; } - + } } diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/core/StartupBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/core/StartupBean.java index 941ae93..90e2345 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/core/StartupBean.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/core/StartupBean.java @@ -87,6 +87,7 @@ import org.rhq.enterprise.server.scheduler.jobs.DynaGroupAutoRecalculationJob; import org.rhq.enterprise.server.scheduler.jobs.PurgePluginsJob; import org.rhq.enterprise.server.scheduler.jobs.PurgeResourceTypesJob; import org.rhq.enterprise.server.scheduler.jobs.SavedSearchResultCountRecalculationJob; +import org.rhq.enterprise.server.scheduler.jobs.StorageClusterCredentialsJob; import org.rhq.enterprise.server.scheduler.jobs.StorageClusterInitJob; import org.rhq.enterprise.server.scheduler.jobs.StorageClusterReadRepairJob; import org.rhq.enterprise.server.storage.StorageClientManagerBean; @@ -774,6 +775,16 @@ public class StartupBean implements StartupLocal { log.error("Cannot create storage cluster init job", e); } } + + try { + // Storage cluster credentials refresh job + final long initialDelay = 1000L * 60 * 5; + final long interval = 1000L * 60 * 2; + schedulerBean.scheduleSimpleRepeatingJob(StorageClusterCredentialsJob.class, true, false, initialDelay, + interval); + } catch (Exception e) { + log.error("Cannot schedule storage cluster credentials refresh job.", e); + }
try { String cronString = "0 30 0 ? * SUN *"; // every sunday starting at 00:30. diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/scheduler/jobs/StorageClusterCredentialsJob.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/scheduler/jobs/StorageClusterCredentialsJob.java new file mode 100644 index 0000000..f5b2bf5 --- /dev/null +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/scheduler/jobs/StorageClusterCredentialsJob.java @@ -0,0 +1,45 @@ +/* + * 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.enterprise.server.scheduler.jobs; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +import org.rhq.enterprise.server.util.LookupUtil; + +/** + * @author Stefan Negrea + */ +public class StorageClusterCredentialsJob extends AbstractStatefulJob { + + private Log log = LogFactory.getLog(StorageClusterCredentialsJob.class); + + @Override + public void executeJobCode(JobExecutionContext context) throws JobExecutionException { + log.debug("Run credential update job for the storage cluster."); + boolean success = LookupUtil.getStorageClientManager().refreshCredentialsAndSession(); + if (!success) { + log.error("Storage session credentials not succesfully refreshed"); + } else { + log.debug("Storage session credentials succesfully refreshed"); + } + } +} diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClientManagerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClientManagerBean.java index c57d7cd..f28dc3b 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClientManagerBean.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClientManagerBean.java @@ -88,6 +88,10 @@ public class StorageClientManagerBean { private boolean initialized; private StorageClusterMonitor storageClusterMonitor;
+ private String cachedStorageUsername; + private String cachedStoragePassword; + + /** * @return true if the storage subsystem is running */ @@ -101,37 +105,9 @@ public class StorageClientManagerBean {
log.info("Initializing storage client subsystem");
- // Always get the creds from the DB, system props may not be up to date at install time - // the code assumes the passwords to be obfuscated, because they can also come that way from other sources - // (like property files). So let's make our lives easy and always use obfuscated passwords. - SystemSettings settings = systemManager.getObfuscatedSystemSettings(true); - final String username = settings.get(SystemSetting.STORAGE_USERNAME); - final String password = settings.get(SystemSetting.STORAGE_PASSWORD); - - List<StorageNode> storageNodes = new ArrayList<StorageNode>(); - for (StorageNode storageNode : storageNodeManager.getStorageNodes()) { - // We only want clustered nodes here because we won't be able to connect to - // node that is not part of the cluster. The filtering here on the operation - // mode is somewhat convservative because we could also include ADD_MAINTENANCE - // and REMOVE_MAINTENANCE, but this errors on the side of being safe. Lastly, - // if a storage node does not have a resource, then that means it was was - // deployed prior to installing the server. - if (storageNode.getOperationMode() == StorageNode.OperationMode.NORMAL - || storageNode.getOperationMode() == StorageNode.OperationMode.MAINTENANCE - || storageNode.getResource() == null) { - storageNodes.add(storageNode); - } - } - - if (storageNodes.isEmpty()) { - throw new IllegalStateException( - "There is no storage node metadata stored in the relational database. This may have happened as a " - + "result of running dbsetup or deleting rows from rhq_storage_node table. Please re-install the " - + "storage node to fix this issue."); - } - + Session wrappedSession; try { - checkSchemaCompability(username, password, storageNodes); + wrappedSession = createSession(); } catch (NoHostAvailableException e) { initialized = false; log.warn("Storage client subsystem wasn't initialized because it wasn't possible to connect to the" @@ -139,7 +115,8 @@ public class StorageClientManagerBean { + " as soon as possible."); return initialized; } - Session wrappedSession = createSession(username, password, storageNodes); + + wrappedSession = createSession(); session = new StorageSession(wrappedSession);
storageClusterMonitor = new StorageClusterMonitor(); @@ -155,6 +132,40 @@ public class StorageClientManagerBean { return initialized; }
+ public synchronized boolean refreshCredentialsAndSession() { + if (!initialized) { + if (log.isDebugEnabled()) { + log.debug("Storage client subsystem not initialized. Skipping session refresh."); + } + return false; + } + + SystemSettings settings = systemManager.getObfuscatedSystemSettings(true); + String username = settings.get(SystemSetting.STORAGE_USERNAME); + String password = settings.get(SystemSetting.STORAGE_PASSWORD); + + if ((username != null && !username.equals(this.cachedStorageUsername)) + || (password != null && !password.equals(this.cachedStoragePassword))) { + + Session wrappedSession; + try { + wrappedSession = createSession(); + } catch (NoHostAvailableException e) { + initialized = false; + log.warn("Storage client subsystem wasn't initialized because it wasn't possible to connect to the" + + " storage cluster. The RHQ server is set to MAINTENANCE mode. Please start the storage cluster" + + " as soon as possible."); + return initialized; + } + + session.registerNewSession(wrappedSession); + initialized = true; + return true; + } + + return true; + } + /** * Checks storage node schema compatibility. * @@ -229,7 +240,38 @@ public class StorageClientManagerBean { return storageClusterMonitor != null && storageClusterMonitor.isClusterAvailable(); }
- private Session createSession(String username, String password, List<StorageNode> storageNodes) { + private Session createSession() { + // Always get the creds from the DB, system props may not be up to date at install time + // the code assumes the passwords to be obfuscated, because they can also come that way from other sources + // (like property files). So let's make our lives easy and always use obfuscated passwords. + SystemSettings settings = systemManager.getObfuscatedSystemSettings(true); + this.cachedStorageUsername = settings.get(SystemSetting.STORAGE_USERNAME); + this.cachedStoragePassword = settings.get(SystemSetting.STORAGE_PASSWORD); + + List<StorageNode> storageNodes = new ArrayList<StorageNode>(); + for (StorageNode storageNode : storageNodeManager.getStorageNodes()) { + // We only want clustered nodes here because we won't be able to connect to + // node that is not part of the cluster. The filtering here on the operation + // mode is somewhat convservative because we could also include ADD_MAINTENANCE + // and REMOVE_MAINTENANCE, but this errors on the side of being safe. Lastly, + // if a storage node does not have a resource, then that means it was was + // deployed prior to installing the server. + if (storageNode.getOperationMode() == StorageNode.OperationMode.NORMAL + || storageNode.getOperationMode() == StorageNode.OperationMode.MAINTENANCE + || storageNode.getResource() == null) { + storageNodes.add(storageNode); + } + } + + if (storageNodes.isEmpty()) { + throw new IllegalStateException( + "There is no storage node metadata stored in the relational database. This may have happened as a " + + "result of running dbsetup or deleting rows from rhq_storage_node table. Please re-install the " + + "storage node to fix this issue."); + } + + checkSchemaCompability(this.cachedStorageUsername, this.cachedStoragePassword, storageNodes); + if (log.isDebugEnabled()) { log.debug("Initializing session to connect to storage node cluster"); } @@ -252,7 +294,7 @@ public class StorageClientManagerBean { }
cluster = new ClusterBuilder().addContactPoints(hostNames.toArray(new String[hostNames.size()])) - .withCredentialsObfuscated(username, password).withPort(port) + .withCredentialsObfuscated(this.cachedStorageUsername, this.cachedStoragePassword).withPort(port) .withLoadBalancingPolicy(new RoundRobinPolicy()) .withRetryPolicy(new LoggingRetryPolicy(DefaultRetryPolicy.INSTANCE)).withCompression(compression).build();
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClusterSettingsManagerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClusterSettingsManagerBean.java index 2a47670..b14db38 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClusterSettingsManagerBean.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClusterSettingsManagerBean.java @@ -10,6 +10,7 @@ import org.rhq.core.domain.common.composite.SystemSetting; import org.rhq.core.domain.common.composite.SystemSettings; import org.rhq.enterprise.server.authz.RequiredPermission; import org.rhq.enterprise.server.system.SystemManagerLocal; +import org.rhq.server.metrics.StorageSession;
/** * @author John Sanda @@ -17,9 +18,14 @@ import org.rhq.enterprise.server.system.SystemManagerLocal; @Stateless public class StorageClusterSettingsManagerBean implements StorageClusterSettingsManagerLocal {
+ private static final String UPDATE_PASSWORD_QUERY = "ALTER USER '%s' WITH PASSWORD '%s'"; + @EJB private SystemManagerLocal systemManager;
+ @EJB + private StorageClientManagerBean storageClienManager; + @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public StorageClusterSettings getClusterSettings(Subject subject) { @@ -76,9 +82,20 @@ public class StorageClusterSettingsManagerBean implements StorageClusterSettings settings.put(SystemSetting.STORAGE_USERNAME, clusterSettings.getUsername()); } if (clusterSettings.getPasswordHash() != null) { + this.updateStorageClusterCredentials(clusterSettings); settings.put(SystemSetting.STORAGE_PASSWORD, clusterSettings.getPasswordHash()); } systemManager.setStorageClusterSettings(subject, settings); }
+ private void updateStorageClusterCredentials(StorageClusterSettings newClusterSettings) { + SystemSettings currentSettings = systemManager.getUnmaskedSystemSettings(true); + String currentPassword = currentSettings.get(SystemSetting.STORAGE_PASSWORD); + + if (!currentPassword.equals(newClusterSettings.getPasswordHash())) { + StorageSession session = this.storageClienManager.getSession(); + session.execute(String.format(UPDATE_PASSWORD_QUERY, currentSettings.get(SystemSetting.STORAGE_USERNAME), + newClusterSettings.getPasswordHash())); + } + } } diff --git a/modules/enterprise/server/server-metrics/src/main/java/org/rhq/server/metrics/StorageSession.java b/modules/enterprise/server/server-metrics/src/main/java/org/rhq/server/metrics/StorageSession.java index 60f3fbd..34eada1 100644 --- a/modules/enterprise/server/server-metrics/src/main/java/org/rhq/server/metrics/StorageSession.java +++ b/modules/enterprise/server/server-metrics/src/main/java/org/rhq/server/metrics/StorageSession.java @@ -34,6 +34,24 @@ public class StorageSession implements Host.StateListener { this.wrappedSession.getCluster().register(this); }
+ public void registerNewSession(Session newWrappedSession) { + Session oldWrappedSession = this.wrappedSession; + + + this.wrappedSession = newWrappedSession; + this.wrappedSession.getCluster().register(this); + + oldWrappedSession.getCluster().unregister(this); + + // initial waiting before the first check + try { + Thread.sleep(60000L); + } catch (InterruptedException e) { + // nothing + } + oldWrappedSession.shutdown(); + } + public void addStorageStateListener(StorageStateListener listener) { listeners.add(listener); }
commit 1e6aee931a92ff658c2def4d5714e81e0a24b4a5 Author: Stefan Negrea snegrea@redhat.com Date: Mon Oct 28 15:53:40 2013 -0500
[BZ 1016175] Add support for changing the storage password
- adding length validators for username and password; moving the storage node credentials to its own config section - renaming "password" field to "passwordHash"; setting the password to null if there was no change at all to it - adding support for storing the C* credentials to system settings db table - adding length validators for username and password; moving the storage node credentials to its own config section
(cherry picked from commit f592690d232393b5127b637b2541278198f8b001)
diff --git a/modules/core/domain/src/main/java/org/rhq/core/domain/cloud/StorageClusterSettings.java b/modules/core/domain/src/main/java/org/rhq/core/domain/cloud/StorageClusterSettings.java index b44a2b5..52b17b5 100644 --- a/modules/core/domain/src/main/java/org/rhq/core/domain/cloud/StorageClusterSettings.java +++ b/modules/core/domain/src/main/java/org/rhq/core/domain/cloud/StorageClusterSettings.java @@ -14,6 +14,10 @@ public class StorageClusterSettings implements Serializable { private int gossipPort;
private Boolean automaticDeployment; + + private String username; + + private String passwordHash;
public int getCqlPort() { return cqlPort; @@ -39,6 +43,22 @@ public class StorageClusterSettings implements Serializable { this.automaticDeployment = automaticDeployment; }
+ public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPasswordHash() { + return passwordHash; + } + + public void setPasswordHash(String passwordHash) { + this.passwordHash = passwordHash; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -49,6 +69,8 @@ public class StorageClusterSettings implements Serializable { if (cqlPort != that.cqlPort) return false; if (gossipPort != that.gossipPort) return false; if (automaticDeployment != that.automaticDeployment) return false; + if (username != that.username) return false; + if (passwordHash != that.passwordHash) return false;
return true; } @@ -58,12 +80,14 @@ public class StorageClusterSettings implements Serializable { int result = cqlPort; result = 29 * result + gossipPort; result = 29 * result + (automaticDeployment ? 1231 : 1237); + result = 29 * result + (username == null ? 0 : username.hashCode()); + result = 29 * result + (passwordHash == null ? 0 : passwordHash.hashCode()); return result; }
@Override public String toString() { return "StorageClusterSettings[cqlPort=" + cqlPort + ", gossipPort=" + gossipPort + ", automaticDeployment=" - + automaticDeployment + "]"; + + automaticDeployment + ", username=" + username + ", passwordHash=********]"; } } diff --git a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java index 188a993..11846df 100644 --- a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java +++ b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/client/admin/storage/ClusterConfigurationEditor.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List;
+import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.AsyncCallback; import com.smartgwt.client.types.Alignment; import com.smartgwt.client.types.Overflow; @@ -30,10 +31,12 @@ import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.events.ClickEvent; import com.smartgwt.client.widgets.events.ClickHandler; import com.smartgwt.client.widgets.form.fields.FormItem; +import com.smartgwt.client.widgets.form.fields.PasswordItem; import com.smartgwt.client.widgets.form.fields.RadioGroupItem; import com.smartgwt.client.widgets.form.fields.StaticTextItem; import com.smartgwt.client.widgets.form.fields.TextItem; import com.smartgwt.client.widgets.form.validator.IsIntegerValidator; +import com.smartgwt.client.widgets.form.validator.MatchesFieldValidator; import com.smartgwt.client.widgets.form.validator.Validator; import com.smartgwt.client.widgets.layout.LayoutSpacer; import com.smartgwt.client.widgets.toolbar.ToolStrip; @@ -42,6 +45,7 @@ import org.rhq.core.domain.cloud.StorageClusterSettings; import org.rhq.coregui.client.CoreGUI; import org.rhq.coregui.client.RefreshableView; import org.rhq.coregui.client.components.form.EnhancedDynamicForm; +import org.rhq.coregui.client.components.form.StringLengthValidator; import org.rhq.coregui.client.gwt.GWTServiceLookup; import org.rhq.coregui.client.util.enhanced.EnhancedIButton; import org.rhq.coregui.client.util.enhanced.EnhancedToolStrip; @@ -56,14 +60,17 @@ import org.rhq.coregui.client.util.message.Message; public class ClusterConfigurationEditor extends EnhancedVLayout implements RefreshableView {
private EnhancedDynamicForm form; + private EnhancedDynamicForm credentialsForm; private EnhancedIButton saveButton; - private boolean oddRow; private StorageClusterSettings settings; private final boolean readOnly;
private static String FIELD_CQL_PORT = "cql_port"; private static String FIELD_GOSSIP_PORT = "gossip_port"; private static String FIELD_AUTOMATIC_DEPLOYMENT = "automatic_deployment"; + private static String FIELD_USERNAME = "username"; + private static String FIELD_PASSWORD = "password"; + private static String FIELD_PASSWORD_VERIFY = "verify_password";
public ClusterConfigurationEditor(boolean readOnly) { super(); @@ -81,7 +88,7 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre @Override public void onSuccess(StorageClusterSettings settings) { ClusterConfigurationEditor.this.settings = settings; - prepareForm(); + prepareForms(); } }); } @@ -100,48 +107,6 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre }); }
- private List<FormItem> buildOneFormRowWithValidator(String name, String title, String value, String description, - Validator validator) { - return buildOneFormRow(name, title, value, description, false, validator); - } - - private List<FormItem> buildOneFormRow(String name, String title, String value, String description, - boolean unitsDropdown, Validator validator) { - List<FormItem> fields = new ArrayList<FormItem>(); - StaticTextItem nameItem = new StaticTextItem(); - nameItem.setStartRow(true); - nameItem.setValue("<b>" + title + "</b>"); - nameItem.setShowTitle(false); - nameItem.setCellStyle(oddRow ? "OddRow" : "EvenRow"); - fields.add(nameItem); - - FormItem valueItem = null; - valueItem = new TextItem(); - valueItem.setName(name); - valueItem.setValue(value); - valueItem.setWidth(220); - if (validator != null) { - valueItem.setValidators(validator); - } - valueItem.setValidateOnChange(true); - valueItem.setAlign(Alignment.CENTER); - valueItem.setShowTitle(false); - valueItem.setRequired(true); - valueItem.setCellStyle(oddRow ? "OddRow" : "EvenRow"); - valueItem.setDisabled(readOnly); - fields.add(valueItem); - - StaticTextItem descriptionItem = new StaticTextItem(); - descriptionItem.setValue(description); - descriptionItem.setShowTitle(false); - descriptionItem.setEndRow(true); - descriptionItem.setCellStyle(oddRow ? "OddRow" : "EvenRow"); - fields.add(descriptionItem); - - oddRow = !oddRow; - return fields; - } - private List<FormItem> buildHeaderItems() { List<FormItem> fields = new ArrayList<FormItem>(); fields.add(createHeaderTextItem(MSG.view_configEdit_property())); @@ -164,7 +129,8 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre refresh(); }
- private void prepareForm() { + private void prepareForms() { + setWidth100(); form = new EnhancedDynamicForm(); form.setHiliteRequiredFields(true); form.setNumCols(3); @@ -173,49 +139,101 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre form.setIsGroup(true); form.setGroupTitle("Cluster Wide Settings"); form.setBorder("1px solid #AAA"); - oddRow = true; + form.setWidth100(); + form.setOverflow(Overflow.VISIBLE); + form.setExtraSpace(10);
List<FormItem> items = buildHeaderItems(); IsIntegerValidator validator = new IsIntegerValidator(); - items.addAll(buildOneFormRowWithValidator(FIELD_CQL_PORT, "CQL Port", String.valueOf(settings.getCqlPort()), - "The port on which the Storage Nodes listens for CQL client connections.", validator)); + + // cql port field + FormItemBuilder builder = new FormItemBuilder(); + List<FormItem> cqlPortItems = builder.withName(FIELD_CQL_PORT).withTitle("CQL Port") + .withValue(String.valueOf(settings.getCqlPort())) + .withDescription("The port on which the Storage Nodes listens for CQL client connections.") + .withValidators(validator).build(); + items.addAll(cqlPortItems);
- items.addAll(buildOneFormRowWithValidator(FIELD_GOSSIP_PORT, "Gossip Port", - String.valueOf(settings.getGossipPort()), - "The port used for internode communication in the storage cluster.", validator)); + // gossip port field + builder = new FormItemBuilder(); + List<FormItem> gossipPortItems = builder.withName(FIELD_GOSSIP_PORT).withTitle("Gossip Port") + .withValue(String.valueOf(settings.getGossipPort())) + .withDescription("The port used for internode communication in the storage cluster.") + .withValidators(validator).build(); + items.addAll(gossipPortItems); + +
- List<FormItem> automaticDeploymentItems = buildOneFormRow( - FIELD_AUTOMATIC_DEPLOYMENT, - "Automatic Deployment", - Boolean.toString(settings.getAutomaticDeployment()), - "If this is set, the newly installed storage nodes will be automatically deployed to the storage cluster.", - false, null); - RadioGroupItem autoDeployRadio = new RadioGroupItem(FIELD_AUTOMATIC_DEPLOYMENT); + // automatic deployment field + builder = new FormItemBuilder(); + List<FormItem> automaticDeploymentItems = builder + .withName(FIELD_AUTOMATIC_DEPLOYMENT) + .withTitle("Automatic Deployment") + .withValue(Boolean.toString(settings.getAutomaticDeployment())) + .withDescription( + "If this is set, the newly installed storage nodes will be automatically deployed to the storage cluster.") + .withReadOnlySetTo(readOnly).build((FormItem) GWT.create(RadioGroupItem.class)); + RadioGroupItem autoDeployRadio = (RadioGroupItem) automaticDeploymentItems.get(1); autoDeployRadio.setVertical(false); LinkedHashMap<String, String> values = new LinkedHashMap<String, String>(2); values.put("true", "On"); values.put("false", "Off"); autoDeployRadio.setValueMap(values); autoDeployRadio.setValue(settings.getAutomaticDeployment()); - autoDeployRadio.setAlign(Alignment.CENTER); - autoDeployRadio.setShowTitle(false); - autoDeployRadio.setRequired(true); - autoDeployRadio.setCellStyle(!oddRow ? "OddRow" : "EvenRow"); - autoDeployRadio.setDisabled(readOnly); - oddRow = !oddRow; - automaticDeploymentItems.set(1, autoDeployRadio); items.addAll(automaticDeploymentItems); + form.setFields(items.toArray(new FormItem[items.size()])); - form.setWidth100(); - form.setOverflow(Overflow.VISIBLE); - setWidth100();
+ credentialsForm = new EnhancedDynamicForm(); + credentialsForm.setHiliteRequiredFields(true); + credentialsForm.setNumCols(3); + credentialsForm.setCellPadding(5); + credentialsForm.setColWidths(190, 220, "*"); + credentialsForm.setIsGroup(true); + credentialsForm.setGroupTitle("Storage Cluster Credentials"); + credentialsForm.setBorder("1px solid #AAA"); + credentialsForm.setWidth100(); + credentialsForm.setOverflow(Overflow.VISIBLE); + + FormItemBuilder.resetOddRow(); + items = buildHeaderItems(); + + // username field + StringLengthValidator usernameValidator = new StringLengthValidator(4, 100, false); + builder = new FormItemBuilder(); + List<FormItem> usernameItems = builder.withName(FIELD_USERNAME).withTitle("Username") + .withValue(settings.getUsername()).withDescription("Username").withReadOnlySetTo(readOnly) + .withValidators(usernameValidator).build(); + items.addAll(usernameItems); + + // password field + StringLengthValidator passwordValidator1 = new StringLengthValidator(6, 100, false); + builder = new FormItemBuilder(); + List<FormItem> passwordItems = builder.withName(FIELD_PASSWORD).withTitle("Password") + .withValue(settings.getPasswordHash()).withDescription("Password").withReadOnlySetTo(readOnly) + .withValidators(passwordValidator1).build((FormItem) GWT.create(PasswordItem.class)); + items.addAll(passwordItems); + + // password_verify field + builder = new FormItemBuilder(); + MatchesFieldValidator passwordValidator2 = new MatchesFieldValidator(); + passwordValidator2.setOtherField(FIELD_PASSWORD); + passwordValidator2.setErrorMessage("This should be the same string as the Password."); + List<FormItem> passwordVerifyItems = builder.withName(FIELD_PASSWORD_VERIFY).withTitle("Verify Password") + .withValue(settings.getPasswordHash()).withDescription("This should be the same string as the Password.") + .withReadOnlySetTo(readOnly).withValidators(passwordValidator1, passwordValidator2) + .build((FormItem) GWT.create(PasswordItem.class)); + + items.addAll(passwordVerifyItems); + credentialsForm.setFields(items.toArray(new FormItem[items.size()])); + LayoutSpacer spacer = new LayoutSpacer(); spacer.setWidth100();
ToolStrip toolStrip = buildToolStrip(); - setMembers(form, spacer, toolStrip); + setMembers(form, credentialsForm, spacer, toolStrip); form.validate(); + credentialsForm.validate(); markForRedraw(); }
@@ -228,7 +246,7 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre saveButton = new EnhancedIButton(MSG.common_button_save()); saveButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent clickEvent) { - if (form.validate()) { + if (form.validate() && credentialsForm.validate()) { SC.ask( "Changing the cluster wide configuration will eventually affect all the storage nodes. Do you want to continue?", new BooleanCallback() { @@ -256,6 +274,97 @@ public class ClusterConfigurationEditor extends EnhancedVLayout implements Refre settings.setCqlPort(Integer.parseInt(form.getValueAsString(FIELD_CQL_PORT))); settings.setGossipPort(Integer.parseInt(form.getValueAsString(FIELD_GOSSIP_PORT))); settings.setAutomaticDeployment(Boolean.parseBoolean(form.getValueAsString(FIELD_AUTOMATIC_DEPLOYMENT))); + + // set the credentials only if there was a change + String wannabeUsername = credentialsForm.getValueAsString(FIELD_USERNAME); + settings.setUsername(wannabeUsername.equals(settings.getUsername()) ? null : wannabeUsername); + String wannabePassword = credentialsForm.getValueAsString(FIELD_PASSWORD); + settings.setPasswordHash(wannabePassword.equals(settings.getPasswordHash()) ? null : wannabePassword); return settings; } + + private static class FormItemBuilder { + private String name; + private String title; + private String value; + private String description; + private Validator[] validators; + private boolean readOnly; + + private static boolean oddRow = true; + + public static void resetOddRow() { + oddRow = true; + } + + public FormItemBuilder withName(String name) { + this.name = name; + return this; + } + + public FormItemBuilder withTitle(String title) { + this.title = title; + return this; + } + + public FormItemBuilder withValue(String value) { + this.value = value; + return this; + } + + public FormItemBuilder withDescription(String description) { + this.description = description; + return this; + } + + public FormItemBuilder withValidators(Validator... validators) { + this.validators = validators; + return this; + } + + public FormItemBuilder withReadOnlySetTo(boolean readOnly) { + this.readOnly = readOnly; + return this; + } + + public List<FormItem> build() { + return build(new TextItem()); + } + + // GWT doesn't support reflection by default, therefore this "hack" + public List<FormItem> build(FormItem valueItem) { + List<FormItem> fields = new ArrayList<FormItem>(); + StaticTextItem nameItem = new StaticTextItem(); + nameItem.setStartRow(true); + nameItem.setValue("<b>" + title + "</b>"); + nameItem.setShowTitle(false); + nameItem.setCellStyle(oddRow ? "OddRow" : "EvenRow"); + fields.add(nameItem); + + valueItem.setName(name); + valueItem.setValue(value); + valueItem.setWidth(220); + if (validators != null && validators.length > 0) { + valueItem.setValidators(validators); + } + valueItem.setValidateOnChange(true); + valueItem.setAlign(Alignment.CENTER); + valueItem.setShowTitle(false); + valueItem.setRequired(true); + valueItem.setCellStyle(oddRow ? "OddRow" : "EvenRow"); + valueItem.setDisabled(readOnly); + fields.add(valueItem); + + StaticTextItem descriptionItem = new StaticTextItem(); + descriptionItem.setValue(description); + descriptionItem.setShowTitle(false); + descriptionItem.setEndRow(true); + descriptionItem.setCellStyle(oddRow ? "OddRow" : "EvenRow"); + fields.add(descriptionItem); + + oddRow = !oddRow; + return fields; + } + + } } diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClusterSettingsManagerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClusterSettingsManagerBean.java index a00200d..2a47670 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClusterSettingsManagerBean.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/storage/StorageClusterSettingsManagerBean.java @@ -46,6 +46,18 @@ public class StorageClusterSettingsManagerBean implements StorageClusterSettings clusterSettings.setAutomaticDeployment(Boolean.parseBoolean(settings .get(SystemSetting.STORAGE_AUTOMATIC_DEPLOYMENT))); } + + if (!settings.containsKey(SystemSetting.STORAGE_USERNAME)) { + return null; + } else { + clusterSettings.setUsername(settings.get(SystemSetting.STORAGE_USERNAME)); + } + + if (!settings.containsKey(SystemSetting.STORAGE_PASSWORD)) { + return null; + } else { + clusterSettings.setPasswordHash(settings.get(SystemSetting.STORAGE_PASSWORD)); + }
return clusterSettings; } @@ -60,6 +72,12 @@ public class StorageClusterSettingsManagerBean implements StorageClusterSettings settings.put(SystemSetting.STORAGE_AUTOMATIC_DEPLOYMENT, Boolean.toString(clusterSettings.getAutomaticDeployment())); } + if (clusterSettings.getUsername() != null) { + settings.put(SystemSetting.STORAGE_USERNAME, clusterSettings.getUsername()); + } + if (clusterSettings.getPasswordHash() != null) { + settings.put(SystemSetting.STORAGE_PASSWORD, clusterSettings.getPasswordHash()); + } systemManager.setStorageClusterSettings(subject, settings); }
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/system/SystemManagerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/system/SystemManagerBean.java index bbef365..610c38d 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/system/SystemManagerBean.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/system/SystemManagerBean.java @@ -367,11 +367,11 @@ public class SystemManagerBean implements SystemManagerLocal, SystemManagerRemot private boolean isStorageSetting(SystemSetting setting) { switch (setting) { case STORAGE_CQL_PORT: - return true; case STORAGE_GOSSIP_PORT: - return true; case STORAGE_AUTOMATIC_DEPLOYMENT: - return true; + case STORAGE_PASSWORD: + case STORAGE_USERNAME: + return true; default: return false; }
rhq-commits@lists.fedorahosted.org