[389-ds-base] branch 389-ds-base-1.3.10 updated: Issue 51132 - Winsync setting winSyncWindowsFilter not working as expected
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.3.10
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.10 by this push:
new 7cc0f1d Issue 51132 - Winsync setting winSyncWindowsFilter not working as expected
7cc0f1d is described below
commit 7cc0f1d0a00ed9887788309efe36c5e4fa70cc10
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Wed Jun 3 15:58:21 2020 -0400
Issue 51132 - Winsync setting winSyncWindowsFilter not working as expected
Bug Description: When processing updates from AD we search AD using a filter,
and this filter can be customized via the attribute setting:
winSyncWindowsFilter. However, after setting a custom filter
replication appears to stop working as expected. New entries
that match the filter are replicated to DS, but not updates
to these entries. The problem is that when dirsync sends
updates, it is just a partial entry - only containing the
attributes that changed. Then the server checks the filter
again on the returned entry, but if it's just a mod update then
the entry is missing most of its attributes, and the filter
check fails and the entry is not updated on DS.
Fix Description: Do not check the filter on the returned entries when processing
incremental updates as the fitler test was already done when
gathering the candidates.
relates: https://pagure.io/389-ds-base/issue/51132
Reviewed by: tbordaz & firstyear (Thanks!)
---
ldap/servers/plugins/replication/windows_protocol_util.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/ldap/servers/plugins/replication/windows_protocol_util.c b/ldap/servers/plugins/replication/windows_protocol_util.c
index e354372..c394f82 100644
--- a/ldap/servers/plugins/replication/windows_protocol_util.c
+++ b/ldap/servers/plugins/replication/windows_protocol_util.c
@@ -48,7 +48,7 @@ static int windows_get_remote_entry(Private_Repl_Protocol *prp, const Slapi_DN *
static int windows_get_remote_tombstone(Private_Repl_Protocol *prp, const Slapi_DN *remote_dn, Slapi_Entry **remote_entry);
static int windows_reanimate_tombstone(Private_Repl_Protocol *prp, const Slapi_DN *tombstone_dn, const char *new_dn);
static const char *op2string(int op);
-static int is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra);
+static int is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra, int test_filter);
static int map_entry_dn_inbound(Slapi_Entry *e, Slapi_DN **dn, const Repl_Agmt *ra);
static int map_entry_dn_inbound_ext(Slapi_Entry *e, Slapi_DN **dn, const Repl_Agmt *ra, int use_guid, int user_username);
static int windows_update_remote_entry(Private_Repl_Protocol *prp, Slapi_Entry *remote_entry, Slapi_Entry *local_entry, int is_user);
@@ -57,6 +57,9 @@ static int map_windows_tombstone_dn(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_
static int windows_check_mods_for_rdn_change(Private_Repl_Protocol *prp, LDAPMod **original_mods, Slapi_Entry *local_entry, Slapi_DN *remote_dn, char **newrdn);
static int windows_get_superior_change(Private_Repl_Protocol *prp, Slapi_DN *local_dn, Slapi_DN *remote_dn, char **newsuperior, int to_windows);
+#define SKIP_FILTER 0
+#define TEST_FILTER 1
+
/* Controls the direction of flow for mapped attributes */
typedef enum mapping_types {
bidirectional,
@@ -442,7 +445,7 @@ map_dn_values(Private_Repl_Protocol *prp, Slapi_ValueSet *original_values, Slapi
/* Try to get the remote entry */
retval = windows_get_remote_entry(prp, original_dn, &remote_entry);
if (remote_entry && 0 == retval) {
- is_ours = is_subject_of_agreement_remote(remote_entry, prp->agmt);
+ is_ours = is_subject_of_agreement_remote(remote_entry, prp->agmt, TEST_FILTER);
if (is_ours) {
retval = map_entry_dn_inbound(remote_entry, &local_dn, prp->agmt);
if (0 == retval && local_dn) {
@@ -3708,7 +3711,7 @@ map_entry_dn_outbound(Slapi_Entry *e,
slapi_sdn_get_dn(new_dn),
remote_entry ? slapi_entry_get_dn_const(remote_entry) : "(null)");
if (0 == rc && remote_entry) {
- if (!is_subject_of_agreement_remote(remote_entry, prp->agmt)) {
+ if (!is_subject_of_agreement_remote(remote_entry, prp->agmt, TEST_FILTER)) {
/* The remote entry is out of scope of the agreement.
* Thus, we don't map the entry_dn.
* This occurs when the remote entry is moved out. */
@@ -4198,7 +4201,7 @@ is_dn_subject_of_agreement_local(const Slapi_DN *sdn, const Repl_Agmt *ra)
* 0 -- out of scope
*/
static int
-is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra)
+is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra, int test_filter)
{
int retval = 0;
int is_in_subtree = 0;
@@ -4232,7 +4235,7 @@ is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra)
Slapi_DN psdn = {0};
Slapi_Entry *pentry = NULL;
- if (windows_private_get_windows_filter(ra) &&
+ if (test_filter && windows_private_get_windows_filter(ra) &&
slapi_filter_test_simple(e, windows_private_get_windows_filter(ra))) {
/* type_winSyncWindowsFilter is set and the remote entry does not match the filter */
goto error;
@@ -5627,7 +5630,7 @@ windows_process_dirsync_entry(Private_Repl_Protocol *prp, Slapi_Entry *e, int is
}
} else {
/* Is this entry one we should be interested in ? */
- if (is_subject_of_agreement_remote(e, prp->agmt)) {
+ if (is_subject_of_agreement_remote(e, prp->agmt, SKIP_FILTER)) {
ConnResult cres = 0;
const char *searchbase = slapi_entry_get_dn_const(e);
char *filter = "(objectclass=*)";
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
3 years, 10 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Issue 51132 - Winsync setting winSyncWindowsFilter not working as expected
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new dbae8c8 Issue 51132 - Winsync setting winSyncWindowsFilter not working as expected
dbae8c8 is described below
commit dbae8c8fd7862abfe6f7883af9cb3dbc90de179c
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Wed Jun 3 15:58:21 2020 -0400
Issue 51132 - Winsync setting winSyncWindowsFilter not working as expected
Bug Description: When processing updates from AD we search AD using a filter,
and this filter can be customized via the attribute setting:
winSyncWindowsFilter. However, after setting a custom filter
replication appears to stop working as expected. New entries
that match the filter are replicated to DS, but not updates
to these entries. The problem is that when dirsync sends
updates, it is just a partial entry - only containing the
attributes that changed. Then the server checks the filter
again on the returned entry, but if it's just a mod update then
the entry is missing most of its attributes, and the filter
check fails and the entry is not updated on DS.
Fix Description: Do not check the filter on the returned entries when processing
incremental updates as the fitler test was already done when
gathering the candidates.
relates: https://pagure.io/389-ds-base/issue/51132
Reviewed by: tbordaz & firstyear (Thanks!)
---
ldap/servers/plugins/replication/windows_protocol_util.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/ldap/servers/plugins/replication/windows_protocol_util.c b/ldap/servers/plugins/replication/windows_protocol_util.c
index 3658139..6aa541a 100644
--- a/ldap/servers/plugins/replication/windows_protocol_util.c
+++ b/ldap/servers/plugins/replication/windows_protocol_util.c
@@ -48,7 +48,7 @@ static int windows_get_remote_entry(Private_Repl_Protocol *prp, const Slapi_DN *
static int windows_get_remote_tombstone(Private_Repl_Protocol *prp, const Slapi_DN *remote_dn, Slapi_Entry **remote_entry);
static int windows_reanimate_tombstone(Private_Repl_Protocol *prp, const Slapi_DN *tombstone_dn, const char *new_dn);
static const char *op2string(int op);
-static int is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra);
+static int is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra, int test_filter);
static int map_entry_dn_inbound(Slapi_Entry *e, Slapi_DN **dn, const Repl_Agmt *ra);
static int map_entry_dn_inbound_ext(Slapi_Entry *e, Slapi_DN **dn, const Repl_Agmt *ra, int use_guid, int user_username);
static int windows_update_remote_entry(Private_Repl_Protocol *prp, Slapi_Entry *remote_entry, Slapi_Entry *local_entry, int is_user);
@@ -57,6 +57,9 @@ static int map_windows_tombstone_dn(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_
static int windows_check_mods_for_rdn_change(Private_Repl_Protocol *prp, LDAPMod **original_mods, Slapi_Entry *local_entry, Slapi_DN *remote_dn, char **newrdn);
static int windows_get_superior_change(Private_Repl_Protocol *prp, Slapi_DN *local_dn, Slapi_DN *remote_dn, char **newsuperior, int to_windows);
+#define SKIP_FILTER 0
+#define TEST_FILTER 1
+
/* Controls the direction of flow for mapped attributes */
typedef enum mapping_types {
bidirectional,
@@ -442,7 +445,7 @@ map_dn_values(Private_Repl_Protocol *prp, Slapi_ValueSet *original_values, Slapi
/* Try to get the remote entry */
retval = windows_get_remote_entry(prp, original_dn, &remote_entry);
if (remote_entry && 0 == retval) {
- is_ours = is_subject_of_agreement_remote(remote_entry, prp->agmt);
+ is_ours = is_subject_of_agreement_remote(remote_entry, prp->agmt, TEST_FILTER);
if (is_ours) {
retval = map_entry_dn_inbound(remote_entry, &local_dn, prp->agmt);
if (0 == retval && local_dn) {
@@ -3701,7 +3704,7 @@ map_entry_dn_outbound(Slapi_Entry *e,
slapi_sdn_get_dn(new_dn),
remote_entry ? slapi_entry_get_dn_const(remote_entry) : "(null)");
if (0 == rc && remote_entry) {
- if (!is_subject_of_agreement_remote(remote_entry, prp->agmt)) {
+ if (!is_subject_of_agreement_remote(remote_entry, prp->agmt, TEST_FILTER)) {
/* The remote entry is out of scope of the agreement.
* Thus, we don't map the entry_dn.
* This occurs when the remote entry is moved out. */
@@ -4188,7 +4191,7 @@ is_dn_subject_of_agreement_local(const Slapi_DN *sdn, const Repl_Agmt *ra)
* 0 -- out of scope
*/
static int
-is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra)
+is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra, int test_filter)
{
int retval = 0;
int is_in_subtree = 0;
@@ -4222,7 +4225,7 @@ is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra)
Slapi_DN psdn = {0};
Slapi_Entry *pentry = NULL;
- if (windows_private_get_windows_filter(ra) &&
+ if (test_filter && windows_private_get_windows_filter(ra) &&
slapi_filter_test_simple(e, windows_private_get_windows_filter(ra))) {
/* type_winSyncWindowsFilter is set and the remote entry does not match the filter */
goto error;
@@ -5617,7 +5620,7 @@ windows_process_dirsync_entry(Private_Repl_Protocol *prp, Slapi_Entry *e, int is
}
} else {
/* Is this entry one we should be interested in ? */
- if (is_subject_of_agreement_remote(e, prp->agmt)) {
+ if (is_subject_of_agreement_remote(e, prp->agmt, SKIP_FILTER)) {
ConnResult cres = 0;
const char *searchbase = slapi_entry_get_dn_const(e);
char *filter = "(objectclass=*)";
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
3 years, 10 months
[389-ds-base] branch 389-ds-base-1.4.3 updated: Issue 51132 - Winsync setting winSyncWindowsFilter not working as expected
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.3
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.3 by this push:
new 3d98eb1 Issue 51132 - Winsync setting winSyncWindowsFilter not working as expected
3d98eb1 is described below
commit 3d98eb10a800f1fada6cfae2084c88d4c284bf25
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Wed Jun 3 15:58:21 2020 -0400
Issue 51132 - Winsync setting winSyncWindowsFilter not working as expected
Bug Description: When processing updates from AD we search AD using a filter,
and this filter can be customized via the attribute setting:
winSyncWindowsFilter. However, after setting a custom filter
replication appears to stop working as expected. New entries
that match the filter are replicated to DS, but not updates
to these entries. The problem is that when dirsync sends
updates, it is just a partial entry - only containing the
attributes that changed. Then the server checks the filter
again on the returned entry, but if it's just a mod update then
the entry is missing most of its attributes, and the filter
check fails and the entry is not updated on DS.
Fix Description: Do not check the filter on the returned entries when processing
incremental updates as the fitler test was already done when
gathering the candidates.
relates: https://pagure.io/389-ds-base/issue/51132
Reviewed by: tbordaz & firstyear (Thanks!)
---
ldap/servers/plugins/replication/windows_protocol_util.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/ldap/servers/plugins/replication/windows_protocol_util.c b/ldap/servers/plugins/replication/windows_protocol_util.c
index 3658139..6aa541a 100644
--- a/ldap/servers/plugins/replication/windows_protocol_util.c
+++ b/ldap/servers/plugins/replication/windows_protocol_util.c
@@ -48,7 +48,7 @@ static int windows_get_remote_entry(Private_Repl_Protocol *prp, const Slapi_DN *
static int windows_get_remote_tombstone(Private_Repl_Protocol *prp, const Slapi_DN *remote_dn, Slapi_Entry **remote_entry);
static int windows_reanimate_tombstone(Private_Repl_Protocol *prp, const Slapi_DN *tombstone_dn, const char *new_dn);
static const char *op2string(int op);
-static int is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra);
+static int is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra, int test_filter);
static int map_entry_dn_inbound(Slapi_Entry *e, Slapi_DN **dn, const Repl_Agmt *ra);
static int map_entry_dn_inbound_ext(Slapi_Entry *e, Slapi_DN **dn, const Repl_Agmt *ra, int use_guid, int user_username);
static int windows_update_remote_entry(Private_Repl_Protocol *prp, Slapi_Entry *remote_entry, Slapi_Entry *local_entry, int is_user);
@@ -57,6 +57,9 @@ static int map_windows_tombstone_dn(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_
static int windows_check_mods_for_rdn_change(Private_Repl_Protocol *prp, LDAPMod **original_mods, Slapi_Entry *local_entry, Slapi_DN *remote_dn, char **newrdn);
static int windows_get_superior_change(Private_Repl_Protocol *prp, Slapi_DN *local_dn, Slapi_DN *remote_dn, char **newsuperior, int to_windows);
+#define SKIP_FILTER 0
+#define TEST_FILTER 1
+
/* Controls the direction of flow for mapped attributes */
typedef enum mapping_types {
bidirectional,
@@ -442,7 +445,7 @@ map_dn_values(Private_Repl_Protocol *prp, Slapi_ValueSet *original_values, Slapi
/* Try to get the remote entry */
retval = windows_get_remote_entry(prp, original_dn, &remote_entry);
if (remote_entry && 0 == retval) {
- is_ours = is_subject_of_agreement_remote(remote_entry, prp->agmt);
+ is_ours = is_subject_of_agreement_remote(remote_entry, prp->agmt, TEST_FILTER);
if (is_ours) {
retval = map_entry_dn_inbound(remote_entry, &local_dn, prp->agmt);
if (0 == retval && local_dn) {
@@ -3701,7 +3704,7 @@ map_entry_dn_outbound(Slapi_Entry *e,
slapi_sdn_get_dn(new_dn),
remote_entry ? slapi_entry_get_dn_const(remote_entry) : "(null)");
if (0 == rc && remote_entry) {
- if (!is_subject_of_agreement_remote(remote_entry, prp->agmt)) {
+ if (!is_subject_of_agreement_remote(remote_entry, prp->agmt, TEST_FILTER)) {
/* The remote entry is out of scope of the agreement.
* Thus, we don't map the entry_dn.
* This occurs when the remote entry is moved out. */
@@ -4188,7 +4191,7 @@ is_dn_subject_of_agreement_local(const Slapi_DN *sdn, const Repl_Agmt *ra)
* 0 -- out of scope
*/
static int
-is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra)
+is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra, int test_filter)
{
int retval = 0;
int is_in_subtree = 0;
@@ -4222,7 +4225,7 @@ is_subject_of_agreement_remote(Slapi_Entry *e, const Repl_Agmt *ra)
Slapi_DN psdn = {0};
Slapi_Entry *pentry = NULL;
- if (windows_private_get_windows_filter(ra) &&
+ if (test_filter && windows_private_get_windows_filter(ra) &&
slapi_filter_test_simple(e, windows_private_get_windows_filter(ra))) {
/* type_winSyncWindowsFilter is set and the remote entry does not match the filter */
goto error;
@@ -5617,7 +5620,7 @@ windows_process_dirsync_entry(Private_Repl_Protocol *prp, Slapi_Entry *e, int is
}
} else {
/* Is this entry one we should be interested in ? */
- if (is_subject_of_agreement_remote(e, prp->agmt)) {
+ if (is_subject_of_agreement_remote(e, prp->agmt, SKIP_FILTER)) {
ConnResult cres = 0;
const char *searchbase = slapi_entry_get_dn_const(e);
char *filter = "(objectclass=*)";
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
3 years, 10 months
[389-ds-base] branch 389-ds-base-1.4.3 updated: Ticket 51072 - improve autotune defaults
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
firstyear pushed a commit to branch 389-ds-base-1.4.3
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.3 by this push:
new f280428 Ticket 51072 - improve autotune defaults
f280428 is described below
commit f2804282bf4ecaa6860fa7b6121aa9bbcd0f4708
Author: William Brown <william(a)blackhats.net.au>
AuthorDate: Thu May 7 14:44:58 2020 +1000
Ticket 51072 - improve autotune defaults
Bug Description: we have learnt that the CPU autotuning is too aggresive, potentially
decreasing throughput due to overhead in context switching and lock contention, and
that our memory tuning is not aggressive enough, at only 10% of the system memory.
Additionally, in containers, we are able to have access to different memory limits
and reservations, so we can choose to be even more forward in our selection.
Fix Description: Change thread tuning to match the number of threads available on
the system. Change memory tuning to 25% of system memory by default. Finally add
an environment variable to containers allowing more aggressive tuning to be
set DS_MEMORY_PERCENTAGE. Later this could be set to a higher default value.
https://pagure.io/389-ds-base/issue/51072
Author: William Brown <william(a)blackhats.net.au>
Review by: mreynolds, vashirov, tbordaz (Thanks!)
---
ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c | 2 +-
ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c | 2 +-
ldap/servers/slapd/util.c | 33 ++++--------------------
src/lib389/cli/dscontainer | 12 +++++++++
4 files changed, 19 insertions(+), 30 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
index 4289134..7c05d2f 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
@@ -1398,7 +1398,7 @@ static config_info bdb_config_param[] = {
{CONFIG_DB_DEBUG_CHECKPOINTING, CONFIG_TYPE_ONOFF, "off", &bdb_config_db_debug_checkpointing_get, &bdb_config_db_debug_checkpointing_set, 0},
{CONFIG_DB_HOME_DIRECTORY, CONFIG_TYPE_STRING, "", &bdb_config_db_home_directory_get, &bdb_config_db_home_directory_set, 0},
{CONFIG_IMPORT_CACHE_AUTOSIZE, CONFIG_TYPE_INT, "-1", &bdb_config_import_cache_autosize_get, &bdb_config_import_cache_autosize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
- {CONFIG_CACHE_AUTOSIZE, CONFIG_TYPE_INT, "10", &bdb_config_cache_autosize_get, &bdb_config_cache_autosize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
+ {CONFIG_CACHE_AUTOSIZE, CONFIG_TYPE_INT, "25", &bdb_config_cache_autosize_get, &bdb_config_cache_autosize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_CACHE_AUTOSIZE_SPLIT, CONFIG_TYPE_INT, "25", &bdb_config_cache_autosize_split_get, &bdb_config_cache_autosize_split_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_IMPORT_CACHESIZE, CONFIG_TYPE_UINT64, "16777216", &bdb_config_import_cachesize_get, &bdb_config_import_cachesize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_BYPASS_FILTER_TEST, CONFIG_TYPE_STRING, "on", &bdb_config_get_bypass_filter_test, &bdb_config_set_bypass_filter_test, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c
index e502ed5..340e4a5 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c
@@ -193,7 +193,7 @@ bdb_start_autotune(struct ldbminfo *li)
* sane defaults and populate these values, but it's only on first run.
*/
msg = "This can be corrected by altering the values of nsslapd-dbcachesize, nsslapd-cachememsize and nsslapd-dncachememsize\n";
- autosize_percentage = 10;
+ autosize_percentage = 25;
} else {
/* In this case we really are setting the values each start up, so
* change the msg.
diff --git a/ldap/servers/slapd/util.c b/ldap/servers/slapd/util.c
index b4f31ff..fc67d70 100644
--- a/ldap/servers/slapd/util.c
+++ b/ldap/servers/slapd/util.c
@@ -1491,34 +1491,11 @@ util_get_hardware_threads(void)
long hw_threads = sysconf(_SC_NPROCESSORS_ONLN);
long threads = 0;
slapi_log_err(SLAPI_LOG_TRACE, "util_get_hardware_threads", "Detected %lu hardware threads\n", threads);
- /*
- * Now we determine the number to run with based on threads. Initially, for
- * low processor counts we ramp up quickly, we plateau a little, then, we
- * at high numbers start to plateau and increase slowly.
- * Should be
- * 1 -> 16
- * 2 -> 16
- * 4 -> 24
- * 8 -> 32
- * 16 -> 48
- * 32 -> 64
- * 64 -> 96
- * 128 -> 192
- * 256 -> 384
- * 512 -> 512
- * 1024 -> 512
- * 2048 -> 512
- */
-
- if (hw_threads >= 0 && hw_threads < 4) {
- threads = 16;
- } else if (hw_threads >= 4 && hw_threads < 32) {
- threads = 16 + (hw_threads * 2);
- } else if (hw_threads >= 32 && hw_threads < 64) {
- threads = (hw_threads * 2);
- } else if (hw_threads >= 64 && hw_threads < 512) {
- /* Same as *1.5 */
- threads = (hw_threads * 2) - (hw_threads / 2);
+ if (hw_threads == 0) {
+ /* Error! */
+ threads = -1;
+ } else if (hw_threads < 512) {
+ threads = hw_threads;
} else {
/* Cap at 512 for now ... */
threads = 512;
diff --git a/src/lib389/cli/dscontainer b/src/lib389/cli/dscontainer
index de74ae1..a519eef 100755
--- a/src/lib389/cli/dscontainer
+++ b/src/lib389/cli/dscontainer
@@ -42,6 +42,7 @@ from lib389.instance.options import General2Base, Slapd2Base
from lib389.passwd import password_generate
from lib389.nss_ssl import NssSsl, CERT_NAME
from lib389.paths import Paths
+from lib389.config import LDBMConfig
from lib389._constants import (
DSRC_CONTAINER,
CONTAINER_TLS_SERVER_KEY,
@@ -81,10 +82,21 @@ def _begin_environment_config():
# TODO: Should we reset cn=Directory Manager from env?
dm_pass = os.getenv("DS_DM_PASSWORD", None)
if dm_pass is not None:
+ log.debug("Setting Directory Manager Password ...")
dm = DirectoryManager(inst)
dm.change_password(dm_pass)
# TODO: Should we set replica id from env?
# TODO: Should we set replication agreements from env?
+ autotune_pct = os.getenv("DS_MEMORY_PERCENTAGE", None)
+ if autotune_pct is not None:
+ try:
+ autotune_pct = int(autotune_pct)
+ except:
+ log.error("Invalid DS_MEMORY_PERCENTAGE - resetting to system default value")
+ autotune_pct = 0
+ log.debug("Setting LDBM Autotune Percentage to: %s", autotune_pct)
+ ldbmconfig = LDBMConfig(inst)
+ ldbmconfig.set("nsslapd-cache-autosize", str(autotune_pct))
inst.close()
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
3 years, 10 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Ticket 51072 - improve autotune defaults
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
firstyear pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new 98fe0d6 Ticket 51072 - improve autotune defaults
98fe0d6 is described below
commit 98fe0d67834a74bf006620b1e90136b1cc5ac235
Author: William Brown <william(a)blackhats.net.au>
AuthorDate: Thu May 7 14:44:58 2020 +1000
Ticket 51072 - improve autotune defaults
Bug Description: we have learnt that the CPU autotuning is too aggresive, potentially
decreasing throughput due to overhead in context switching and lock contention, and
that our memory tuning is not aggressive enough, at only 10% of the system memory.
Additionally, in containers, we are able to have access to different memory limits
and reservations, so we can choose to be even more forward in our selection.
Fix Description: Change thread tuning to match the number of threads available on
the system. Change memory tuning to 25% of system memory by default. Finally add
an environment variable to containers allowing more aggressive tuning to be
set DS_MEMORY_PERCENTAGE. Later this could be set to a higher default value.
https://pagure.io/389-ds-base/issue/51072
Author: William Brown <william(a)blackhats.net.au>
Review by: mreynolds, vashirov, tbordaz (Thanks!)
---
ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c | 2 +-
ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c | 2 +-
ldap/servers/slapd/util.c | 33 ++++--------------------
src/lib389/cli/dscontainer | 20 +++++++++++++-
4 files changed, 26 insertions(+), 31 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
index 4289134..7c05d2f 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
@@ -1398,7 +1398,7 @@ static config_info bdb_config_param[] = {
{CONFIG_DB_DEBUG_CHECKPOINTING, CONFIG_TYPE_ONOFF, "off", &bdb_config_db_debug_checkpointing_get, &bdb_config_db_debug_checkpointing_set, 0},
{CONFIG_DB_HOME_DIRECTORY, CONFIG_TYPE_STRING, "", &bdb_config_db_home_directory_get, &bdb_config_db_home_directory_set, 0},
{CONFIG_IMPORT_CACHE_AUTOSIZE, CONFIG_TYPE_INT, "-1", &bdb_config_import_cache_autosize_get, &bdb_config_import_cache_autosize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
- {CONFIG_CACHE_AUTOSIZE, CONFIG_TYPE_INT, "10", &bdb_config_cache_autosize_get, &bdb_config_cache_autosize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
+ {CONFIG_CACHE_AUTOSIZE, CONFIG_TYPE_INT, "25", &bdb_config_cache_autosize_get, &bdb_config_cache_autosize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_CACHE_AUTOSIZE_SPLIT, CONFIG_TYPE_INT, "25", &bdb_config_cache_autosize_split_get, &bdb_config_cache_autosize_split_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_IMPORT_CACHESIZE, CONFIG_TYPE_UINT64, "16777216", &bdb_config_import_cachesize_get, &bdb_config_import_cachesize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_BYPASS_FILTER_TEST, CONFIG_TYPE_STRING, "on", &bdb_config_get_bypass_filter_test, &bdb_config_set_bypass_filter_test, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c
index e502ed5..340e4a5 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c
@@ -193,7 +193,7 @@ bdb_start_autotune(struct ldbminfo *li)
* sane defaults and populate these values, but it's only on first run.
*/
msg = "This can be corrected by altering the values of nsslapd-dbcachesize, nsslapd-cachememsize and nsslapd-dncachememsize\n";
- autosize_percentage = 10;
+ autosize_percentage = 25;
} else {
/* In this case we really are setting the values each start up, so
* change the msg.
diff --git a/ldap/servers/slapd/util.c b/ldap/servers/slapd/util.c
index 5ef1cd9..f380ba8 100644
--- a/ldap/servers/slapd/util.c
+++ b/ldap/servers/slapd/util.c
@@ -1491,34 +1491,11 @@ util_get_hardware_threads(void)
long hw_threads = sysconf(_SC_NPROCESSORS_ONLN);
long threads = 0;
slapi_log_err(SLAPI_LOG_TRACE, "util_get_hardware_threads", "Detected %lu hardware threads\n", threads);
- /*
- * Now we determine the number to run with based on threads. Initially, for
- * low processor counts we ramp up quickly, we plateau a little, then, we
- * at high numbers start to plateau and increase slowly.
- * Should be
- * 1 -> 16
- * 2 -> 16
- * 4 -> 24
- * 8 -> 32
- * 16 -> 48
- * 32 -> 64
- * 64 -> 96
- * 128 -> 192
- * 256 -> 384
- * 512 -> 512
- * 1024 -> 512
- * 2048 -> 512
- */
-
- if (hw_threads >= 0 && hw_threads < 4) {
- threads = 16;
- } else if (hw_threads >= 4 && hw_threads < 32) {
- threads = 16 + (hw_threads * 2);
- } else if (hw_threads >= 32 && hw_threads < 64) {
- threads = (hw_threads * 2);
- } else if (hw_threads >= 64 && hw_threads < 512) {
- /* Same as *1.5 */
- threads = (hw_threads * 2) - (hw_threads / 2);
+ if (hw_threads == 0) {
+ /* Error! */
+ threads = -1;
+ } else if (hw_threads < 512) {
+ threads = hw_threads;
} else {
/* Cap at 512 for now ... */
threads = 512;
diff --git a/src/lib389/cli/dscontainer b/src/lib389/cli/dscontainer
index 55d79d2..5671b5c 100755
--- a/src/lib389/cli/dscontainer
+++ b/src/lib389/cli/dscontainer
@@ -41,7 +41,14 @@ from lib389.instance.setup import SetupDs
from lib389.instance.options import General2Base, Slapd2Base
from lib389.passwd import password_generate
from lib389.paths import Paths
-from lib389._constants import DSRC_CONTAINER
+from lib389.config import LDBMConfig
+from lib389._constants import (
+ DSRC_CONTAINER,
+ CONTAINER_TLS_SERVER_KEY,
+ CONTAINER_TLS_SERVER_CERT,
+ CONTAINER_TLS_SERVER_CADIR,
+ CONTAINER_TLS_PWDFILE
+)
from lib389.idm.directorymanager import DirectoryManager
@@ -74,10 +81,21 @@ def _begin_environment_config():
# TODO: Should we reset cn=Directory Manager from env?
dm_pass = os.getenv("DS_DM_PASSWORD", None)
if dm_pass is not None:
+ log.debug("Setting Directory Manager Password ...")
dm = DirectoryManager(inst)
dm.change_password(dm_pass)
# TODO: Should we set replica id from env?
# TODO: Should we set replication agreements from env?
+ autotune_pct = os.getenv("DS_MEMORY_PERCENTAGE", None)
+ if autotune_pct is not None:
+ try:
+ autotune_pct = int(autotune_pct)
+ except:
+ log.error("Invalid DS_MEMORY_PERCENTAGE - resetting to system default value")
+ autotune_pct = 0
+ log.debug("Setting LDBM Autotune Percentage to: %s", autotune_pct)
+ ldbmconfig = LDBMConfig(inst)
+ ldbmconfig.set("nsslapd-cache-autosize", str(autotune_pct))
inst.close()
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
3 years, 10 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Issue 50746 - Add option to healthcheck to list all the lint reports
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mhonek pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new 08eed33 Issue 50746 - Add option to healthcheck to list all the lint reports
08eed33 is described below
commit 08eed33d62eb30998e246af22a3622470e7531cd
Author: Matus Honek <mhonek(a)redhat.com>
AuthorDate: Mon Dec 2 14:53:31 2019 +0100
Issue 50746 - Add option to healthcheck to list all the lint reports
Bug Description:
Healthcheck lacks a way to find out what checks are available.
Fix Description:
Add dsctl healthcheck options to list available checks, known error
codes, and ability to run cehcks selectively. The checks are rather
hierarchically structured and in some cases matchable by patterns (by
use of asterisk).
Fixes https://pagure.io/389-ds-base/issue/50746
Author: Matus Honek <mhonek(a)redhat.com>
Review by: Mark, William, Simon (thanks for the patience!)
(cherry picked from commit 4a55322c7bdb0b9ff57428ad0dc2e4d943572a69)
---
src/lib389/cli/dsctl | 1 +
src/lib389/lib389/_mapped_object.py | 34 +----
src/lib389/lib389/_mapped_object_lint.py | 157 +++++++++++++++++++++
src/lib389/lib389/backend.py | 13 +-
src/lib389/lib389/cli_ctl/health.py | 119 +++++++++++-----
src/lib389/lib389/config.py | 13 +-
src/lib389/lib389/dseldif.py | 29 ++--
src/lib389/lib389/encrypted_attributes.py | 1 -
src/lib389/lib389/index.py | 3 -
src/lib389/lib389/lint.py | 125 +++++++++-------
src/lib389/lib389/monitor.py | 5 +-
src/lib389/lib389/nss_ssl.py | 23 ++-
src/lib389/lib389/plugins.py | 5 +-
src/lib389/lib389/replica.py | 10 +-
src/lib389/lib389/tests/mapped_object_lint_test.py | 78 ++++++++++
15 files changed, 449 insertions(+), 167 deletions(-)
diff --git a/src/lib389/cli/dsctl b/src/lib389/cli/dsctl
index f3bbabc..0a5f73b 100755
--- a/src/lib389/cli/dsctl
+++ b/src/lib389/cli/dsctl
@@ -64,6 +64,7 @@ cli_nsstate.create_parser(subparsers)
argcomplete.autocomplete(parser)
+
# handle a control-c gracefully
def signal_handler(signal, frame):
print('\n\nExiting...')
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
index ce0ebfe..c608376 100644
--- a/src/lib389/lib389/_mapped_object.py
+++ b/src/lib389/lib389/_mapped_object.py
@@ -15,6 +15,7 @@ import json
from functools import partial
from lib389._entry import Entry
from lib389._constants import DIRSRV_STATE_ONLINE
+from lib389._mapped_object_lint import DSLint, DSLints
from lib389.utils import (
ensure_bytes, ensure_str, ensure_int, ensure_list_bytes, ensure_list_str,
ensure_list_int, display_log_value, display_log_data
@@ -82,7 +83,7 @@ class DSLogging(object):
self._log.setLevel(logging.INFO)
-class DSLdapObject(DSLogging):
+class DSLdapObject(DSLogging, DSLint):
"""A single instance of DSLdapObjects
:param instance: An instance
@@ -107,7 +108,6 @@ class DSLdapObject(DSLogging):
self._must_attributes = None
# attributes, we don't want to compare
self._compare_exclude = ['entryid', 'modifytimestamp', 'nsuniqueid']
- self._lint_functions = None
self._server_controls = None
self._client_controls = None
self._object_filter = '(objectClass=*)'
@@ -985,38 +985,10 @@ class DSLdapObject(DSLogging):
"""
return self._create(rdn, properties, basedn, ensure=True)
- def lint(self):
- """Override this to create a linter for a type. This means that we can detect
- and report common administrative errors in the server from our cli and
- rest tools.
-
- The structure of a result is::
-
- {
- dsle: '<identifier>'. dsle == ds lint error. Will be a code unique to
- this module for the error, IE DSBLE0001.
- severity: '[HIGH:MEDIUM:LOW]'. severity of the error.
- items: '(dn,dn,dn)'. List of affected DNs or names.
- detail: 'msg ...'. An explination of the error.
- fix: 'msg ...'. Steps to resolve the error.
- }
-
- :returns: An array of these dicts, on None if there are no errors.
- """
-
- if not self._lint_functions:
- return None
- results = []
- for fn in self._lint_functions:
- for result in fn():
- if result is not None:
- results.append(result)
- return results
-
# A challenge of this, is how do we manage indexes? They have two naming attributes....
-class DSLdapObjects(DSLogging):
+class DSLdapObjects(DSLogging, DSLints):
"""The object represents the next idea: "Everything is an instance of something
that exists in this way", i.e. we unite LDAP entries by some
set of parameters with the object.
diff --git a/src/lib389/lib389/_mapped_object_lint.py b/src/lib389/lib389/_mapped_object_lint.py
new file mode 100644
index 0000000..2d03de9
--- /dev/null
+++ b/src/lib389/lib389/_mapped_object_lint.py
@@ -0,0 +1,157 @@
+from abc import ABC, abstractmethod
+from functools import partial
+from inspect import signature
+from typing import (
+ Callable,
+ List,
+ Optional,
+ Tuple,
+ Union,
+ Type,
+ Generator,
+ Any
+)
+
+
+DSLintSpec = Tuple[str, Callable]
+DSLintParsedSpec = Tuple[Optional[str], Optional[str]]
+DSLintClassSpec = Generator[DSLintSpec, None, None]
+DSLintMethodSpec = Union[str, None, Type[List]]
+DSLintResults = Generator[Any, None, None]
+
+
+class DSLint():
+ """In a super-class, create a method with name beginning with `_lint_`
+ which would yield results (as described below). Such a method will
+ then be available to the `lint()` method of the class.
+
+ `lint_list`: takes a spec and yields available lints, recursively
+ `lint`: takes a spac and runs lints according to it, yielding results if any
+
+ `spec`: is a colon-separated string, with prefix matching a method name and suffix
+ being passed down to the method.
+
+ A class inheriting from hereby class shall implement a method named `lint_uid()` which
+ returns a pretty name of the object. This is to be used by a higher level code.
+
+ Each lint method has to have a name prefix with _lint_. It may accept an optional
+ parameter `spec` in which case:
+ - it has to accept typing.List class as a parameter, in which case it shall yield
+ all possible lint specs for that method
+ - it receives the suffix provided to the `spec` of hereby `lint` method (as mentioned above)
+
+ This means that we can detect and report common administrative errors
+ in the server from our cli and rest tools.
+
+ The structure of a result shall be:
+
+ {
+ dsle: '<identifier>'. dsle == ds lint error. Will be a code unique to
+ this module for the error, IE DSBLE0001.
+ severity: '[HIGH:MEDIUM:LOW]'. severity of the error.
+ items: '(dn,dn,dn)'. List of affected DNs or names.
+ detail: 'msg ...'. An explination of the error.
+ fix: 'msg ...'. Steps to resolve the error.
+ }
+ """
+
+ @classmethod
+ def _dslint_fname(cls, method: Callable) -> Optional[str]:
+ """Return a pretty name for a method."""
+ if callable(method) and method.__name__.startswith('_lint_'):
+ return method.__name__[len('_lint_'):]
+ else:
+ return None
+
+ @staticmethod
+ def _dslint_parse_spec(spec: Optional[str]) -> DSLintParsedSpec:
+ """Split `spec` to prefix and suffix."""
+ wanted, *rest = spec.split(':', 1) if spec else (None, None)
+ return (wanted if wanted not in [None, '*'] else None,
+ rest[0] if rest else None)
+
+ @classmethod
+ def _dslint_make_spec(cls, method: Callable, spec: Optional[str] = None) -> str:
+ """Build a new spec from prefix (`method` name) and suffix (`spec`)."""
+ fname = cls._dslint_fname(method)
+ return f'{fname}:{spec}' if spec else fname
+
+ def lint_list(self, spec: Optional[str] = None) -> DSLintClassSpec:
+ """Yield specs the object provides.
+
+ This yields from each lint method yielding all specs it can provide.
+ """
+
+ assert hasattr(self, 'lint_uid')
+
+ # Find _lint_ methods
+ # NOTE: There is a caveat: don't you dare try to getattr on a @property, or
+ # you get it executed. That's why the following line's complexity.
+ fs = [getattr(self, f) for f in dir(self)
+ if f.startswith('_lint_') and self._dslint_fname(getattr(self, f))]
+
+ # Filter acording to the `spec`
+ wanted, rest = self._dslint_parse_spec(spec)
+ if wanted:
+ try:
+ fs = [next(filter(lambda f: self._dslint_fname(f) == wanted, fs))]
+ except StopIteration:
+ raise ValueError('there is no such lint function')
+
+ # Yield known specs
+ for f in fs:
+ fspec_t = signature(f).parameters.get('spec', None)
+ if fspec_t:
+ assert fspec_t.annotation == DSLintMethodSpec
+ for fspec in [rest] if rest else f(spec=List):
+ yield self._dslint_make_spec(f, fspec), partial(f, spec=fspec)
+ else:
+ yield self._dslint_make_spec(f, rest), f
+
+ def lint(self, spec: DSLintMethodSpec = None) -> DSLintResults:
+ """Lint the object according to the `spec`."""
+
+ if spec == List:
+ yield from self.lint_list()
+ else:
+ for fn, f in self.lint_list(spec):
+ yield from f()
+
+
+class DSLints():
+ """This is a meta class to provide lint functionality to classes that provide
+ method `list` which returns list of objects that inherit from DSLint.
+
+ Calling `lint` or `lint_list` method yields from respective object's methods.
+
+ The `spec` is a colon-separated string. Its prefix matches the respective object's
+ `lint_uid` (or all when asterisk); the suffix is passed down to the respective
+ object's method.
+ """
+
+ def lint_list(self, spec: Optional[str] = None) -> DSLintClassSpec:
+ """Yield specs the objects returned by `list` method provide."""
+
+ assert hasattr(self, 'list')
+
+ # Filter acording to the `spec`
+ wanted, rest_spec = DSLint._dslint_parse_spec(spec)
+ if wanted in [None, '*']:
+ clss = self.list()
+ else:
+ clss = (cls for cls in self.list() if cls.lint_uid() == wanted)
+
+ # Yield known specs
+ for cls in clss:
+ for fn, f in cls.lint_list(spec=rest_spec):
+ yield (f'{cls.lint_uid()}:{fn}',
+ partial(f, rest_spec) if rest_spec else f)
+
+ def lint(self, spec: DSLintMethodSpec = None) -> DSLintResults:
+ """Lint the objects returned by `list` method according to the `spec`."""
+
+ if spec == List:
+ yield from self.lint_list()
+ else:
+ for obj in self.list():
+ yield from obj.lint()
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index 4f752f4..8863ad1 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -393,6 +393,10 @@ class BackendLegacy(object):
replace = [(ldap.MOD_REPLACE, 'nsslapd-require-index', 'on')]
self.modify_s(dn, replace)
+ @classmethod
+ def lint_uid(cls):
+ return 'backends'
+
class Backend(DSLdapObject):
"""Backend DSLdapObject with:
@@ -413,10 +417,12 @@ class Backend(DSLdapObject):
self._must_attributes = ['nsslapd-suffix', 'cn']
self._create_objectclasses = ['top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE]
self._protected = False
- self._lint_functions = [self._lint_mappingtree, self._lint_search, self._lint_virt_attrs]
# Check if a mapping tree for this suffix exists.
self._mts = MappingTrees(self._instance)
+ def lint_uid(self):
+ return self.get_attr_val_utf8_l('cn').lower()
+
def _lint_virt_attrs(self):
"""Check if any virtual attribute are incorrectly indexed"""
indexes = self.get_indexes()
@@ -497,7 +503,6 @@ class Backend(DSLdapObject):
result = DSBLE0001
result['items'] = [bename, ]
yield result
- return None
def create_sample_entries(self, version):
"""Creates sample entries under nsslapd-suffix value
@@ -848,6 +853,10 @@ class Backends(DSLdapObjects):
self._childobject = Backend
self._basedn = DN_LDBM
+ @classmethod
+ def lint_uid(cls):
+ return 'backends'
+
def import_ldif(self, be_name, ldifs, chunk_size=None, encrypted=False, gen_uniq_id=None, only_core=False,
include_suffixes=None, exclude_suffixes=None):
"""Do an import of the suffix"""
diff --git a/src/lib389/lib389/cli_ctl/health.py b/src/lib389/lib389/cli_ctl/health.py
index 10eaa44..fa7b5ee 100644
--- a/src/lib389/lib389/cli_ctl/health.py
+++ b/src/lib389/lib389/cli_ctl/health.py
@@ -7,8 +7,10 @@
# --- END COPYRIGHT BLOCK ---
import json
-from getpass import getpass
-from lib389.cli_base import connect_instance, disconnect_instance, format_error_to_dict
+import re
+from lib389._mapped_object import DSLdapObjects
+from lib389._mapped_object_lint import DSLint
+from lib389.cli_base import connect_instance, disconnect_instance
from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat
from lib389.backend import Backend, Backends
from lib389.config import Encryption, Config
@@ -16,17 +18,17 @@ from lib389.monitor import MonitorDiskSpace
from lib389.replica import Replica, Changelog5
from lib389.nss_ssl import NssSsl
from lib389.dseldif import FSChecks, DSEldif
+from lib389 import lint
from lib389 import plugins
from lib389._constants import DSRC_HOME
+from functools import partial
+from typing import Iterable
-# These get all instances, then check them all.
-CHECK_MANY_OBJECTS = [
- Backends,
-]
# These get single instances and check them.
CHECK_OBJECTS = [
Config,
+ Backends,
Encryption,
FSChecks,
plugins.ReferentialIntegrityPlugin,
@@ -53,44 +55,51 @@ def _format_check_output(log, result, idx):
log.info(result['fix'])
-def health_check_run(inst, log, args):
- """Connect to the local server using LDAPI, and perform various health checks
- """
+def _list_targets(inst):
+ for c in CHECK_OBJECTS:
+ o = c(inst)
+ yield o.lint_uid(), o
+
+
+def _list_errors(log):
+ for r in map(partial(getattr, lint),
+ filter(partial(re.match, r'^DS'),
+ dir(lint))):
+ log.info(f"{r['dsle']} :: {r['description']}")
- # update the args for connect_instance()
- args.basedn = None
- args.binddn = None
- args.bindpw = None
- args.starttls = None
- args.pwdfile = None
- args.prompt = False
- dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
- dsrc_inst = dsrc_arg_concat(args, dsrc_inst)
- try:
- inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
- except Exception as e:
- raise ValueError('Failed to connect to Directory Server instance: ' + str(e))
+def _list_checks(inst, specs: Iterable[str]):
+ o_uids = dict(_list_targets(inst))
+ for s in specs:
+ wanted, rest = DSLint._dslint_parse_spec(s)
+ if wanted == '*':
+ raise ValueError('Unexpected spec selector asterisk')
+
+ if wanted in o_uids:
+ for l in o_uids[wanted].lint_list(rest):
+ yield o_uids[wanted], l
+ else:
+ raise ValueError('No such object specifier')
+
+
+def _print_checks(inst, specs: Iterable[str]) -> None:
+ for o, s in _list_checks(inst, specs):
+ print(f'{o.lint_uid()}:{s[0]}')
+
+
+def _run(inst, log, args, checks):
if not args.json:
log.info("Beginning lint report, this could take a while ...")
+
report = []
- for lo in CHECK_MANY_OBJECTS:
+ for o, s in checks:
if not args.json:
- log.info("Checking %s ..." % lo.__name__)
- lo_inst = lo(inst)
- for clo in lo_inst.list():
- result = clo.lint()
- if result is not None:
- report += result
- for lo in CHECK_OBJECTS:
- if not args.json:
- log.info("Checking %s ..." % lo.__name__)
- lo_inst = lo(inst)
- result = lo_inst.lint()
- if result is not None:
- report += result
+ log.info(f"Checking {o.lint_uid()}:{s[0]} ...")
+ report += o.lint(s[0]) or []
+
if not args.json:
log.info("Healthcheck complete.")
+
count = len(report)
if count == 0:
if not args.json:
@@ -111,6 +120,37 @@ def health_check_run(inst, log, args):
else:
log.info(json.dumps(report, indent=4))
+
+def health_check_run(inst, log, args):
+ """Connect to the local server using LDAPI, and perform various health checks
+ """
+
+ if args.list_errors:
+ _list_errors(log)
+ return
+
+ # update the args for connect_instance()
+ args.basedn = None
+ args.binddn = None
+ args.bindpw = None
+ args.starttls = None
+ args.pwdfile = None
+ args.prompt = False
+ dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
+ dsrc_inst = dsrc_arg_concat(args, dsrc_inst)
+ try:
+ inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
+ except Exception as e:
+ raise ValueError('Failed to connect to Directory Server instance: ' + str(e))
+
+ checks = args.check or dict(_list_targets(inst)).keys()
+
+ if args.list_checks or args.dry_run:
+ _print_checks(inst, checks)
+ return
+
+ _run(inst, log, args, _list_checks(inst, checks))
+
disconnect_instance(inst)
@@ -121,4 +161,9 @@ def create_parser(subparsers):
"remote Directory Server as this tool needs access to local resources, "
"otherwise the report may be inaccurate.")
run_healthcheck_parser.set_defaults(func=health_check_run)
-
+ run_healthcheck_parser.add_argument('--list-checks', action='store_true', help='List of known checks')
+ run_healthcheck_parser.add_argument('--list-errors', action='store_true', help='List of known error codes')
+ run_healthcheck_parser.add_argument('--dry-run', action='store_true', help='Do not execute the actual check, only list what would be done')
+ run_healthcheck_parser.add_argument('--check', nargs='+', default=None,
+ help='Areas to check. These can be obtained by --list-checks. Every element on the left of the colon (:)'
+ ' may be replaced by an asterisk if multiple options on the right are available.')
diff --git a/src/lib389/lib389/config.py b/src/lib389/lib389/config.py
index bdc6dc6..63edfaf 100644
--- a/src/lib389/lib389/config.py
+++ b/src/lib389/lib389/config.py
@@ -54,7 +54,6 @@ class Config(DSLdapObject):
]
self._compare_exclude = self._compare_exclude + config_compare_exclude
self._rdn_attribute = 'cn'
- self._lint_functions = [self._lint_hr_timestamp, self._lint_passwordscheme]
@property
def dn(self):
@@ -197,6 +196,10 @@ class Config(DSLdapObject):
fields = 'nsslapd-security nsslapd-ssl-check-hostname'.split()
return self._instance.getEntry(DN_CONFIG, attrlist=fields)
+ @classmethod
+ def lint_uid(cls):
+ return 'config'
+
def _lint_hr_timestamp(self):
hr_timestamp = self.get_attr_val('nsslapd-logging-hr-timestamps-enabled')
if ensure_bytes('on') != hr_timestamp:
@@ -229,20 +232,22 @@ class Encryption(DSLdapObject):
self._rdn_attribute = 'cn'
self._must_attributes = ['cn']
self._protected = True
- self._lint_functions = [self._lint_check_tls_version]
def create(self, rdn=None, properties={'cn': 'encryption', 'nsSSLClientAuth': 'allowed'}):
if rdn is not None:
self._log.debug("dn on cn=encryption is not None. This is a mistake.")
super(Encryption, self).create(properties=properties)
+ @classmethod
+ def lint_uid(cls):
+ return 'encryption'
+
def _lint_check_tls_version(self):
tls_min = self.get_attr_val('sslVersionMin')
if tls_min is not None and tls_min < ensure_bytes('TLS1.1'):
report = copy.deepcopy(DSELE0001)
report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
yield report
- yield None
@property
def ciphers(self):
@@ -474,7 +479,6 @@ class LDBMConfig(DSLdapObject):
self._dn = DN_CONFIG_LDBM
# config_compare_exclude = []
self._rdn_attribute = 'cn'
- self._lint_functions = []
self._protected = True
@@ -493,5 +497,4 @@ class BDB_LDBMConfig(DSLdapObject):
self._dn = DN_CONFIG_LDBM_BDB
self._config_compare_exclude = []
self._rdn_attribute = 'cn'
- self._lint_functions = []
self._protected = True
diff --git a/src/lib389/lib389/dseldif.py b/src/lib389/lib389/dseldif.py
index 5378e6e..96c9af9 100644
--- a/src/lib389/lib389/dseldif.py
+++ b/src/lib389/lib389/dseldif.py
@@ -16,6 +16,7 @@ from datetime import timedelta
from stat import ST_MODE
# from lib389.utils import print_nice_time
from lib389.paths import Paths
+from lib389._mapped_object_lint import DSLint
from lib389.lint import (
DSPERMLE0001,
DSPERMLE0002,
@@ -25,7 +26,7 @@ from lib389.lint import (
)
-class DSEldif(object):
+class DSEldif(DSLint):
"""A class for working with dse.ldif file
:param instance: An instance
@@ -58,15 +59,10 @@ class DSEldif(object):
processed_line = line
else:
processed_line = processed_line[:-1] + line[1:]
- self._lint_functions = [self._lint_nsstate]
- def lint(self):
- results = []
- for fn in self._lint_functions:
- for result in fn():
- if result is not None:
- results.append(result)
- return results
+ @classmethod
+ def lint_uid(cls):
+ return 'dseldif'
def _lint_nsstate(self):
suffixes = self.readNsState()
@@ -320,7 +316,7 @@ class DSEldif(object):
return states
-class FSChecks(object):
+class FSChecks(DSLint):
"""This is for the healthcheck feature, check commonly used system config files the
server uses. This is here for lack of a better place to add this class.
"""
@@ -344,17 +340,10 @@ class FSChecks(object):
'report': DSPERMLE0002
},
]
- self._lint_functions = [self._lint_file_perms]
- def lint(self):
- """Run a lint/healthcheck for this class
- """
- results = []
- for fn in self._lint_functions:
- for result in fn():
- if result is not None:
- results.append(result)
- return results
+ @classmethod
+ def lint_uid(cls):
+ return 'fschecks'
def _lint_file_perms(self):
"""Test file permissions are safe
diff --git a/src/lib389/lib389/encrypted_attributes.py b/src/lib389/lib389/encrypted_attributes.py
index 9afd2e6..2fa26ce 100644
--- a/src/lib389/lib389/encrypted_attributes.py
+++ b/src/lib389/lib389/encrypted_attributes.py
@@ -27,7 +27,6 @@ class EncryptedAttr(DSLdapObject):
self._must_attributes = ['cn', 'nsEncryptionAlgorithm']
self._create_objectclasses = ['top', 'nsAttributeEncryption']
self._protected = False
- self._lint_functions = []
class EncryptedAttrs(DSLdapObjects):
diff --git a/src/lib389/lib389/index.py b/src/lib389/lib389/index.py
index 6932883..a3d019d 100644
--- a/src/lib389/lib389/index.py
+++ b/src/lib389/lib389/index.py
@@ -41,7 +41,6 @@ class Index(DSLdapObject):
self._must_attributes = ['cn', 'nsSystemIndex', 'nsIndexType']
self._create_objectclasses = ['top', 'nsIndex']
self._protected = False
- self._lint_functions = []
class Indexes(DSLdapObjects):
@@ -77,7 +76,6 @@ class VLVSearch(DSLdapObject):
self._must_attributes = ['cn', 'vlvbase', 'vlvscope', 'vlvfilter']
self._create_objectclasses = ['top', 'vlvSearch']
self._protected = False
- self._lint_functions = []
self._be_name = None
def get_sorts(self):
@@ -163,7 +161,6 @@ class VLVIndex(DSLdapObject):
self._must_attributes = ['cn', 'vlvsort']
self._create_objectclasses = ['top', 'vlvIndex']
self._protected = False
- self._lint_functions = []
class VLVIndexes(DSLdapObjects):
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
index b5a305b..a103fee 100644
--- a/src/lib389/lib389/lint.py
+++ b/src/lib389/lib389/lint.py
@@ -14,8 +14,9 @@
DSBLE0001 = {
'dsle': 'DSBLE0001',
'severity': 'MEDIUM',
- 'items' : [],
- 'detail' : """This backend may be missing the correct mapping tree references. Mapping Trees allow
+ 'description': 'Possibly incorrect mapping tree.',
+ 'items': [],
+ 'detail': """This backend may be missing the correct mapping tree references. Mapping Trees allow
the directory server to determine which backend an operation is routed to in the
abscence of other information. This is extremely important for correct functioning
of LDAP ADD for example.
@@ -32,7 +33,7 @@ objectClass: extensibleObject
objectClass: nsMappingTree
""",
- 'fix' : """Either you need to create the mapping tree, or you need to repair the related
+ 'fix': """Either you need to create the mapping tree, or you need to repair the related
mapping tree. You will need to do this by hand by editing cn=config, or stopping
the instance and editing dse.ldif.
"""
@@ -41,25 +42,28 @@ the instance and editing dse.ldif.
DSBLE0002 = {
'dsle': 'DSBLE0002',
'severity': 'HIGH',
- 'items' : [],
- 'detail' : """Unable to query the backend. LDAP error (ERROR)""",
- 'fix' : """Check the server's error and access logs for more information."""
+ 'description': 'Unable to query backend.',
+ 'items': [],
+ 'detail': """Unable to query the backend. LDAP error (ERROR)""",
+ 'fix': """Check the server's error and access logs for more information."""
}
DSBLE0003 = {
'dsle': 'DSBLE0003',
'severity': 'LOW',
- 'items' : [],
- 'detail' : """The backend database has not been initialized yet""",
- 'fix' : """You need to import an LDIF file, or create the suffix entry, in order to initialize the database."""
+ 'description': 'Uninitialized backend database.',
+ 'items': [],
+ 'detail': """The backend database has not been initialized yet""",
+ 'fix': """You need to import an LDIF file, or create the suffix entry, in order to initialize the database."""
}
# Config checks
DSCLE0001 = {
- 'dsle' : 'DSCLE0001',
- 'severity' : 'LOW',
+ 'dsle': 'DSCLE0001',
+ 'severity': 'LOW',
+ 'description': 'Different log timestamp format.',
'items': ['cn=config', ],
- 'detail' : """nsslapd-logging-hr-timestamps-enabled changes the log format in directory server from
+ 'detail': """nsslapd-logging-hr-timestamps-enabled changes the log format in directory server from
[07/Jun/2017:17:15:58 +1000]
@@ -70,7 +74,7 @@ to
This actually provides a performance improvement. Additionally, this setting will be
removed in a future release.
""",
- 'fix' : """Set nsslapd-logging-hr-timestamps-enabled to on.
+ 'fix': """Set nsslapd-logging-hr-timestamps-enabled to on.
You can use 'dsconf' to set this attribute. Here is an example:
# dsconf slapd-YOUR_INSTANCE config replace nsslapd-logging-hr-timestamps-enabled=on"""
@@ -79,8 +83,9 @@ You can use 'dsconf' to set this attribute. Here is an example:
DSCLE0002 = {
'dsle': 'DSCLE0002',
'severity': 'HIGH',
- 'items' : ['cn=config', ],
- 'detail' : """Password storage schemes in Directory Server define how passwords are hashed via a
+ 'description': 'Weak passwordStorageScheme.',
+ 'items': ['cn=config', ],
+ 'detail': """Password storage schemes in Directory Server define how passwords are hashed via a
one-way mathematical function for storage. Knowing the hash it is difficult to gain
the input, but knowing the input you can easily compare the hash.
@@ -112,14 +117,15 @@ You can also use 'dsconf' to replace these values. Here is an example:
DSELE0001 = {
'dsle': 'DSELE0001',
'severity': 'MEDIUM',
- 'items' : ['cn=encryption,cn=config', ],
+ 'description': 'Weak TLS protocol version.',
+ 'items': ['cn=encryption,cn=config', ],
'detail': """This Directory Server may not be using strong TLS protocol versions. TLS1.0 is known to
have a number of issues with the protocol. Please see:
https://tools.ietf.org/html/rfc7457
It is advised you set this value to the maximum possible.""",
- 'fix' : """There are two options for setting the TLS minimum version allowed. You,
+ 'fix': """There are two options for setting the TLS minimum version allowed. You,
can set "sslVersionMin" in "cn=encryption,cn=config" to a version greater than "TLS1.0"
You can also use 'dsconf' to set this value. Here is an example:
@@ -137,7 +143,8 @@ minimum version, but doing this affects the entire system:
DSRILE0001 = {
'dsle': 'DSRILE0001',
'severity': 'LOW',
- 'items' : ['cn=referential integrity postoperation,cn=plugins,cn=config', ],
+ 'description': 'Referential integrity plugin may be slower.',
+ 'items': ['cn=referential integrity postoperation,cn=plugins,cn=config', ],
'detail': """The referential integrity plugin has an asynchronous processing mode.
This is controlled by the update-delay flag. When this value is 0, referential
integrity plugin processes these changes inside of the operation that modified
@@ -151,7 +158,7 @@ delays to your server by batching changes rather than smaller updates during syn
We advise that you set this value to 0, and enable referint on all masters as it provides a more predictable behaviour.
""",
- 'fix' : """Set referint-update-delay to 0.
+ 'fix': """Set referint-update-delay to 0.
You can use 'dsconf' to set this value. Here is an example:
@@ -164,12 +171,13 @@ You must restart the Directory Server for this change to take effect."""
DSRILE0002 = {
'dsle': 'DSRILE0002',
'severity': 'HIGH',
- 'items' : ['cn=referential integrity postoperation,cn=plugins,cn=config'],
+ 'description': 'Referential integrity plugin configured with unindexed attribute.',
+ 'items': ['cn=referential integrity postoperation,cn=plugins,cn=config'],
'detail': """The referential integrity plugin is configured to use an attribute (ATTR)
that does not have an "equality" index in backend (BACKEND).
Failure to have the proper indexing will lead to unindexed searches which
cause high CPU and can significantly slow the server down.""",
- 'fix' : """Check the attributes set in "referint-membership-attr" to make sure they have
+ 'fix': """Check the attributes set in "referint-membership-attr" to make sure they have
an index defined that has at least the equality "eq" index type. You will
need to reindex the database after adding the missing index type. Here is an
example using dsconf:
@@ -182,12 +190,13 @@ example using dsconf:
DSDSLE0001 = {
'dsle': 'DSDSLE0001',
'severity': 'HIGH',
- 'items' : ['Server', 'cn=config'],
+ 'description': 'Low disk space.',
+ 'items': ['Server', 'cn=config'],
'detail': """The disk partition used by the server (PARTITION), either for the database, the
configuration files, or the logs is over 90% full. If the partition becomes
completely filled serious problems can occur with the database or the server's
stability.""",
- 'fix' : """Attempt to free up disk space. Also try removing old rotated logs, or disable any
+ 'fix': """Attempt to free up disk space. Also try removing old rotated logs, or disable any
verbose logging levels that might have been set. You might consider enabling
the "Disk Monitoring" feature in cn=config to help prevent a disorderly shutdown
of the server:
@@ -210,9 +219,10 @@ Please see the Administration guide for more information:
DSREPLLE0001 = {
'dsle': 'DSREPLLE0001',
'severity': 'HIGH',
- 'items' : ['Replication', 'Agreement'],
+ 'description': 'Replication agreement not set to be synchronized.',
+ 'items': ['Replication', 'Agreement'],
'detail': """The replication agreement (AGMT) under "SUFFIX" is not in synchronization.""",
- 'fix' : """You may need to reinitialize this replication agreement. Please check the errors
+ 'fix': """You may need to reinitialize this replication agreement. Please check the errors
log for more information. If you do need to reinitialize the agreement you can do so
using dsconf. Here is an example:
@@ -223,9 +233,10 @@ using dsconf. Here is an example:
DSREPLLE0002 = {
'dsle': 'DSREPLLE0002',
'severity': 'LOW',
- 'items' : ['Replication', 'Conflict Entries'],
+ 'description': 'Replication conflict entries found.',
+ 'items': ['Replication', 'Conflict Entries'],
'detail': "There were COUNT conflict entries found under the replication suffix \"SUFFIX\".",
- 'fix' : """While conflict entries are expected to occur in an MMR environment, they
+ 'fix': """While conflict entries are expected to occur in an MMR environment, they
should be resolved. In regards to conflict entries there is always the original/counterpart
entry that has a normal DN, and then the conflict version of that entry. Technically both
entries are valid, you as the administrator, needs to decide which entry you want to keep.
@@ -253,38 +264,42 @@ can use the CLI tool "dsconf" to resolve the conflict. Here is an example:
DSREPLLE0003 = {
'dsle': 'DSREPLLE0003',
'severity': 'MEDIUM',
- 'items' : ['Replication', 'Agreement'],
+ 'description': 'Unsynchronized replication agreement.',
+ 'items': ['Replication', 'Agreement'],
'detail': """The replication agreement (AGMT) under "SUFFIX" is not in synchronization.
Status message: MSG""",
- 'fix' : """Replication is not in synchronization but it may recover. Continue to
+ 'fix': """Replication is not in synchronization but it may recover. Continue to
monitor this agreement."""
}
DSREPLLE0004 = {
'dsle': 'DSREPLLE0004',
'severity': 'MEDIUM',
- 'items' : ['Replication', 'Agreement'],
+ 'description': 'Unable to get replication agreement status.',
+ 'items': ['Replication', 'Agreement'],
'detail': """Failed to get the agreement status for agreement (AGMT) under "SUFFIX". Error (ERROR).""",
- 'fix' : """None"""
+ 'fix': """None"""
}
DSREPLLE0005 = {
'dsle': 'DSREPLLE0005',
'severity': 'MEDIUM',
- 'items' : ['Replication', 'Agreement'],
+ 'description': 'Replication consumer not reachable.',
+ 'items': ['Replication', 'Agreement'],
'detail': """The replication agreement (AGMT) under "SUFFIX" is not in synchronization,
because the consumer server is not reachable.""",
- 'fix' : """Check if the consumer is running, and also check the errors log for more information."""
+ 'fix': """Check if the consumer is running, and also check the errors log for more information."""
}
# Replication changelog
DSCLLE0001 = {
'dsle': 'DSCLLE0001',
'severity': 'LOW',
- 'items' : ['Replication', 'Changelog'],
+ 'description': 'Changelog trimming not configured.',
+ 'items': ['Replication', 'Changelog'],
'detail': """The replication changelog does have any kind of trimming configured. This will
lead to the changelog size growing indefinitely.""",
- 'fix' : """Configure changelog trimming, preferably by setting the maximum age of a changelog
+ 'fix': """Configure changelog trimming, preferably by setting the maximum age of a changelog
record. Here is an example:
# dsconf slapd-YOUR_INSTANCE replication set-changelog --max-age 30d"""
@@ -294,27 +309,30 @@ record. Here is an example:
DSCERTLE0001 = {
'dsle': 'DSCERTLE0001',
'severity': 'MEDIUM',
- 'items' : ['Expiring Certificate'],
+ 'description': 'Certificate about to expire.',
+ 'items': ['Expiring Certificate'],
'detail': """The certificate (CERT) will expire in less than 30 days""",
- 'fix' : """Renew the certificate before it expires to prevent disruptions with TLS connections."""
+ 'fix': """Renew the certificate before it expires to prevent disruptions with TLS connections."""
}
DSCERTLE0002 = {
'dsle': 'DSCERTLE0002',
'severity': 'HIGH',
- 'items' : ['Expired Certificate'],
+ 'description': 'Certificate expired.',
+ 'items': ['Expired Certificate'],
'detail': """The certificate (CERT) has expired""",
- 'fix' : """Renew or remove the certificate."""
+ 'fix': """Renew or remove the certificate."""
}
# Virtual Attrs & COS. Note - ATTR and SUFFIX are replaced by the reporting function
DSVIRTLE0001 = {
'dsle': 'DSVIRTLE0001',
'severity': 'HIGH',
- 'items' : ['Virtual Attributes'],
+ 'description': 'Virtual attribute indexed.',
+ 'items': ['Virtual Attributes'],
'detail': """You should not index virtual attributes, and as this will break searches that
use the attribute in a filter.""",
- 'fix' : """Remove the index for this attribute from the backend configuration.
+ 'fix': """Remove the index for this attribute from the backend configuration.
Here is an example using 'dsconf' to remove an index:
# dsconf slapd-YOUR_INSTANCE backend index delete --attr ATTR SUFFIX"""
@@ -324,10 +342,11 @@ Here is an example using 'dsconf' to remove an index:
DSPERMLE0001 = {
'dsle': 'DSPERMLE0001',
'severity': 'MEDIUM',
- 'items' : ['File Permissions'],
+ 'description': 'Incorrect file permissions.',
+ 'items': ['File Permissions'],
'detail': """The file "FILE" does not have the expected permissions (PERMS). This
can cause issues with replication and chaining.""",
- 'fix' : """Change the file permissions:
+ 'fix': """Change the file permissions:
# chmod PERMS FILE"""
}
@@ -336,10 +355,11 @@ can cause issues with replication and chaining.""",
DSPERMLE0002 = {
'dsle': 'DSPERMLE0002',
'severity': 'HIGH',
- 'items' : ['File Permissions'],
+ 'description': 'Incorrect security database file permissions.',
+ 'items': ['File Permissions'],
'detail': """The file "FILE" does not have the expected permissions (PERMS). The
security database pin/password files should only be readable by Directory Server user.""",
- 'fix' : """Change the file permissions:
+ 'fix': """Change the file permissions:
# chmod PERMS FILE"""
}
@@ -348,11 +368,12 @@ security database pin/password files should only be readable by Directory Server
DSSKEWLE0001 = {
'dsle': 'DSSKEWLE0001',
'severity': 'Low',
- 'items' : ['Replication'],
+ 'description': 'Medium time skew.',
+ 'items': ['Replication'],
'detail': """The time skew is over 6 hours. If this time skew continues to increase
to 24 hours then replication can potentially stop working. Please continue to
monitor the time skew offsets for increasing values.""",
- 'fix' : """Monitor the time skew and avoid making changes to the system time.
+ 'fix': """Monitor the time skew and avoid making changes to the system time.
Also look at https://access.redhat.com/documentation/en-us/red_hat_directory_server/11...
and find the paragraph "Too much time skew"."""
}
@@ -360,13 +381,14 @@ and find the paragraph "Too much time skew"."""
DSSKEWLE0002 = {
'dsle': 'DSSKEWLE0002',
'severity': 'Medium',
- 'items' : ['Replication'],
+ 'description': 'Major time skew.',
+ 'items': ['Replication'],
'detail': """The time skew is over 12 hours. If this time skew continues to increase
to 24 hours then replication can potentially stop working. Please continue to
monitor the time skew offsets for increasing values. Setting nsslapd-ignore-time-skew
to "on" on each replica will allow replication to continue, but if the time skew
continues to increase other more serious replication problems can occur.""",
- 'fix' : """Monitor the time skew and avoid making changes to the system time.
+ 'fix': """Monitor the time skew and avoid making changes to the system time.
If you get close to 24 hours of time skew replication may stop working.
In that case configure the server to ignore the time skew until the system
times can be fixed/synchronized:
@@ -380,12 +402,13 @@ and find the paragraph "Too much time skew"."""
DSSKEWLE0003 = {
'dsle': 'DSSKEWLE0003',
'severity': 'High',
- 'items' : ['Replication'],
+ 'description': 'Extensive time skew.',
+ 'items': ['Replication'],
'detail': """The time skew is over 24 hours. Setting nsslapd-ignore-time-skew
to "on" on each replica will allow replication to continue, but if the
time skew continues to increase other serious replication problems can
occur.""",
- 'fix' : """Avoid making changes to the system time, and make sure the clocks
+ 'fix': """Avoid making changes to the system time, and make sure the clocks
on all the replicas are correct. If you haven't set the server's
"ignore time skew" setting then do the following on all the replicas
until the time issues have been resolved:
diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py
index a111182..d8b7f7e 100644
--- a/src/lib389/lib389/monitor.py
+++ b/src/lib389/lib389/monitor.py
@@ -358,7 +358,10 @@ class MonitorDiskSpace(DSLdapObject):
def __init__(self, instance, dn=None):
super(MonitorDiskSpace, self).__init__(instance=instance, dn=dn)
self._dn = "cn=disk space,cn=monitor"
- self._lint_functions = [self._lint_disk_space]
+
+ @classmethod
+ def lint_uid(cls):
+ return 'monitor-disk-space'
def _lint_disk_space(self):
partitions = self.get_attr_vals_utf8_l("dsDisk")
diff --git a/src/lib389/lib389/nss_ssl.py b/src/lib389/lib389/nss_ssl.py
index d14e7ce..e257424 100644
--- a/src/lib389/lib389/nss_ssl.py
+++ b/src/lib389/lib389/nss_ssl.py
@@ -21,6 +21,7 @@ import subprocess
from datetime import datetime, timedelta
from subprocess import check_output, run, PIPE
from lib389.passwd import password_generate
+from lib389._mapped_object_lint import DSLint
from lib389.lint import DSCERTLE0001, DSCERTLE0002
from lib389.utils import ensure_str, format_cmd_list
import uuid
@@ -42,7 +43,7 @@ VALID_MIN = 61 # Days
log = logging.getLogger(__name__)
-class NssSsl(object):
+class NssSsl(DSLint):
def __init__(self, dirsrv=None, dbpassword=None, dbpath=None):
self.dirsrv = dirsrv
self._certdb = dbpath
@@ -56,18 +57,14 @@ class NssSsl(object):
else:
self.dbpassword = dbpassword
- self.db_files = {"dbm_backend": ["%s/%s" % (self._certdb, f) for f in ("key3.db", "cert8.db", "secmod.db")],
- "sql_backend": ["%s/%s" % (self._certdb, f) for f in ("key4.db", "cert9.db", "pkcs11.txt")],
- "support": ["%s/%s" % (self._certdb, f) for f in ("noise.txt", PIN_TXT, PWD_TXT)]}
- self._lint_functions = [self._lint_certificate_expiration,]
-
- def lint(self):
- results = []
- for fn in self._lint_functions:
- for result in fn():
- if result is not None:
- results.append(result)
- return results
+ self.db_files = {group: [f"{self._certdb}/{f}" for f in files]
+ for group, files in {"dbm_backend": ("key3.db", "cert8.db", "secmod.db"),
+ "sql_backend": ("key4.db", "cert9.db", "pkcs11.txt"),
+ "support": ("noise.txt", PIN_TXT, PWD_TXT)}.items()}
+
+ @classmethod
+ def lint_uid(cls):
+ return 'ssl'
def _lint_certificate_expiration(self):
"""Check all the certificates in the db if they will expire within 30 days
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
index c012026..a12e579 100644
--- a/src/lib389/lib389/plugins.py
+++ b/src/lib389/lib389/plugins.py
@@ -431,7 +431,6 @@ class ReferentialIntegrityPlugin(Plugin):
'referint-logfile',
'referint-membership-attr',
])
- self._lint_functions = [self._lint_update_delay, self._lint_attr_indexes]
def create(self, rdn=None, properties=None, basedn=None):
"""Create an instance of the plugin"""
@@ -443,6 +442,10 @@ class ReferentialIntegrityPlugin(Plugin):
properties['referint-logfile'] = referint_log
return super(ReferentialIntegrityPlugin, self).create(rdn, properties, basedn)
+ @classmethod
+ def lint_uid(cls):
+ return 'refint'
+
def _lint_update_delay(self):
if self.status():
delay = self.get_attr_val_int("referint-update-delay")
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index f8adb3c..f575e58 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -1049,7 +1049,10 @@ class Changelog5(DSLdapObject):
'extensibleobject',
]
self._protected = False
- self._lint_functions = [self._lint_cl_trimming]
+
+ @classmethod
+ def lint_uid(cls):
+ return 'changelog'
def _lint_cl_trimming(self):
"""Check that cl trimming is at least defined to prevent unbounded growth"""
@@ -1120,7 +1123,10 @@ class Replica(DSLdapObject):
self._create_objectclasses.append('extensibleobject')
self._protected = False
self._suffix = None
- self._lint_functions = [self._lint_agmts_status, self._lint_conflicts]
+
+ @classmethod
+ def lint_uid(cls):
+ return 'replication'
def _lint_agmts_status(self):
replicas = Replicas(self._instance).list()
diff --git a/src/lib389/lib389/tests/mapped_object_lint_test.py b/src/lib389/lib389/tests/mapped_object_lint_test.py
new file mode 100644
index 0000000..a4ca0ea
--- /dev/null
+++ b/src/lib389/lib389/tests/mapped_object_lint_test.py
@@ -0,0 +1,78 @@
+from typing import List
+
+import pytest
+
+from lib389._mapped_object_lint import (
+ DSLint,
+ DSLints,
+ DSLintMethodSpec
+)
+
+
+def test_dslint():
+ class DS(DSLint):
+ def lint_uid(self) -> str:
+ return self.param
+
+ def __init__(self, param):
+ self.param = param
+ self.suffixes = ['suffixA', 'suffixB']
+
+ def _lint_nsstate(self, spec: DSLintMethodSpec = None):
+ if spec == List:
+ yield from self.suffixes
+ else:
+ to_lint = [spec] if spec else self._lint_nsstate(spec=List)
+ for tl in to_lint:
+ if tl == 'suffixA':
+ pass
+ elif tl == 'suffixB':
+ yield 'suffixB is bad'
+ else:
+ raise ValueError('There is no such suffix')
+
+ def _lint_second(self):
+ yield from ()
+
+ def _lint_third(self):
+ yield from ['this is a fail']
+
+ class DSs(DSLints):
+ def list(self):
+ for i in [DS("ma"), DS("mb")]:
+ yield i
+
+ # single
+ inst = DS("a")
+ inst_lints = {'nsstate:suffixA', 'nsstate:suffixB', 'second', 'third'}
+
+ assert inst.param == "a"
+
+ assert set(dict(inst.lint_list()).keys()) == inst_lints
+
+ assert set(dict(inst.lint_list('nsstate')).keys()) \
+ == {f'nsstate:suffix{s}' for s in "AB"}
+
+ assert list(inst._lint_nsstate(spec=List)) == ['suffixA', 'suffixB']
+ assert list(inst.lint()) == ['suffixB is bad', 'this is a fail']
+
+ assert list(inst.lint('nsstate')) == ['suffixB is bad']
+ assert list(inst.lint('nsstate:suffixA')) == []
+ assert list(inst.lint('nsstate:suffixB')) == ['suffixB is bad']
+ with pytest.raises(ValueError):
+ list(inst.lint('nonexistent'))
+
+ # multiple
+ insts = DSs()
+
+ assert insts.lint_list
+ assert insts.lint
+
+ assert set(dict(insts.lint_list()).keys()) \
+ == {f'{m}:{s}' for m in ['ma', 'mb'] for s in inst_lints}
+ assert set(dict(insts.lint_list('*')).keys()) \
+ == {f'{m}:{s}' for m in ['ma', 'mb'] for s in inst_lints}
+ assert set(dict(insts.lint_list('*:nsstate')).keys()) \
+ == {f'{m}:nsstate:suffix{s}' for m in ['ma', 'mb'] for s in "AB"}
+ assert set(dict(insts.lint_list('mb:nsstate')).keys()) \
+ == {f'mb:nsstate:suffix{s}' for s in "AB"}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
3 years, 10 months
[389-ds-base] branch 389-ds-base-1.4.3 updated: Issue 50746 - Add option to healthcheck to list all the lint reports
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mhonek pushed a commit to branch 389-ds-base-1.4.3
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.3 by this push:
new 66cd2a9 Issue 50746 - Add option to healthcheck to list all the lint reports
66cd2a9 is described below
commit 66cd2a90602825b1025794a96c576684f829e6ec
Author: Matus Honek <mhonek(a)redhat.com>
AuthorDate: Mon Dec 2 14:53:31 2019 +0100
Issue 50746 - Add option to healthcheck to list all the lint reports
Bug Description:
Healthcheck lacks a way to find out what checks are available.
Fix Description:
Add dsctl healthcheck options to list available checks, known error
codes, and ability to run cehcks selectively. The checks are rather
hierarchically structured and in some cases matchable by patterns (by
use of asterisk).
Fixes https://pagure.io/389-ds-base/issue/50746
Author: Matus Honek <mhonek(a)redhat.com>
Review by: Mark, William, Simon (thanks for the patience!)
(cherry picked from commit 4a55322c7bdb0b9ff57428ad0dc2e4d943572a69)
---
src/lib389/cli/dsctl | 1 +
src/lib389/lib389/_mapped_object.py | 34 +----
src/lib389/lib389/_mapped_object_lint.py | 157 +++++++++++++++++++++
src/lib389/lib389/backend.py | 13 +-
src/lib389/lib389/cli_ctl/health.py | 116 ++++++++++-----
src/lib389/lib389/config.py | 13 +-
src/lib389/lib389/dseldif.py | 29 ++--
src/lib389/lib389/encrypted_attributes.py | 1 -
src/lib389/lib389/index.py | 3 -
src/lib389/lib389/lint.py | 125 +++++++++-------
src/lib389/lib389/monitor.py | 5 +-
src/lib389/lib389/nss_ssl.py | 23 ++-
src/lib389/lib389/plugins.py | 5 +-
src/lib389/lib389/replica.py | 10 +-
src/lib389/lib389/tests/mapped_object_lint_test.py | 78 ++++++++++
15 files changed, 448 insertions(+), 165 deletions(-)
diff --git a/src/lib389/cli/dsctl b/src/lib389/cli/dsctl
index fd9bd87..9deda70 100755
--- a/src/lib389/cli/dsctl
+++ b/src/lib389/cli/dsctl
@@ -64,6 +64,7 @@ cli_dbgen.create_parser(subparsers)
argcomplete.autocomplete(parser)
+
# handle a control-c gracefully
def signal_handler(signal, frame):
print('\n\nExiting...')
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
index ce0ebfe..c608376 100644
--- a/src/lib389/lib389/_mapped_object.py
+++ b/src/lib389/lib389/_mapped_object.py
@@ -15,6 +15,7 @@ import json
from functools import partial
from lib389._entry import Entry
from lib389._constants import DIRSRV_STATE_ONLINE
+from lib389._mapped_object_lint import DSLint, DSLints
from lib389.utils import (
ensure_bytes, ensure_str, ensure_int, ensure_list_bytes, ensure_list_str,
ensure_list_int, display_log_value, display_log_data
@@ -82,7 +83,7 @@ class DSLogging(object):
self._log.setLevel(logging.INFO)
-class DSLdapObject(DSLogging):
+class DSLdapObject(DSLogging, DSLint):
"""A single instance of DSLdapObjects
:param instance: An instance
@@ -107,7 +108,6 @@ class DSLdapObject(DSLogging):
self._must_attributes = None
# attributes, we don't want to compare
self._compare_exclude = ['entryid', 'modifytimestamp', 'nsuniqueid']
- self._lint_functions = None
self._server_controls = None
self._client_controls = None
self._object_filter = '(objectClass=*)'
@@ -985,38 +985,10 @@ class DSLdapObject(DSLogging):
"""
return self._create(rdn, properties, basedn, ensure=True)
- def lint(self):
- """Override this to create a linter for a type. This means that we can detect
- and report common administrative errors in the server from our cli and
- rest tools.
-
- The structure of a result is::
-
- {
- dsle: '<identifier>'. dsle == ds lint error. Will be a code unique to
- this module for the error, IE DSBLE0001.
- severity: '[HIGH:MEDIUM:LOW]'. severity of the error.
- items: '(dn,dn,dn)'. List of affected DNs or names.
- detail: 'msg ...'. An explination of the error.
- fix: 'msg ...'. Steps to resolve the error.
- }
-
- :returns: An array of these dicts, on None if there are no errors.
- """
-
- if not self._lint_functions:
- return None
- results = []
- for fn in self._lint_functions:
- for result in fn():
- if result is not None:
- results.append(result)
- return results
-
# A challenge of this, is how do we manage indexes? They have two naming attributes....
-class DSLdapObjects(DSLogging):
+class DSLdapObjects(DSLogging, DSLints):
"""The object represents the next idea: "Everything is an instance of something
that exists in this way", i.e. we unite LDAP entries by some
set of parameters with the object.
diff --git a/src/lib389/lib389/_mapped_object_lint.py b/src/lib389/lib389/_mapped_object_lint.py
new file mode 100644
index 0000000..2d03de9
--- /dev/null
+++ b/src/lib389/lib389/_mapped_object_lint.py
@@ -0,0 +1,157 @@
+from abc import ABC, abstractmethod
+from functools import partial
+from inspect import signature
+from typing import (
+ Callable,
+ List,
+ Optional,
+ Tuple,
+ Union,
+ Type,
+ Generator,
+ Any
+)
+
+
+DSLintSpec = Tuple[str, Callable]
+DSLintParsedSpec = Tuple[Optional[str], Optional[str]]
+DSLintClassSpec = Generator[DSLintSpec, None, None]
+DSLintMethodSpec = Union[str, None, Type[List]]
+DSLintResults = Generator[Any, None, None]
+
+
+class DSLint():
+ """In a super-class, create a method with name beginning with `_lint_`
+ which would yield results (as described below). Such a method will
+ then be available to the `lint()` method of the class.
+
+ `lint_list`: takes a spec and yields available lints, recursively
+ `lint`: takes a spac and runs lints according to it, yielding results if any
+
+ `spec`: is a colon-separated string, with prefix matching a method name and suffix
+ being passed down to the method.
+
+ A class inheriting from hereby class shall implement a method named `lint_uid()` which
+ returns a pretty name of the object. This is to be used by a higher level code.
+
+ Each lint method has to have a name prefix with _lint_. It may accept an optional
+ parameter `spec` in which case:
+ - it has to accept typing.List class as a parameter, in which case it shall yield
+ all possible lint specs for that method
+ - it receives the suffix provided to the `spec` of hereby `lint` method (as mentioned above)
+
+ This means that we can detect and report common administrative errors
+ in the server from our cli and rest tools.
+
+ The structure of a result shall be:
+
+ {
+ dsle: '<identifier>'. dsle == ds lint error. Will be a code unique to
+ this module for the error, IE DSBLE0001.
+ severity: '[HIGH:MEDIUM:LOW]'. severity of the error.
+ items: '(dn,dn,dn)'. List of affected DNs or names.
+ detail: 'msg ...'. An explination of the error.
+ fix: 'msg ...'. Steps to resolve the error.
+ }
+ """
+
+ @classmethod
+ def _dslint_fname(cls, method: Callable) -> Optional[str]:
+ """Return a pretty name for a method."""
+ if callable(method) and method.__name__.startswith('_lint_'):
+ return method.__name__[len('_lint_'):]
+ else:
+ return None
+
+ @staticmethod
+ def _dslint_parse_spec(spec: Optional[str]) -> DSLintParsedSpec:
+ """Split `spec` to prefix and suffix."""
+ wanted, *rest = spec.split(':', 1) if spec else (None, None)
+ return (wanted if wanted not in [None, '*'] else None,
+ rest[0] if rest else None)
+
+ @classmethod
+ def _dslint_make_spec(cls, method: Callable, spec: Optional[str] = None) -> str:
+ """Build a new spec from prefix (`method` name) and suffix (`spec`)."""
+ fname = cls._dslint_fname(method)
+ return f'{fname}:{spec}' if spec else fname
+
+ def lint_list(self, spec: Optional[str] = None) -> DSLintClassSpec:
+ """Yield specs the object provides.
+
+ This yields from each lint method yielding all specs it can provide.
+ """
+
+ assert hasattr(self, 'lint_uid')
+
+ # Find _lint_ methods
+ # NOTE: There is a caveat: don't you dare try to getattr on a @property, or
+ # you get it executed. That's why the following line's complexity.
+ fs = [getattr(self, f) for f in dir(self)
+ if f.startswith('_lint_') and self._dslint_fname(getattr(self, f))]
+
+ # Filter acording to the `spec`
+ wanted, rest = self._dslint_parse_spec(spec)
+ if wanted:
+ try:
+ fs = [next(filter(lambda f: self._dslint_fname(f) == wanted, fs))]
+ except StopIteration:
+ raise ValueError('there is no such lint function')
+
+ # Yield known specs
+ for f in fs:
+ fspec_t = signature(f).parameters.get('spec', None)
+ if fspec_t:
+ assert fspec_t.annotation == DSLintMethodSpec
+ for fspec in [rest] if rest else f(spec=List):
+ yield self._dslint_make_spec(f, fspec), partial(f, spec=fspec)
+ else:
+ yield self._dslint_make_spec(f, rest), f
+
+ def lint(self, spec: DSLintMethodSpec = None) -> DSLintResults:
+ """Lint the object according to the `spec`."""
+
+ if spec == List:
+ yield from self.lint_list()
+ else:
+ for fn, f in self.lint_list(spec):
+ yield from f()
+
+
+class DSLints():
+ """This is a meta class to provide lint functionality to classes that provide
+ method `list` which returns list of objects that inherit from DSLint.
+
+ Calling `lint` or `lint_list` method yields from respective object's methods.
+
+ The `spec` is a colon-separated string. Its prefix matches the respective object's
+ `lint_uid` (or all when asterisk); the suffix is passed down to the respective
+ object's method.
+ """
+
+ def lint_list(self, spec: Optional[str] = None) -> DSLintClassSpec:
+ """Yield specs the objects returned by `list` method provide."""
+
+ assert hasattr(self, 'list')
+
+ # Filter acording to the `spec`
+ wanted, rest_spec = DSLint._dslint_parse_spec(spec)
+ if wanted in [None, '*']:
+ clss = self.list()
+ else:
+ clss = (cls for cls in self.list() if cls.lint_uid() == wanted)
+
+ # Yield known specs
+ for cls in clss:
+ for fn, f in cls.lint_list(spec=rest_spec):
+ yield (f'{cls.lint_uid()}:{fn}',
+ partial(f, rest_spec) if rest_spec else f)
+
+ def lint(self, spec: DSLintMethodSpec = None) -> DSLintResults:
+ """Lint the objects returned by `list` method according to the `spec`."""
+
+ if spec == List:
+ yield from self.lint_list()
+ else:
+ for obj in self.list():
+ yield from obj.lint()
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index 4f752f4..8863ad1 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -393,6 +393,10 @@ class BackendLegacy(object):
replace = [(ldap.MOD_REPLACE, 'nsslapd-require-index', 'on')]
self.modify_s(dn, replace)
+ @classmethod
+ def lint_uid(cls):
+ return 'backends'
+
class Backend(DSLdapObject):
"""Backend DSLdapObject with:
@@ -413,10 +417,12 @@ class Backend(DSLdapObject):
self._must_attributes = ['nsslapd-suffix', 'cn']
self._create_objectclasses = ['top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE]
self._protected = False
- self._lint_functions = [self._lint_mappingtree, self._lint_search, self._lint_virt_attrs]
# Check if a mapping tree for this suffix exists.
self._mts = MappingTrees(self._instance)
+ def lint_uid(self):
+ return self.get_attr_val_utf8_l('cn').lower()
+
def _lint_virt_attrs(self):
"""Check if any virtual attribute are incorrectly indexed"""
indexes = self.get_indexes()
@@ -497,7 +503,6 @@ class Backend(DSLdapObject):
result = DSBLE0001
result['items'] = [bename, ]
yield result
- return None
def create_sample_entries(self, version):
"""Creates sample entries under nsslapd-suffix value
@@ -848,6 +853,10 @@ class Backends(DSLdapObjects):
self._childobject = Backend
self._basedn = DN_LDBM
+ @classmethod
+ def lint_uid(cls):
+ return 'backends'
+
def import_ldif(self, be_name, ldifs, chunk_size=None, encrypted=False, gen_uniq_id=None, only_core=False,
include_suffixes=None, exclude_suffixes=None):
"""Do an import of the suffix"""
diff --git a/src/lib389/lib389/cli_ctl/health.py b/src/lib389/lib389/cli_ctl/health.py
index 3d15ad8..6333a75 100644
--- a/src/lib389/lib389/cli_ctl/health.py
+++ b/src/lib389/lib389/cli_ctl/health.py
@@ -7,6 +7,9 @@
# --- END COPYRIGHT BLOCK ---
import json
+import re
+from lib389._mapped_object import DSLdapObjects
+from lib389._mapped_object_lint import DSLint
from lib389.cli_base import connect_instance, disconnect_instance
from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat
from lib389.backend import Backends
@@ -15,17 +18,17 @@ from lib389.monitor import MonitorDiskSpace
from lib389.replica import Replica, Changelog5
from lib389.nss_ssl import NssSsl
from lib389.dseldif import FSChecks, DSEldif
+from lib389 import lint
from lib389 import plugins
from lib389._constants import DSRC_HOME
+from functools import partial
+from typing import Iterable
-# These get all instances, then check them all.
-CHECK_MANY_OBJECTS = [
- Backends,
-]
# These get single instances and check them.
CHECK_OBJECTS = [
Config,
+ Backends,
Encryption,
FSChecks,
plugins.ReferentialIntegrityPlugin,
@@ -52,44 +55,51 @@ def _format_check_output(log, result, idx):
log.info(result['fix'])
-def health_check_run(inst, log, args):
- """Connect to the local server using LDAPI, and perform various health checks
- """
+def _list_targets(inst):
+ for c in CHECK_OBJECTS:
+ o = c(inst)
+ yield o.lint_uid(), o
+
+
+def _list_errors(log):
+ for r in map(partial(getattr, lint),
+ filter(partial(re.match, r'^DS'),
+ dir(lint))):
+ log.info(f"{r['dsle']} :: {r['description']}")
- # update the args for connect_instance()
- args.basedn = None
- args.binddn = None
- args.bindpw = None
- args.starttls = None
- args.pwdfile = None
- args.prompt = False
- dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
- dsrc_inst = dsrc_arg_concat(args, dsrc_inst)
- try:
- inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
- except Exception as e:
- raise ValueError('Failed to connect to Directory Server instance: ' + str(e))
+def _list_checks(inst, specs: Iterable[str]):
+ o_uids = dict(_list_targets(inst))
+ for s in specs:
+ wanted, rest = DSLint._dslint_parse_spec(s)
+ if wanted == '*':
+ raise ValueError('Unexpected spec selector asterisk')
+
+ if wanted in o_uids:
+ for l in o_uids[wanted].lint_list(rest):
+ yield o_uids[wanted], l
+ else:
+ raise ValueError('No such object specifier')
+
+
+def _print_checks(inst, specs: Iterable[str]) -> None:
+ for o, s in _list_checks(inst, specs):
+ print(f'{o.lint_uid()}:{s[0]}')
+
+
+def _run(inst, log, args, checks):
if not args.json:
log.info("Beginning lint report, this could take a while ...")
+
report = []
- for lo in CHECK_MANY_OBJECTS:
+ for o, s in checks:
if not args.json:
- log.info("Checking %s ..." % lo.__name__)
- lo_inst = lo(inst)
- for clo in lo_inst.list():
- result = clo.lint()
- if result is not None:
- report += result
- for lo in CHECK_OBJECTS:
- if not args.json:
- log.info("Checking %s ..." % lo.__name__)
- lo_inst = lo(inst)
- result = lo_inst.lint()
- if result is not None:
- report += result
+ log.info(f"Checking {o.lint_uid()}:{s[0]} ...")
+ report += o.lint(s[0]) or []
+
if not args.json:
log.info("Healthcheck complete.")
+
count = len(report)
if count == 0:
if not args.json:
@@ -110,6 +120,37 @@ def health_check_run(inst, log, args):
else:
log.info(json.dumps(report, indent=4))
+
+def health_check_run(inst, log, args):
+ """Connect to the local server using LDAPI, and perform various health checks
+ """
+
+ if args.list_errors:
+ _list_errors(log)
+ return
+
+ # update the args for connect_instance()
+ args.basedn = None
+ args.binddn = None
+ args.bindpw = None
+ args.starttls = None
+ args.pwdfile = None
+ args.prompt = False
+ dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
+ dsrc_inst = dsrc_arg_concat(args, dsrc_inst)
+ try:
+ inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
+ except Exception as e:
+ raise ValueError('Failed to connect to Directory Server instance: ' + str(e))
+
+ checks = args.check or dict(_list_targets(inst)).keys()
+
+ if args.list_checks or args.dry_run:
+ _print_checks(inst, checks)
+ return
+
+ _run(inst, log, args, _list_checks(inst, checks))
+
disconnect_instance(inst)
@@ -120,4 +161,9 @@ def create_parser(subparsers):
"remote Directory Server as this tool needs access to local resources, "
"otherwise the report may be inaccurate.")
run_healthcheck_parser.set_defaults(func=health_check_run)
-
+ run_healthcheck_parser.add_argument('--list-checks', action='store_true', help='List of known checks')
+ run_healthcheck_parser.add_argument('--list-errors', action='store_true', help='List of known error codes')
+ run_healthcheck_parser.add_argument('--dry-run', action='store_true', help='Do not execute the actual check, only list what would be done')
+ run_healthcheck_parser.add_argument('--check', nargs='+', default=None,
+ help='Areas to check. These can be obtained by --list-checks. Every element on the left of the colon (:)'
+ ' may be replaced by an asterisk if multiple options on the right are available.')
diff --git a/src/lib389/lib389/config.py b/src/lib389/lib389/config.py
index a29d024..aa4c92b 100644
--- a/src/lib389/lib389/config.py
+++ b/src/lib389/lib389/config.py
@@ -54,7 +54,6 @@ class Config(DSLdapObject):
]
self._compare_exclude = self._compare_exclude + config_compare_exclude
self._rdn_attribute = 'cn'
- self._lint_functions = [self._lint_hr_timestamp, self._lint_passwordscheme]
@property
def dn(self):
@@ -197,6 +196,10 @@ class Config(DSLdapObject):
fields = 'nsslapd-security nsslapd-ssl-check-hostname'.split()
return self._instance.getEntry(DN_CONFIG, attrlist=fields)
+ @classmethod
+ def lint_uid(cls):
+ return 'config'
+
def _lint_hr_timestamp(self):
hr_timestamp = self.get_attr_val('nsslapd-logging-hr-timestamps-enabled')
if ensure_bytes('on') != hr_timestamp:
@@ -242,20 +245,22 @@ class Encryption(DSLdapObject):
self._rdn_attribute = 'cn'
self._must_attributes = ['cn']
self._protected = True
- self._lint_functions = [self._lint_check_tls_version]
def create(self, rdn=None, properties={'cn': 'encryption', 'nsSSLClientAuth': 'allowed'}):
if rdn is not None:
self._log.debug("dn on cn=encryption is not None. This is a mistake.")
super(Encryption, self).create(properties=properties)
+ @classmethod
+ def lint_uid(cls):
+ return 'encryption'
+
def _lint_check_tls_version(self):
tls_min = self.get_attr_val('sslVersionMin')
if tls_min is not None and tls_min < ensure_bytes('TLS1.1'):
report = copy.deepcopy(DSELE0001)
report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
yield report
- yield None
@property
def ciphers(self):
@@ -487,7 +492,6 @@ class LDBMConfig(DSLdapObject):
self._dn = DN_CONFIG_LDBM
# config_compare_exclude = []
self._rdn_attribute = 'cn'
- self._lint_functions = []
self._protected = True
@@ -506,5 +510,4 @@ class BDB_LDBMConfig(DSLdapObject):
self._dn = DN_CONFIG_LDBM_BDB
self._config_compare_exclude = []
self._rdn_attribute = 'cn'
- self._lint_functions = []
self._protected = True
diff --git a/src/lib389/lib389/dseldif.py b/src/lib389/lib389/dseldif.py
index 5378e6e..96c9af9 100644
--- a/src/lib389/lib389/dseldif.py
+++ b/src/lib389/lib389/dseldif.py
@@ -16,6 +16,7 @@ from datetime import timedelta
from stat import ST_MODE
# from lib389.utils import print_nice_time
from lib389.paths import Paths
+from lib389._mapped_object_lint import DSLint
from lib389.lint import (
DSPERMLE0001,
DSPERMLE0002,
@@ -25,7 +26,7 @@ from lib389.lint import (
)
-class DSEldif(object):
+class DSEldif(DSLint):
"""A class for working with dse.ldif file
:param instance: An instance
@@ -58,15 +59,10 @@ class DSEldif(object):
processed_line = line
else:
processed_line = processed_line[:-1] + line[1:]
- self._lint_functions = [self._lint_nsstate]
- def lint(self):
- results = []
- for fn in self._lint_functions:
- for result in fn():
- if result is not None:
- results.append(result)
- return results
+ @classmethod
+ def lint_uid(cls):
+ return 'dseldif'
def _lint_nsstate(self):
suffixes = self.readNsState()
@@ -320,7 +316,7 @@ class DSEldif(object):
return states
-class FSChecks(object):
+class FSChecks(DSLint):
"""This is for the healthcheck feature, check commonly used system config files the
server uses. This is here for lack of a better place to add this class.
"""
@@ -344,17 +340,10 @@ class FSChecks(object):
'report': DSPERMLE0002
},
]
- self._lint_functions = [self._lint_file_perms]
- def lint(self):
- """Run a lint/healthcheck for this class
- """
- results = []
- for fn in self._lint_functions:
- for result in fn():
- if result is not None:
- results.append(result)
- return results
+ @classmethod
+ def lint_uid(cls):
+ return 'fschecks'
def _lint_file_perms(self):
"""Test file permissions are safe
diff --git a/src/lib389/lib389/encrypted_attributes.py b/src/lib389/lib389/encrypted_attributes.py
index 9afd2e6..2fa26ce 100644
--- a/src/lib389/lib389/encrypted_attributes.py
+++ b/src/lib389/lib389/encrypted_attributes.py
@@ -27,7 +27,6 @@ class EncryptedAttr(DSLdapObject):
self._must_attributes = ['cn', 'nsEncryptionAlgorithm']
self._create_objectclasses = ['top', 'nsAttributeEncryption']
self._protected = False
- self._lint_functions = []
class EncryptedAttrs(DSLdapObjects):
diff --git a/src/lib389/lib389/index.py b/src/lib389/lib389/index.py
index 6932883..a3d019d 100644
--- a/src/lib389/lib389/index.py
+++ b/src/lib389/lib389/index.py
@@ -41,7 +41,6 @@ class Index(DSLdapObject):
self._must_attributes = ['cn', 'nsSystemIndex', 'nsIndexType']
self._create_objectclasses = ['top', 'nsIndex']
self._protected = False
- self._lint_functions = []
class Indexes(DSLdapObjects):
@@ -77,7 +76,6 @@ class VLVSearch(DSLdapObject):
self._must_attributes = ['cn', 'vlvbase', 'vlvscope', 'vlvfilter']
self._create_objectclasses = ['top', 'vlvSearch']
self._protected = False
- self._lint_functions = []
self._be_name = None
def get_sorts(self):
@@ -163,7 +161,6 @@ class VLVIndex(DSLdapObject):
self._must_attributes = ['cn', 'vlvsort']
self._create_objectclasses = ['top', 'vlvIndex']
self._protected = False
- self._lint_functions = []
class VLVIndexes(DSLdapObjects):
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
index b5a305b..a103fee 100644
--- a/src/lib389/lib389/lint.py
+++ b/src/lib389/lib389/lint.py
@@ -14,8 +14,9 @@
DSBLE0001 = {
'dsle': 'DSBLE0001',
'severity': 'MEDIUM',
- 'items' : [],
- 'detail' : """This backend may be missing the correct mapping tree references. Mapping Trees allow
+ 'description': 'Possibly incorrect mapping tree.',
+ 'items': [],
+ 'detail': """This backend may be missing the correct mapping tree references. Mapping Trees allow
the directory server to determine which backend an operation is routed to in the
abscence of other information. This is extremely important for correct functioning
of LDAP ADD for example.
@@ -32,7 +33,7 @@ objectClass: extensibleObject
objectClass: nsMappingTree
""",
- 'fix' : """Either you need to create the mapping tree, or you need to repair the related
+ 'fix': """Either you need to create the mapping tree, or you need to repair the related
mapping tree. You will need to do this by hand by editing cn=config, or stopping
the instance and editing dse.ldif.
"""
@@ -41,25 +42,28 @@ the instance and editing dse.ldif.
DSBLE0002 = {
'dsle': 'DSBLE0002',
'severity': 'HIGH',
- 'items' : [],
- 'detail' : """Unable to query the backend. LDAP error (ERROR)""",
- 'fix' : """Check the server's error and access logs for more information."""
+ 'description': 'Unable to query backend.',
+ 'items': [],
+ 'detail': """Unable to query the backend. LDAP error (ERROR)""",
+ 'fix': """Check the server's error and access logs for more information."""
}
DSBLE0003 = {
'dsle': 'DSBLE0003',
'severity': 'LOW',
- 'items' : [],
- 'detail' : """The backend database has not been initialized yet""",
- 'fix' : """You need to import an LDIF file, or create the suffix entry, in order to initialize the database."""
+ 'description': 'Uninitialized backend database.',
+ 'items': [],
+ 'detail': """The backend database has not been initialized yet""",
+ 'fix': """You need to import an LDIF file, or create the suffix entry, in order to initialize the database."""
}
# Config checks
DSCLE0001 = {
- 'dsle' : 'DSCLE0001',
- 'severity' : 'LOW',
+ 'dsle': 'DSCLE0001',
+ 'severity': 'LOW',
+ 'description': 'Different log timestamp format.',
'items': ['cn=config', ],
- 'detail' : """nsslapd-logging-hr-timestamps-enabled changes the log format in directory server from
+ 'detail': """nsslapd-logging-hr-timestamps-enabled changes the log format in directory server from
[07/Jun/2017:17:15:58 +1000]
@@ -70,7 +74,7 @@ to
This actually provides a performance improvement. Additionally, this setting will be
removed in a future release.
""",
- 'fix' : """Set nsslapd-logging-hr-timestamps-enabled to on.
+ 'fix': """Set nsslapd-logging-hr-timestamps-enabled to on.
You can use 'dsconf' to set this attribute. Here is an example:
# dsconf slapd-YOUR_INSTANCE config replace nsslapd-logging-hr-timestamps-enabled=on"""
@@ -79,8 +83,9 @@ You can use 'dsconf' to set this attribute. Here is an example:
DSCLE0002 = {
'dsle': 'DSCLE0002',
'severity': 'HIGH',
- 'items' : ['cn=config', ],
- 'detail' : """Password storage schemes in Directory Server define how passwords are hashed via a
+ 'description': 'Weak passwordStorageScheme.',
+ 'items': ['cn=config', ],
+ 'detail': """Password storage schemes in Directory Server define how passwords are hashed via a
one-way mathematical function for storage. Knowing the hash it is difficult to gain
the input, but knowing the input you can easily compare the hash.
@@ -112,14 +117,15 @@ You can also use 'dsconf' to replace these values. Here is an example:
DSELE0001 = {
'dsle': 'DSELE0001',
'severity': 'MEDIUM',
- 'items' : ['cn=encryption,cn=config', ],
+ 'description': 'Weak TLS protocol version.',
+ 'items': ['cn=encryption,cn=config', ],
'detail': """This Directory Server may not be using strong TLS protocol versions. TLS1.0 is known to
have a number of issues with the protocol. Please see:
https://tools.ietf.org/html/rfc7457
It is advised you set this value to the maximum possible.""",
- 'fix' : """There are two options for setting the TLS minimum version allowed. You,
+ 'fix': """There are two options for setting the TLS minimum version allowed. You,
can set "sslVersionMin" in "cn=encryption,cn=config" to a version greater than "TLS1.0"
You can also use 'dsconf' to set this value. Here is an example:
@@ -137,7 +143,8 @@ minimum version, but doing this affects the entire system:
DSRILE0001 = {
'dsle': 'DSRILE0001',
'severity': 'LOW',
- 'items' : ['cn=referential integrity postoperation,cn=plugins,cn=config', ],
+ 'description': 'Referential integrity plugin may be slower.',
+ 'items': ['cn=referential integrity postoperation,cn=plugins,cn=config', ],
'detail': """The referential integrity plugin has an asynchronous processing mode.
This is controlled by the update-delay flag. When this value is 0, referential
integrity plugin processes these changes inside of the operation that modified
@@ -151,7 +158,7 @@ delays to your server by batching changes rather than smaller updates during syn
We advise that you set this value to 0, and enable referint on all masters as it provides a more predictable behaviour.
""",
- 'fix' : """Set referint-update-delay to 0.
+ 'fix': """Set referint-update-delay to 0.
You can use 'dsconf' to set this value. Here is an example:
@@ -164,12 +171,13 @@ You must restart the Directory Server for this change to take effect."""
DSRILE0002 = {
'dsle': 'DSRILE0002',
'severity': 'HIGH',
- 'items' : ['cn=referential integrity postoperation,cn=plugins,cn=config'],
+ 'description': 'Referential integrity plugin configured with unindexed attribute.',
+ 'items': ['cn=referential integrity postoperation,cn=plugins,cn=config'],
'detail': """The referential integrity plugin is configured to use an attribute (ATTR)
that does not have an "equality" index in backend (BACKEND).
Failure to have the proper indexing will lead to unindexed searches which
cause high CPU and can significantly slow the server down.""",
- 'fix' : """Check the attributes set in "referint-membership-attr" to make sure they have
+ 'fix': """Check the attributes set in "referint-membership-attr" to make sure they have
an index defined that has at least the equality "eq" index type. You will
need to reindex the database after adding the missing index type. Here is an
example using dsconf:
@@ -182,12 +190,13 @@ example using dsconf:
DSDSLE0001 = {
'dsle': 'DSDSLE0001',
'severity': 'HIGH',
- 'items' : ['Server', 'cn=config'],
+ 'description': 'Low disk space.',
+ 'items': ['Server', 'cn=config'],
'detail': """The disk partition used by the server (PARTITION), either for the database, the
configuration files, or the logs is over 90% full. If the partition becomes
completely filled serious problems can occur with the database or the server's
stability.""",
- 'fix' : """Attempt to free up disk space. Also try removing old rotated logs, or disable any
+ 'fix': """Attempt to free up disk space. Also try removing old rotated logs, or disable any
verbose logging levels that might have been set. You might consider enabling
the "Disk Monitoring" feature in cn=config to help prevent a disorderly shutdown
of the server:
@@ -210,9 +219,10 @@ Please see the Administration guide for more information:
DSREPLLE0001 = {
'dsle': 'DSREPLLE0001',
'severity': 'HIGH',
- 'items' : ['Replication', 'Agreement'],
+ 'description': 'Replication agreement not set to be synchronized.',
+ 'items': ['Replication', 'Agreement'],
'detail': """The replication agreement (AGMT) under "SUFFIX" is not in synchronization.""",
- 'fix' : """You may need to reinitialize this replication agreement. Please check the errors
+ 'fix': """You may need to reinitialize this replication agreement. Please check the errors
log for more information. If you do need to reinitialize the agreement you can do so
using dsconf. Here is an example:
@@ -223,9 +233,10 @@ using dsconf. Here is an example:
DSREPLLE0002 = {
'dsle': 'DSREPLLE0002',
'severity': 'LOW',
- 'items' : ['Replication', 'Conflict Entries'],
+ 'description': 'Replication conflict entries found.',
+ 'items': ['Replication', 'Conflict Entries'],
'detail': "There were COUNT conflict entries found under the replication suffix \"SUFFIX\".",
- 'fix' : """While conflict entries are expected to occur in an MMR environment, they
+ 'fix': """While conflict entries are expected to occur in an MMR environment, they
should be resolved. In regards to conflict entries there is always the original/counterpart
entry that has a normal DN, and then the conflict version of that entry. Technically both
entries are valid, you as the administrator, needs to decide which entry you want to keep.
@@ -253,38 +264,42 @@ can use the CLI tool "dsconf" to resolve the conflict. Here is an example:
DSREPLLE0003 = {
'dsle': 'DSREPLLE0003',
'severity': 'MEDIUM',
- 'items' : ['Replication', 'Agreement'],
+ 'description': 'Unsynchronized replication agreement.',
+ 'items': ['Replication', 'Agreement'],
'detail': """The replication agreement (AGMT) under "SUFFIX" is not in synchronization.
Status message: MSG""",
- 'fix' : """Replication is not in synchronization but it may recover. Continue to
+ 'fix': """Replication is not in synchronization but it may recover. Continue to
monitor this agreement."""
}
DSREPLLE0004 = {
'dsle': 'DSREPLLE0004',
'severity': 'MEDIUM',
- 'items' : ['Replication', 'Agreement'],
+ 'description': 'Unable to get replication agreement status.',
+ 'items': ['Replication', 'Agreement'],
'detail': """Failed to get the agreement status for agreement (AGMT) under "SUFFIX". Error (ERROR).""",
- 'fix' : """None"""
+ 'fix': """None"""
}
DSREPLLE0005 = {
'dsle': 'DSREPLLE0005',
'severity': 'MEDIUM',
- 'items' : ['Replication', 'Agreement'],
+ 'description': 'Replication consumer not reachable.',
+ 'items': ['Replication', 'Agreement'],
'detail': """The replication agreement (AGMT) under "SUFFIX" is not in synchronization,
because the consumer server is not reachable.""",
- 'fix' : """Check if the consumer is running, and also check the errors log for more information."""
+ 'fix': """Check if the consumer is running, and also check the errors log for more information."""
}
# Replication changelog
DSCLLE0001 = {
'dsle': 'DSCLLE0001',
'severity': 'LOW',
- 'items' : ['Replication', 'Changelog'],
+ 'description': 'Changelog trimming not configured.',
+ 'items': ['Replication', 'Changelog'],
'detail': """The replication changelog does have any kind of trimming configured. This will
lead to the changelog size growing indefinitely.""",
- 'fix' : """Configure changelog trimming, preferably by setting the maximum age of a changelog
+ 'fix': """Configure changelog trimming, preferably by setting the maximum age of a changelog
record. Here is an example:
# dsconf slapd-YOUR_INSTANCE replication set-changelog --max-age 30d"""
@@ -294,27 +309,30 @@ record. Here is an example:
DSCERTLE0001 = {
'dsle': 'DSCERTLE0001',
'severity': 'MEDIUM',
- 'items' : ['Expiring Certificate'],
+ 'description': 'Certificate about to expire.',
+ 'items': ['Expiring Certificate'],
'detail': """The certificate (CERT) will expire in less than 30 days""",
- 'fix' : """Renew the certificate before it expires to prevent disruptions with TLS connections."""
+ 'fix': """Renew the certificate before it expires to prevent disruptions with TLS connections."""
}
DSCERTLE0002 = {
'dsle': 'DSCERTLE0002',
'severity': 'HIGH',
- 'items' : ['Expired Certificate'],
+ 'description': 'Certificate expired.',
+ 'items': ['Expired Certificate'],
'detail': """The certificate (CERT) has expired""",
- 'fix' : """Renew or remove the certificate."""
+ 'fix': """Renew or remove the certificate."""
}
# Virtual Attrs & COS. Note - ATTR and SUFFIX are replaced by the reporting function
DSVIRTLE0001 = {
'dsle': 'DSVIRTLE0001',
'severity': 'HIGH',
- 'items' : ['Virtual Attributes'],
+ 'description': 'Virtual attribute indexed.',
+ 'items': ['Virtual Attributes'],
'detail': """You should not index virtual attributes, and as this will break searches that
use the attribute in a filter.""",
- 'fix' : """Remove the index for this attribute from the backend configuration.
+ 'fix': """Remove the index for this attribute from the backend configuration.
Here is an example using 'dsconf' to remove an index:
# dsconf slapd-YOUR_INSTANCE backend index delete --attr ATTR SUFFIX"""
@@ -324,10 +342,11 @@ Here is an example using 'dsconf' to remove an index:
DSPERMLE0001 = {
'dsle': 'DSPERMLE0001',
'severity': 'MEDIUM',
- 'items' : ['File Permissions'],
+ 'description': 'Incorrect file permissions.',
+ 'items': ['File Permissions'],
'detail': """The file "FILE" does not have the expected permissions (PERMS). This
can cause issues with replication and chaining.""",
- 'fix' : """Change the file permissions:
+ 'fix': """Change the file permissions:
# chmod PERMS FILE"""
}
@@ -336,10 +355,11 @@ can cause issues with replication and chaining.""",
DSPERMLE0002 = {
'dsle': 'DSPERMLE0002',
'severity': 'HIGH',
- 'items' : ['File Permissions'],
+ 'description': 'Incorrect security database file permissions.',
+ 'items': ['File Permissions'],
'detail': """The file "FILE" does not have the expected permissions (PERMS). The
security database pin/password files should only be readable by Directory Server user.""",
- 'fix' : """Change the file permissions:
+ 'fix': """Change the file permissions:
# chmod PERMS FILE"""
}
@@ -348,11 +368,12 @@ security database pin/password files should only be readable by Directory Server
DSSKEWLE0001 = {
'dsle': 'DSSKEWLE0001',
'severity': 'Low',
- 'items' : ['Replication'],
+ 'description': 'Medium time skew.',
+ 'items': ['Replication'],
'detail': """The time skew is over 6 hours. If this time skew continues to increase
to 24 hours then replication can potentially stop working. Please continue to
monitor the time skew offsets for increasing values.""",
- 'fix' : """Monitor the time skew and avoid making changes to the system time.
+ 'fix': """Monitor the time skew and avoid making changes to the system time.
Also look at https://access.redhat.com/documentation/en-us/red_hat_directory_server/11...
and find the paragraph "Too much time skew"."""
}
@@ -360,13 +381,14 @@ and find the paragraph "Too much time skew"."""
DSSKEWLE0002 = {
'dsle': 'DSSKEWLE0002',
'severity': 'Medium',
- 'items' : ['Replication'],
+ 'description': 'Major time skew.',
+ 'items': ['Replication'],
'detail': """The time skew is over 12 hours. If this time skew continues to increase
to 24 hours then replication can potentially stop working. Please continue to
monitor the time skew offsets for increasing values. Setting nsslapd-ignore-time-skew
to "on" on each replica will allow replication to continue, but if the time skew
continues to increase other more serious replication problems can occur.""",
- 'fix' : """Monitor the time skew and avoid making changes to the system time.
+ 'fix': """Monitor the time skew and avoid making changes to the system time.
If you get close to 24 hours of time skew replication may stop working.
In that case configure the server to ignore the time skew until the system
times can be fixed/synchronized:
@@ -380,12 +402,13 @@ and find the paragraph "Too much time skew"."""
DSSKEWLE0003 = {
'dsle': 'DSSKEWLE0003',
'severity': 'High',
- 'items' : ['Replication'],
+ 'description': 'Extensive time skew.',
+ 'items': ['Replication'],
'detail': """The time skew is over 24 hours. Setting nsslapd-ignore-time-skew
to "on" on each replica will allow replication to continue, but if the
time skew continues to increase other serious replication problems can
occur.""",
- 'fix' : """Avoid making changes to the system time, and make sure the clocks
+ 'fix': """Avoid making changes to the system time, and make sure the clocks
on all the replicas are correct. If you haven't set the server's
"ignore time skew" setting then do the following on all the replicas
until the time issues have been resolved:
diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py
index 73750c3..4ac7d71 100644
--- a/src/lib389/lib389/monitor.py
+++ b/src/lib389/lib389/monitor.py
@@ -358,7 +358,10 @@ class MonitorDiskSpace(DSLdapObject):
def __init__(self, instance, dn=None):
super(MonitorDiskSpace, self).__init__(instance=instance, dn=dn)
self._dn = "cn=disk space,cn=monitor"
- self._lint_functions = [self._lint_disk_space]
+
+ @classmethod
+ def lint_uid(cls):
+ return 'monitor-disk-space'
def _lint_disk_space(self):
partitions = self.get_attr_vals_utf8_l("dsDisk")
diff --git a/src/lib389/lib389/nss_ssl.py b/src/lib389/lib389/nss_ssl.py
index d14e7ce..e257424 100644
--- a/src/lib389/lib389/nss_ssl.py
+++ b/src/lib389/lib389/nss_ssl.py
@@ -21,6 +21,7 @@ import subprocess
from datetime import datetime, timedelta
from subprocess import check_output, run, PIPE
from lib389.passwd import password_generate
+from lib389._mapped_object_lint import DSLint
from lib389.lint import DSCERTLE0001, DSCERTLE0002
from lib389.utils import ensure_str, format_cmd_list
import uuid
@@ -42,7 +43,7 @@ VALID_MIN = 61 # Days
log = logging.getLogger(__name__)
-class NssSsl(object):
+class NssSsl(DSLint):
def __init__(self, dirsrv=None, dbpassword=None, dbpath=None):
self.dirsrv = dirsrv
self._certdb = dbpath
@@ -56,18 +57,14 @@ class NssSsl(object):
else:
self.dbpassword = dbpassword
- self.db_files = {"dbm_backend": ["%s/%s" % (self._certdb, f) for f in ("key3.db", "cert8.db", "secmod.db")],
- "sql_backend": ["%s/%s" % (self._certdb, f) for f in ("key4.db", "cert9.db", "pkcs11.txt")],
- "support": ["%s/%s" % (self._certdb, f) for f in ("noise.txt", PIN_TXT, PWD_TXT)]}
- self._lint_functions = [self._lint_certificate_expiration,]
-
- def lint(self):
- results = []
- for fn in self._lint_functions:
- for result in fn():
- if result is not None:
- results.append(result)
- return results
+ self.db_files = {group: [f"{self._certdb}/{f}" for f in files]
+ for group, files in {"dbm_backend": ("key3.db", "cert8.db", "secmod.db"),
+ "sql_backend": ("key4.db", "cert9.db", "pkcs11.txt"),
+ "support": ("noise.txt", PIN_TXT, PWD_TXT)}.items()}
+
+ @classmethod
+ def lint_uid(cls):
+ return 'ssl'
def _lint_certificate_expiration(self):
"""Check all the certificates in the db if they will expire within 30 days
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
index f68a1d1..89e6602 100644
--- a/src/lib389/lib389/plugins.py
+++ b/src/lib389/lib389/plugins.py
@@ -431,7 +431,6 @@ class ReferentialIntegrityPlugin(Plugin):
'referint-logfile',
'referint-membership-attr',
])
- self._lint_functions = [self._lint_update_delay, self._lint_attr_indexes]
def create(self, rdn=None, properties=None, basedn=None):
"""Create an instance of the plugin"""
@@ -443,6 +442,10 @@ class ReferentialIntegrityPlugin(Plugin):
properties['referint-logfile'] = referint_log
return super(ReferentialIntegrityPlugin, self).create(rdn, properties, basedn)
+ @classmethod
+ def lint_uid(cls):
+ return 'refint'
+
def _lint_update_delay(self):
if self.status():
delay = self.get_attr_val_int("referint-update-delay")
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index f8adb3c..f575e58 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -1049,7 +1049,10 @@ class Changelog5(DSLdapObject):
'extensibleobject',
]
self._protected = False
- self._lint_functions = [self._lint_cl_trimming]
+
+ @classmethod
+ def lint_uid(cls):
+ return 'changelog'
def _lint_cl_trimming(self):
"""Check that cl trimming is at least defined to prevent unbounded growth"""
@@ -1120,7 +1123,10 @@ class Replica(DSLdapObject):
self._create_objectclasses.append('extensibleobject')
self._protected = False
self._suffix = None
- self._lint_functions = [self._lint_agmts_status, self._lint_conflicts]
+
+ @classmethod
+ def lint_uid(cls):
+ return 'replication'
def _lint_agmts_status(self):
replicas = Replicas(self._instance).list()
diff --git a/src/lib389/lib389/tests/mapped_object_lint_test.py b/src/lib389/lib389/tests/mapped_object_lint_test.py
new file mode 100644
index 0000000..a4ca0ea
--- /dev/null
+++ b/src/lib389/lib389/tests/mapped_object_lint_test.py
@@ -0,0 +1,78 @@
+from typing import List
+
+import pytest
+
+from lib389._mapped_object_lint import (
+ DSLint,
+ DSLints,
+ DSLintMethodSpec
+)
+
+
+def test_dslint():
+ class DS(DSLint):
+ def lint_uid(self) -> str:
+ return self.param
+
+ def __init__(self, param):
+ self.param = param
+ self.suffixes = ['suffixA', 'suffixB']
+
+ def _lint_nsstate(self, spec: DSLintMethodSpec = None):
+ if spec == List:
+ yield from self.suffixes
+ else:
+ to_lint = [spec] if spec else self._lint_nsstate(spec=List)
+ for tl in to_lint:
+ if tl == 'suffixA':
+ pass
+ elif tl == 'suffixB':
+ yield 'suffixB is bad'
+ else:
+ raise ValueError('There is no such suffix')
+
+ def _lint_second(self):
+ yield from ()
+
+ def _lint_third(self):
+ yield from ['this is a fail']
+
+ class DSs(DSLints):
+ def list(self):
+ for i in [DS("ma"), DS("mb")]:
+ yield i
+
+ # single
+ inst = DS("a")
+ inst_lints = {'nsstate:suffixA', 'nsstate:suffixB', 'second', 'third'}
+
+ assert inst.param == "a"
+
+ assert set(dict(inst.lint_list()).keys()) == inst_lints
+
+ assert set(dict(inst.lint_list('nsstate')).keys()) \
+ == {f'nsstate:suffix{s}' for s in "AB"}
+
+ assert list(inst._lint_nsstate(spec=List)) == ['suffixA', 'suffixB']
+ assert list(inst.lint()) == ['suffixB is bad', 'this is a fail']
+
+ assert list(inst.lint('nsstate')) == ['suffixB is bad']
+ assert list(inst.lint('nsstate:suffixA')) == []
+ assert list(inst.lint('nsstate:suffixB')) == ['suffixB is bad']
+ with pytest.raises(ValueError):
+ list(inst.lint('nonexistent'))
+
+ # multiple
+ insts = DSs()
+
+ assert insts.lint_list
+ assert insts.lint
+
+ assert set(dict(insts.lint_list()).keys()) \
+ == {f'{m}:{s}' for m in ['ma', 'mb'] for s in inst_lints}
+ assert set(dict(insts.lint_list('*')).keys()) \
+ == {f'{m}:{s}' for m in ['ma', 'mb'] for s in inst_lints}
+ assert set(dict(insts.lint_list('*:nsstate')).keys()) \
+ == {f'{m}:nsstate:suffix{s}' for m in ['ma', 'mb'] for s in "AB"}
+ assert set(dict(insts.lint_list('mb:nsstate')).keys()) \
+ == {f'mb:nsstate:suffix{s}' for s in "AB"}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
3 years, 10 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Issue 51118 - UI - improve modal validation when creating an instance
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new db11e8e Issue 51118 - UI - improve modal validation when creating an instance
db11e8e is described below
commit db11e8ecc7f964d6f7fd41b724cfb7981a1f4f69
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Fri May 29 13:42:43 2020 -0400
Issue 51118 - UI - improve modal validation when creating an instance
Description: Do not enable the "create" button until all the fields are
valid (DN's, port numbers, passwords, etc).
Improve layout and handling of optional database settings.
Add a json argument to dscreate so the UI can report any
failure text. Also improve error reporting in dscreate.
relates: https://pagure.io/389-ds-base/issue/51118
Reviewed by: firstyear & spichugi(Thanks!!)
Improve validation error messages
Fix allowed characters
---
src/cockpit/389-console/src/ds.jsx | 1191 +-----------------
.../389-console/src/{ds.jsx => dsModals.jsx} | 1282 ++++++--------------
src/cockpit/389-console/src/lib/server/ldapi.jsx | 1 -
src/cockpit/389-console/src/lib/tools.jsx | 2 +-
src/lib389/cli/dscreate | 12 +-
src/lib389/lib389/instance/setup.py | 7 +-
6 files changed, 388 insertions(+), 2107 deletions(-)
diff --git a/src/cockpit/389-console/src/ds.jsx b/src/cockpit/389-console/src/ds.jsx
index 53aa5cb..691a6f2 100644
--- a/src/cockpit/389-console/src/ds.jsx
+++ b/src/cockpit/389-console/src/ds.jsx
@@ -1,16 +1,15 @@
import cockpit from "cockpit";
import React from "react";
-import PropTypes from "prop-types";
import { Plugins } from "./plugins.jsx";
import { Database } from "./database.jsx";
import { Monitor } from "./monitor.jsx";
import { Schema } from "./schema.jsx";
import { Replication } from "./replication.jsx";
import { Server } from "./server.jsx";
-import { ConfirmPopup, DoubleConfirmModal, NotificationController } from "./lib/notifications.jsx";
-import { BackupTable } from "./lib/database/databaseTables.jsx";
-import { BackupModal, RestoreModal, DeleteBackupModal } from "./lib/database/backups.jsx";
-import { log_cmd, bad_file_name } from "./lib/tools.jsx";
+import { DoubleConfirmModal, NotificationController } from "./lib/notifications.jsx";
+import { ManageBackupsModal, SchemaReloadModal, CreateInstanceModal } from "./dsModals.jsx";
+import { log_cmd } from "./lib/tools.jsx";
+
import {
Nav,
NavItem,
@@ -20,18 +19,7 @@ import {
TabContent,
TabPane,
ProgressBar,
- FormControl,
- FormGroup,
- ControlLabel,
- Radio,
- Form,
- noop,
- Checkbox,
Spinner,
- Row,
- Modal,
- Icon,
- Col,
Button
} from "patternfly-react";
import "./css/ds.css";
@@ -722,1175 +710,4 @@ export class DSInstance extends React.Component {
}
}
-class CreateInstanceModal extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- createServerId: "",
- createPort: 389,
- createSecurePort: 636,
- createDM: "cn=Directory Manager",
- createDMPassword: "",
- createDMPasswordConfirm: "",
- createDBSuffix: "",
- createDBName: "",
- createTLSCert: true,
- createInitDB: "noInit",
- loadingCreate: false
- };
-
- this.handleFieldChange = this.handleFieldChange.bind(this);
- this.createInstance = this.createInstance.bind(this);
- }
-
- handleFieldChange(e) {
- let value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
- if (e.target.type === "number") {
- if (e.target.value) {
- value = parseInt(e.target.value);
- } else {
- value = 1;
- }
- }
- this.setState({
- [e.target.id]: value
- });
- }
-
- createInstance() {
- const {
- createServerId,
- createPort,
- createSecurePort,
- createDM,
- createDMPassword,
- createDMPasswordConfirm,
- createDBSuffix,
- createDBName,
- createTLSCert,
- createInitDB
- } = this.state;
- const { closeHandler, addNotification, loadInstanceList } = this.props;
-
- let setup_inf =
- "[general]\n" +
- "config_version = 2\n" +
- "full_machine_name = FQDN\n\n" +
- "[slapd]\n" +
- "user = dirsrv\n" +
- "group = dirsrv\n" +
- "instance_name = INST_NAME\n" +
- "port = PORT\n" +
- "root_dn = ROOTDN\n" +
- "root_password = ROOTPW\n" +
- "secure_port = SECURE_PORT\n" +
- "self_sign_cert = SELF_SIGN\n";
-
- // Server ID
- let newServerId = createServerId;
- if (newServerId === "") {
- addNotification("warning", "Instance Name is required.");
- return;
- }
- newServerId = newServerId.replace(/^slapd-/i, ""); // strip "slapd-"
- if (newServerId === "admin") {
- addNotification("warning", "Instance Name 'admin' is reserved, please choose a different name");
- return;
- }
- if (newServerId.length > 80) {
- addNotification(
- "warning",
- "Instance name is too long, it must not exceed 80 characters"
- );
- return;
- }
- if (newServerId.match(/^[#%:A-Za-z0-9_-]+$/g)) {
- setup_inf = setup_inf.replace("INST_NAME", newServerId);
- } else {
- addNotification(
- "warning",
- "Instance name can only contain letters, numbers, and: # % : - _"
- );
- return;
- }
-
- // Port
- if (createPort < 1 || createPort > 65535) {
- addNotification("warning", "Port must be a number between 1 and 65534!");
- return;
- } else {
- setup_inf = setup_inf.replace("PORT", createPort);
- }
-
- // Secure Port
- if (createSecurePort < 1 || createSecurePort > 65535) {
- addNotification("warning", "Secure Port must be a number between 1 and 65534!");
- return;
- } else {
- setup_inf = setup_inf.replace("SECURE_PORT", createSecurePort);
- }
-
- // Root DN
- if (createDM === "") {
- addNotification("warning", "You must provide a Directory Manager DN");
- return;
- } else {
- setup_inf = setup_inf.replace("ROOTDN", createDM);
- }
-
- // Setup Self-Signed Certs
- if (createTLSCert) {
- setup_inf = setup_inf.replace("SELF_SIGN", "True");
- } else {
- setup_inf = setup_inf.replace("SELF_SIGN", "False");
- }
-
- // Root DN password
- if (createDMPassword != createDMPasswordConfirm) {
- addNotification("warning", "Directory Manager passwords do not match!");
- return;
- } else if (createDMPassword == "") {
- addNotification("warning", "Directory Manager password can not be empty!");
- return;
- } else if (createDMPassword.length < 8) {
- addNotification(
- "warning",
- "Directory Manager password must have at least 8 characters"
- );
- return;
- } else {
- setup_inf = setup_inf.replace("ROOTPW", createDMPassword);
- }
-
- // Backend/Suffix
- if (
- (createDBName != "" && createDBSuffix == "") ||
- (createDBName == "" && createDBSuffix != "")
- ) {
- if (createDBName == "") {
- addNotification(
- "warning",
- "If you specify a backend suffix, you must also specify a backend name"
- );
- return;
- } else {
- addNotification(
- "warning",
- "If you specify a backend name, you must also specify a backend suffix"
- );
- return;
- }
- }
- if (createDBName != "") {
- // We definitely have a backend name and suffix, next validate the suffix is a DN
- let dn_regex = new RegExp("^([A-Za-z]+=.*)");
- if (dn_regex.test(createDBSuffix)) {
- // It's valid, add it
- setup_inf += "\n[backend-" + createDBName + "]\nsuffix = " + createDBSuffix + "\n";
- } else {
- // Not a valid DN
- addNotification("warning", "Invalid DN for Backend Suffix");
- return;
- }
-
- if (createInitDB === "createSample") {
- setup_inf += "\nsample_entries = yes\n";
- }
- if (createInitDB === "createSuffix") {
- setup_inf += "\ncreate_suffix_entry = yes\n";
- }
- }
-
- /*
- * Here are steps we take to create the instance
- *
- * [1] Get FQDN Name for nsslapd-localhost setting in setup file
- * [2] Create a file for the inf setup parameters
- * [3] Set strict permissions on that file
- * [4] Populate the new setup file with settings (including cleartext password)
- * [5] Create the instance
- * [6] Remove setup file
- */
- this.setState({
- loadingCreate: true
- });
- cockpit
-
- .spawn(["hostnamectl", "status", "--static"], { superuser: true, err: "message" })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.setState({
- loadingCreate: false
- });
- addNotification("error", `Failed to get hostname!", ${errMsg.desc}`);
- })
- .done(data => {
- /*
- * We have FQDN, so set the hostname in inf file, and create the setup file
- */
- setup_inf = setup_inf.replace("FQDN", data);
- let setup_file = "/tmp/389-setup-" + new Date().getTime() + ".inf";
- let rm_cmd = ["rm", setup_file];
- let create_file_cmd = ["touch", setup_file];
- cockpit
- .spawn(create_file_cmd, { superuser: true, err: "message" })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.setState({
- loadingCreate: false
- });
- addNotification(
- "error",
- `Failed to create installation file!" ${errMsg.desc}`
- );
- })
- .done(_ => {
- /*
- * We have our new setup file, now set permissions on that setup file before we add sensitive data
- */
- let chmod_cmd = ["chmod", "600", setup_file];
- cockpit
- .spawn(chmod_cmd, { superuser: true, err: "message" })
- .fail(err => {
- let errMsg = JSON.parse(err);
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
- this.setState({
- loadingCreate: false
- });
- addNotification(
- "error",
- `Failed to set permission on setup file ${setup_file}: ${
- errMsg.desc
- }`
- );
- })
- .done(_ => {
- /*
- * Success we have our setup file and it has the correct permissions.
- * Now populate the setup file...
- */
- let cmd = [
- "/bin/sh",
- "-c",
- '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file
- ];
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.setState({
- loadingCreate: false
- });
- addNotification(
- "error",
- `Failed to populate installation file! ${errMsg.desc}`
- );
- })
- .done(_ => {
- /*
- * Next, create the instance...
- */
- let cmd = ["dscreate", "from-file", setup_file];
- cockpit
- .spawn(cmd, {
- superuser: true,
- err: "message"
- })
- .fail(_ => {
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
- this.setState({
- loadingCreate: false
- });
- addNotification(
- "error",
- "Failed to create instance!"
- );
- })
- .done(_ => {
- // Success!!! Now cleanup everything up...
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
- this.setState({
- loadingCreate: false
- });
-
- loadInstanceList(createServerId);
- addNotification(
- "success",
- `Successfully created instance: slapd-${createServerId}`
- );
- closeHandler();
- });
- });
- });
- });
- });
- }
-
- render() {
- const { showModal, closeHandler } = this.props;
-
- const {
- loadingCreate,
- createServerId,
- createPort,
- createSecurePort,
- createDM,
- createDMPassword,
- createDMPasswordConfirm,
- createDBSuffix,
- createDBName,
- createTLSCert,
- createInitDB
- } = this.state;
-
- let createSpinner = "";
- if (loadingCreate) {
- createSpinner = (
- <Row>
- <div className="ds-margin-top-lg ds-modal-spinner">
- <Spinner loading inline size="lg" />
- Creating instance...
- </div>
- </Row>
- );
- }
-
- return (
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title className="ds-center">Create New Server Instance</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Form horizontal>
- <FormGroup controlId="createServerId">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The instance name, this is what gets appended to 'slapi-'. The instance name can only contain letters, numbers, and: # % : - _"
- >
- Instance Name
- </Col>
- <Col sm={7}>
- <FormControl
- id="createServerId"
- type="text"
- placeholder="Your_Instance_Name"
- value={createServerId}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createPort">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The server port number"
- >
- Port
- </Col>
- <Col sm={7}>
- <FormControl
- type="number"
- min="0"
- max="65535"
- value={createPort}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createSecurePort">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The secure port number for TLS connections"
- >
- Secure Port
- </Col>
- <Col sm={7}>
- <FormControl
- type="number"
- min="0"
- max="65535"
- value={createSecurePort}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createTLSCert">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="Create a self-signed certificate database"
- >
- Create Self-Signed TLS Certificate
- </Col>
- <Col sm={7}>
- <Checkbox
- id="createTLSCert"
- checked={createTLSCert}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDM">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The DN for the unrestricted user"
- >
- Directory Manager DN
- </Col>
- <Col sm={7}>
- <FormControl
- type="text"
- id="createDM"
- onChange={this.handleFieldChange}
- value={createDM}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDMPassword">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="Directory Manager password."
- >
- Directory Manager Password
- </Col>
- <Col sm={7}>
- <FormControl
- id="createDMPassword"
- type="password"
- placeholder="Enter password"
- onChange={this.handleFieldChange}
- value={createDMPassword}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDMPasswordConfirm">
- <Col componentClass={ControlLabel} sm={5} title="Confirm password.">
- Confirm Password
- </Col>
- <Col sm={7}>
- <FormControl
- id="createDMPasswordConfirm"
- type="password"
- placeholder="Confirm password"
- onChange={this.handleFieldChange}
- value={createDMPasswordConfirm}
- />
- </Col>
- </FormGroup>
- <hr />
- <h5 className="ds-center">Optional Database Settings</h5>
- <FormGroup className="ds-margin-top-lg" controlId="createDBSuffix">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN)"
- >
- Database Suffix
- </Col>
- <Col sm={7}>
- <FormControl
- type="text"
- id="createDBSuffix"
- placeholder="e.g. dc=example,dc=com"
- onChange={this.handleFieldChange}
- value={createDBSuffix}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDBName">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends."
- >
- Database Name
- </Col>
- <Col sm={7}>
- <FormControl
- type="text"
- id="createDBName"
- placeholder="e.g. userRoot"
- onChange={this.handleFieldChange}
- value={createDBName}
- />
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBn"
- controlId="createInitDBn"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="noInit"
- name="noInit"
- inline
- checked={createInitDB === "noInit"}
- onChange={this.handleFieldChange}
- >
- Do Not Initialize Database
- </Radio>
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBs"
- controlId="createInitDBs"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="createSuffix"
- name="createSuffix"
- inline
- checked={createInitDB === "createSuffix"}
- onChange={this.handleFieldChange}
- >
- Create Suffix Entry
- </Radio>
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBp"
- controlId="createInitDBp"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="createSample"
- name="createSample"
- inline
- checked={createInitDB === "createSample"}
- onChange={this.handleFieldChange}
- >
- Create Suffix Entry And Add Sample Entries
- </Radio>
- </Col>
- </FormGroup>
- </Form>
- {createSpinner}
- </Modal.Body>
- <Modal.Footer>
- <Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>
- Cancel
- </Button>
- <Button bsStyle="primary" onClick={this.createInstance}>
- Create Instance
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- );
- }
-}
-
-CreateInstanceModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- loadInstanceList: PropTypes.func
-};
-
-CreateInstanceModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- loadInstanceList: noop
-};
-
-export class SchemaReloadModal extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- reloadSchemaDir: "",
- loadingSchemaTask: false
- };
-
- this.reloadSchema = this.reloadSchema.bind(this);
- this.handleFieldChange = this.handleFieldChange.bind(this);
- }
-
- handleFieldChange(e) {
- this.setState({
- [e.target.id]: e.target.value
- });
- }
-
- reloadSchema(e) {
- const { addNotification, serverId, closeHandler } = this.props;
- const { reloadSchemaDir } = this.state;
-
- this.setState({
- loadingSchemaTask: true
- });
-
- let cmd = ["dsconf", "-j", serverId, "schema", "reload", "--wait"];
- if (reloadSchemaDir !== "") {
- cmd = [...cmd, "--schemadir", reloadSchemaDir];
- }
- log_cmd("reloadSchemaDir", "Reload schema files", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(data => {
- addNotification("success", "Successfully reloaded schema");
- this.setState({
- loadingSchemaTask: false
- });
- closeHandler();
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- addNotification("error", `Failed to reload schema files - ${errMsg.desc}`);
- closeHandler();
- });
- }
-
- render() {
- const { loadingSchemaTask, reloadSchemaDir } = this.state;
- const { showModal, closeHandler } = this.props;
-
- let spinner = "";
- if (loadingSchemaTask) {
- spinner = (
- <Row>
- <div className="ds-margin-top ds-modal-spinner">
- <Spinner loading inline size="md" />
- Reloading schema files...
- </div>
- </Row>
- );
- }
-
- return (
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title>Reload Schema Files</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Form horizontal autoComplete="off">
- <Row title="The name of the database link.">
- <Col sm={3}>
- <ControlLabel>Schema File Directory:</ControlLabel>
- </Col>
- <Col sm={9}>
- <FormControl
- type="text"
- id="reloadSchemaDir"
- value={reloadSchemaDir}
- onChange={this.handleFieldChange}
- />
- </Col>
- </Row>
- {spinner}
- </Form>
- </Modal.Body>
- <Modal.Footer>
- <Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>
- Cancel
- </Button>
- <Button bsStyle="primary" onClick={this.reloadSchema}>
- Reload Schema
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- );
- }
-}
-
-SchemaReloadModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- serverId: PropTypes.string
-};
-
-SchemaReloadModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- serverId: ""
-};
-
-class ManageBackupsModal extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- activeKey: 1,
- showConfirmBackupDelete: false,
- showConfirmBackup: false,
- showConfirmRestore: false,
- showConfirmRestoreReplace: false,
- showConfirmLDIFReplace: false,
- showRestoreSpinningModal: false,
- showDelBackupSpinningModal: false,
- showBackupModal: false,
- backupSpinning: false,
- backupName: "",
- deleteBackup: "",
- errObj: {}
- };
-
- this.handleNavSelect = this.handleNavSelect.bind(this);
- this.handleChange = this.handleChange.bind(this);
-
- // Backups
- this.doBackup = this.doBackup.bind(this);
- this.deleteBackup = this.deleteBackup.bind(this);
- this.restoreBackup = this.restoreBackup.bind(this);
- this.showConfirmRestore = this.showConfirmRestore.bind(this);
- this.closeConfirmRestore = this.closeConfirmRestore.bind(this);
- this.showConfirmBackup = this.showConfirmBackup.bind(this);
- this.closeConfirmBackup = this.closeConfirmBackup.bind(this);
- this.showConfirmBackupDelete = this.showConfirmBackupDelete.bind(this);
- this.closeConfirmBackupDelete = this.closeConfirmBackupDelete.bind(this);
- this.showBackupModal = this.showBackupModal.bind(this);
- this.closeBackupModal = this.closeBackupModal.bind(this);
- this.showRestoreSpinningModal = this.showRestoreSpinningModal.bind(this);
- this.closeRestoreSpinningModal = this.closeRestoreSpinningModal.bind(this);
- this.showDelBackupSpinningModal = this.showDelBackupSpinningModal.bind(this);
- this.closeDelBackupSpinningModal = this.closeDelBackupSpinningModal.bind(this);
- this.validateBackup = this.validateBackup.bind(this);
- this.closeConfirmRestoreReplace = this.closeConfirmRestoreReplace.bind(this);
- }
-
- closeExportModal() {
- this.setState({
- showExportModal: false
- });
- }
-
- showDelBackupSpinningModal() {
- this.setState({
- showDelBackupSpinningModal: true
- });
- }
-
- closeDelBackupSpinningModal() {
- this.setState({
- showDelBackupSpinningModal: false
- });
- }
-
- showRestoreSpinningModal() {
- this.setState({
- showRestoreSpinningModal: true
- });
- }
-
- closeRestoreSpinningModal() {
- this.setState({
- showRestoreSpinningModal: false
- });
- }
-
- showBackupModal() {
- this.setState({
- showBackupModal: true,
- backupSpinning: false,
- backupName: ""
- });
- }
-
- closeBackupModal() {
- this.setState({
- showBackupModal: false
- });
- }
-
- showConfirmBackup(item) {
- // call deleteLDIF
- this.setState({
- showConfirmBackup: true,
- backupName: item.name
- });
- }
-
- closeConfirmBackup() {
- // call importLDIF
- this.setState({
- showConfirmBackup: false
- });
- }
-
- showConfirmRestore(item) {
- this.setState({
- showConfirmRestore: true,
- backupName: item.name
- });
- }
-
- closeConfirmRestore() {
- // call importLDIF
- this.setState({
- showConfirmRestore: false
- });
- }
-
- showConfirmBackupDelete(item) {
- // calls deleteBackup
- this.setState({
- showConfirmBackupDelete: true,
- backupName: item.name
- });
- }
-
- closeConfirmBackupDelete() {
- // call importLDIF
- this.setState({
- showConfirmBackupDelete: false
- });
- }
-
- closeConfirmRestoreReplace() {
- this.setState({
- showConfirmRestoreReplace: false
- });
- }
-
- validateBackup() {
- for (let i = 0; i < this.props.backups.length; i++) {
- if (this.state.backupName == this.props.backups[i]["name"]) {
- this.setState({
- showConfirmRestoreReplace: true
- });
- return;
- }
- }
- this.doBackup();
- }
-
- doBackup() {
- this.setState({
- backupSpinning: true
- });
-
- let cmd = ["dsctl", "-j", this.props.serverId, "status"];
- cockpit
- .spawn(cmd, { superuser: true })
- .done(status_data => {
- let status_json = JSON.parse(status_data);
- if (status_json.running == true) {
- let cmd = [
- "dsconf",
- "-j",
- "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
- "backup",
- "create"
- ];
- if (this.state.backupName != "") {
- if (bad_file_name(this.state.backupName)) {
- this.props.addNotification(
- "warning",
- `Backup name should not be a path. All backups are stored in the server's backup directory`
- );
- return;
- }
- cmd.push(this.state.backupName);
- }
-
- log_cmd("doBackup", "Add backup task online", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- this.props.reload();
- this.closeBackupModal();
- this.props.addNotification("success", `Server has been backed up`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.props.reload();
- this.closeBackupModal();
- this.props.addNotification(
- "error",
- `Failure backing up server - ${errMsg.desc}`
- );
- });
- } else {
- const cmd = ["dsctl", "-j", this.props.serverId, "db2bak"];
- if (this.state.backupName != "") {
- if (bad_file_name(this.state.backupName)) {
- this.props.addNotification(
- "warning",
- `Backup name should not be a path. All backups are stored in the server's backup directory`
- );
- return;
- }
- cmd.push(this.state.backupName);
- }
- log_cmd("doBackup", "Doing backup of the server offline", cmd);
- cockpit
- .spawn(cmd, { superuser: true })
- .done(content => {
- this.props.reload();
- this.closeBackupModal();
- this.props.addNotification("success", `Server has been backed up`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.props.reload();
- this.closeBackupModal();
- this.props.addNotification(
- "error",
- `Failure backing up server - ${errMsg.desc}`
- );
- });
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("Failed to check the server status", errMsg.desc);
- });
- }
-
- restoreBackup() {
- this.showRestoreSpinningModal();
- let cmd = ["dsctl", "-j", this.props.serverId, "status"];
- cockpit
- .spawn(cmd, { superuser: true })
- .done(status_data => {
- let status_json = JSON.parse(status_data);
- if (status_json.running == true) {
- const cmd = [
- "dsconf",
- "-j",
- "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
- "backup",
- "restore",
- this.state.backupName
- ];
- log_cmd("restoreBackup", "Restoring server online", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- this.closeRestoreSpinningModal();
- this.props.addNotification("success", `Server has been restored`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.closeRestoreSpinningModal();
- this.props.addNotification(
- "error",
- `Failure restoring up server - ${errMsg.desc}`
- );
- });
- } else {
- const cmd = [
- "dsctl",
- "-j",
- this.props.serverId,
- "bak2db",
- this.state.backupName
- ];
- log_cmd("restoreBackup", "Restoring server offline", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- this.closeRestoreSpinningModal();
- this.props.addNotification("success", `Server has been restored`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.closeRestoreSpinningModal();
- this.props.addNotification(
- "error",
- `Failure restoring up server - ${errMsg.desc}`
- );
- });
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("Failed to check the server status", errMsg.desc);
- });
- }
-
- deleteBackup(e) {
- // Show confirmation
- this.showDelBackupSpinningModal();
-
- const cmd = [
- "dsctl",
- "-j",
- this.props.serverId,
- "backups",
- "--delete",
- this.state.backupName
- ];
- log_cmd("deleteBackup", "Deleting backup", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- this.props.reload();
- this.closeDelBackupSpinningModal();
- this.props.addNotification("success", `Backup was successfully deleted`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.props.reload();
- this.closeDelBackupSpinningModal();
- this.props.addNotification("error", `Failure deleting backup - ${errMsg.desc}`);
- });
- }
-
- handleNavSelect(key) {
- this.setState({ activeKey: key });
- }
-
- handleChange(e) {
- const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
- let valueErr = false;
- let errObj = this.state.errObj;
- if (value == "") {
- valueErr = true;
- }
- errObj[e.target.id] = valueErr;
- this.setState({
- [e.target.id]: value,
- errObj: errObj
- });
- }
-
- render() {
- const { showModal, closeHandler, backups, reload, loadingBackup } = this.props;
-
- let backupSpinner = "";
- if (loadingBackup) {
- backupSpinner = (
- <Row>
- <div className="ds-margin-top-lg ds-modal-spinner">
- <Spinner loading inline size="lg" />
- Creating instance...
- </div>
- </Row>
- );
- }
-
- return (
- <div>
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title>Manage Backups</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <div className="ds-margin-top-xlg">
- <BackupTable
- rows={backups}
- confirmRestore={this.showConfirmRestore}
- confirmDelete={this.showConfirmBackupDelete}
- />
- </div>
- {backupSpinner}
- </Modal.Body>
- <Modal.Footer>
- <Button
- bsStyle="primary"
- onClick={this.showBackupModal}
- className="ds-margin-top"
- >
- Create Backup
- </Button>
- <Button
- bsStyle="default"
- onClick={reload}
- className="ds-left-margin ds-margin-top"
- >
- Refresh Backups
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- <BackupModal
- showModal={this.state.showBackupModal}
- closeHandler={this.closeBackupModal}
- handleChange={this.handleChange}
- saveHandler={this.validateBackup}
- spinning={this.state.backupSpinning}
- error={this.state.errObj}
- />
- <RestoreModal
- showModal={this.state.showRestoreSpinningModal}
- closeHandler={this.closeRestoreSpinningModal}
- msg={this.state.backupName}
- />
- <DeleteBackupModal
- showModal={this.state.showDelBackupSpinningModal}
- closeHandler={this.closeDelBackupSpinningModal}
- msg={this.state.backupName}
- />
- <ConfirmPopup
- showModal={this.state.showConfirmRestore}
- closeHandler={this.closeConfirmRestore}
- actionFunc={this.restoreBackup}
- actionParam={this.state.backupName}
- msg="Are you sure you want to restore this backup?"
- msgContent={this.state.backupName}
- />
- <ConfirmPopup
- showModal={this.state.showConfirmBackupDelete}
- closeHandler={this.closeConfirmBackupDelete}
- actionFunc={this.deleteBackup}
- actionParam={this.state.backupName}
- msg="Are you sure you want to delete this backup?"
- msgContent={this.state.backupName}
- />
- <ConfirmPopup
- showModal={this.state.showConfirmRestoreReplace}
- closeHandler={this.closeConfirmRestoreReplace}
- actionFunc={this.doBackup}
- msg="Replace Existing Backup"
- msgContent="A backup already eixsts with the same name, do you want to replace it?"
- />
- </div>
- );
- }
-}
-
-ManageBackupsModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- serverId: PropTypes.string
-};
-
-ManageBackupsModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- serverId: ""
-};
-
export default DSInstance;
diff --git a/src/cockpit/389-console/src/ds.jsx b/src/cockpit/389-console/src/dsModals.jsx
similarity index 53%
copy from src/cockpit/389-console/src/ds.jsx
copy to src/cockpit/389-console/src/dsModals.jsx
index 53aa5cb..8f59e08 100644
--- a/src/cockpit/389-console/src/ds.jsx
+++ b/src/cockpit/389-console/src/dsModals.jsx
@@ -1,29 +1,15 @@
import cockpit from "cockpit";
import React from "react";
import PropTypes from "prop-types";
-import { Plugins } from "./plugins.jsx";
-import { Database } from "./database.jsx";
-import { Monitor } from "./monitor.jsx";
-import { Schema } from "./schema.jsx";
-import { Replication } from "./replication.jsx";
-import { Server } from "./server.jsx";
-import { ConfirmPopup, DoubleConfirmModal, NotificationController } from "./lib/notifications.jsx";
+import { ConfirmPopup } from "./lib/notifications.jsx";
import { BackupTable } from "./lib/database/databaseTables.jsx";
import { BackupModal, RestoreModal, DeleteBackupModal } from "./lib/database/backups.jsx";
-import { log_cmd, bad_file_name } from "./lib/tools.jsx";
+import { log_cmd, bad_file_name, valid_dn, valid_port } from "./lib/tools.jsx";
+
import {
- Nav,
- NavItem,
- DropdownButton,
- MenuItem,
- TabContainer,
- TabContent,
- TabPane,
- ProgressBar,
FormControl,
FormGroup,
ControlLabel,
- Radio,
Form,
noop,
Checkbox,
@@ -35,725 +21,272 @@ import {
Button
} from "patternfly-react";
import "./css/ds.css";
-import "./css/branding.css";
-
-const staticStates = {
- noPackage: (
- <h3>
- There is no <b>389-ds-base</b> package installed on this system. Sorry there is nothing
- to manage...
- </h3>
- ),
- noInsts: <h3>There are no Directory Server instances to manage</h3>,
- notRunning: (
- <h3>
- This server instance is not running, either start it from the <b>Actions</b> dropdown
- menu, or choose a different instance
- </h3>
- ),
- notConnecting: (
- <h3>
- This server instance is running, but we can not connect to it. Check LDAPI is properly
- configured on this instance.
- </h3>
- )
-};
-
-export class DSInstance extends React.Component {
- componentDidMount() {
- this.loadInstanceList();
- this.updateProgress(25);
- }
+export class CreateInstanceModal extends React.Component {
constructor(props) {
super(props);
this.state = {
- pageLoadingState: { state: "loading", jsx: "" },
- serverId: "",
- instList: [],
- backupRows: [],
- notifications: [],
- activeKey: 1,
- wasActiveList: [1],
- progressValue: 0,
- loadingOperate: false,
-
- showDeleteConfirm: false,
- modalSpinning: false,
- modalChecked: false,
-
- showSchemaReloadModal: false,
- showManageBackupsModal: false,
- showCreateInstanceModal: false
+ createServerId: "",
+ createPort: 389,
+ createSecurePort: 636,
+ createDM: "cn=Directory Manager",
+ createDMPassword: "",
+ createDMPasswordConfirm: "",
+ createDBCheckbox: false,
+ createDBSuffix: "",
+ createDBName: "",
+ createTLSCert: true,
+ createInitDB: "noInit",
+ loadingCreate: false,
+ createOK: false,
+ modalMsg: "",
+ errObj: {},
};
- this.handleServerIdChange = this.handleServerIdChange.bind(this);
this.handleFieldChange = this.handleFieldChange.bind(this);
- this.addNotification = this.addNotification.bind(this);
- this.removeNotification = this.removeNotification.bind(this);
- this.handleNavSelect = this.handleNavSelect.bind(this);
- this.loadInstanceList = this.loadInstanceList.bind(this);
- this.loadBackups = this.loadBackups.bind(this);
- this.setServerId = this.setServerId.bind(this);
- this.updateProgress = this.updateProgress.bind(this);
- this.openCreateInstanceModal = this.openCreateInstanceModal.bind(this);
- this.closeCreateInstanceModal = this.closeCreateInstanceModal.bind(this);
- this.operateInstance = this.operateInstance.bind(this);
- this.openManageBackupsModal = this.openManageBackupsModal.bind(this);
- this.closeManageBackupsModal = this.closeManageBackupsModal.bind(this);
- this.openSchemaReloadModal = this.openSchemaReloadModal.bind(this);
- this.closeSchemaReloadModal = this.closeSchemaReloadModal.bind(this);
- this.removeInstance = this.removeInstance.bind(this);
- this.showDeleteConfirm = this.showDeleteConfirm.bind(this);
- this.closeDeleteConfirm = this.closeDeleteConfirm.bind(this);
- }
-
- updateProgress(value) {
- this.setState(
- prevState => ({
- progressValue: prevState.progressValue + value
- }),
- () => {
- if (this.state.progressValue > 100) {
- this.setState(prevState => ({
- pageLoadingState: {
- ...prevState.pageLoadingState,
- state: "success"
- }
- }));
- }
- }
- );
- }
-
- setServerId(serverId, action) {
- // First we need to check if the instance is alive and well
- let cmd = ["dsctl", "-j", serverId, "status"];
- log_cmd("setServerId", "Test if instance is running ", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(status_data => {
- let status_json = JSON.parse(status_data);
- if (status_json.running) {
- this.updateProgress(25);
- let cmd = [
- "dsconf",
- "-j",
- "ldapi://%2fvar%2frun%2fslapd-" + serverId + ".socket",
- "backend",
- "suffix",
- "list",
- "--suffix"
- ];
- log_cmd("setServerId", "Test if instance is alive ", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(_ => {
- this.updateProgress(25);
- this.setState(
- {
- serverId: serverId
- },
- () => {
- this.loadBackups();
- }
- );
- if (action === "restart") {
- this.setState(
- {
- serverId: ""
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("setServerId failed: ", errMsg.desc);
- this.setState(
- {
- pageLoadingState: {
- state: "notConnecting",
- jsx: staticStates["notConnecting"]
- }
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- });
- } else {
- this.setState(
- {
- pageLoadingState: {
- state: "notRunning",
- jsx: staticStates["notRunning"]
- }
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("setServerId failed: ", errMsg.desc);
- this.setState(
- {
- pageLoadingState: {
- state: "notConnecting",
- jsx: staticStates["notConnecting"]
- }
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- });
- }
-
- loadInstanceList(serverId, action) {
- if (serverId === undefined) {
- this.setState(prevState => ({
- pageLoadingState: {
- ...prevState.pageLoadingState,
- state: "loading"
- }
- }));
- }
- let cmd = ["dsctl", "-l", "-j"];
- log_cmd("loadInstanceList", "Load the instance list select", cmd);
- cockpit
- .spawn(cmd, { superuser: true })
- .done(data => {
- this.updateProgress(25);
- let myObject = JSON.parse(data);
- this.setState({
- instList: myObject.insts,
- loadingOperate: false
- });
- // Set default value for the inst select
- if (serverId !== undefined && serverId !== "") {
- this.setState({
- wasActiveList: [this.state.activeKey]
- });
- this.setServerId(serverId, action);
- } else {
- if (myObject.insts.length > 0) {
- this.setState({
- wasActiveList: [this.state.activeKey]
- });
- this.setServerId(myObject.insts[0].replace("slapd-", ""), action);
- } else {
- this.setState({
- serverId: "",
- pageLoadingState: {
- state: "noInsts",
- jsx: staticStates["noInsts"]
- }
- });
- }
- }
- })
- .fail(_ => {
- this.setState({
- instList: [],
- serverId: "",
- loadingOperate: false,
- pageLoadingState: {
- state: "noInsts",
- jsx: staticStates["noInsts"]
- }
- });
- });
- }
-
- loadBackups() {
- const cmd = ["dsctl", "-j", this.state.serverId, "backups"];
- log_cmd("loadBackupsDSInstance", "Load Backups", cmd);
- cockpit.spawn(cmd, { superuser: true, err: "message" }).done(content => {
- this.updateProgress(25);
- const config = JSON.parse(content);
- let rows = [];
- for (let row of config.items) {
- rows.push({ name: row[0], date: [row[1]], size: [row[2]] });
- }
- this.setState({
- backupRows: rows,
- });
- });
+ this.createInstance = this.createInstance.bind(this);
+ this.validInstName = this.validInstName.bind(this);
+ this.validRootDN = this.validRootDN.bind(this);
+ this.resetModal = this.resetModal.bind(this);
}
- addNotification(type, message, timerdelay, persistent) {
- this.setState(prevState => ({
- notifications: [
- ...prevState.notifications,
- {
- key: prevState.notifications.length + 1,
- type: type,
- persistent: persistent,
- timerdelay: timerdelay,
- message: message
- }
- ]
- }));
+ componentDidMount() {
+ this.resetModal();
}
- removeNotification(notificationToRemove) {
+ resetModal() {
this.setState({
- notifications: this.state.notifications.filter(
- notification => notificationToRemove.key !== notification.key
- )
+ createServerId: "",
+ createPort: 389,
+ createSecurePort: 636,
+ createDM: "cn=Directory Manager",
+ createDMPassword: "",
+ createDMPasswordConfirm: "",
+ createDBCheckbox: false,
+ createDBSuffix: "",
+ createDBName: "",
+ createTLSCert: true,
+ createInitDB: "noInit",
+ loadingCreate: false,
+ createOK: false,
+ modalMsg: "",
+ errObj: {
+ createServerId: true,
+ createDMPassword: true,
+ createDMPasswordConfirm: true,
+ createDBSuffix: false,
+ createDBName: false,
+ },
});
}
- handleNavSelect(key) {
- this.setState({
- activeKey: key
- });
- const { wasActiveList } = this.state;
- if (!wasActiveList.includes(key)) {
- let newList = wasActiveList.concat(key);
- this.setState({
- wasActiveList: newList
- });
- }
+ validInstName(name) {
+ return /^[\w@_:-]*$/.test(name);
}
- handleServerIdChange(e) {
- this.setState({
- pageLoadingState: { state: "loading", jsx: "" },
- progressValue: 25,
- serverId: e.target.value
- });
- this.loadInstanceList(e.target.value);
+ validRootDN(dn) {
+ // Validate a DN for Directory Manager. We have to be stricter than
+ // valid_dn() and only allow stand ascii characters for the value
+ if (dn.endsWith(",")) {
+ return false;
+ }
+ // Check that the attr is only letters [A-Za-z]+ and the value is standard
+ // ascii [ -~]+ that it does not start with a space \\S
+ let dn_regex = new RegExp("^([A-Za-z]+=\\S[ -~]+$)");
+ let result = dn_regex.test(dn);
+ return result;
}
handleFieldChange(e) {
let value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
- if (e.target.type === "number") {
- if (e.target.value) {
- value = parseInt(e.target.value);
- } else {
- value = 1;
+ let target_id = e.target.id;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ let all_good = true;
+ let modal_msg = "";
+
+ errObj[target_id] = valueErr;
+ if (target_id == 'createServerId') {
+ if (value == "") {
+ all_good = false;
+ errObj['createServerId'] = true;
+ } else if (value > 80) {
+ all_good = false;
+ errObj['createServerId'] = true;
+ modal_msg = "Instance name must be less than 80 characters";
+ } else if (!this.validInstName(value)) {
+ all_good = false;
+ errObj['createServerId'] = true;
+ modal_msg = "Instance name can only contain letters, numbers, and these 4 characters: - @ : _";
}
+ } else if (this.state.createServerId == "") {
+ all_good = false;
+ errObj['createServerId'] = true;
+ } else if (!this.validInstName(this.state.createServerId)) {
+ all_good = false;
+ errObj['createServerId'] = true;
+ modal_msg = "Not all required fields have values";
}
- this.setState({
- [e.target.id]: value
- });
- }
-
- removeInstance() {
- this.operateInstance();
- this.closeDeleteConfirm();
- }
-
- operateInstance(e) {
- this.setState({
- loadingOperate: true
- });
-
- let action = "remove";
- if (e !== undefined) {
- action = e.target.id.split("-")[0];
+ if (target_id == 'createPort') {
+ if (value == "") {
+ all_good = false;
+ errObj['createPort'] = true;
+ } else if (!valid_port(value)) {
+ all_good = false;
+ errObj['createPort'] = true;
+ modal_msg = "Invalid Port number. The port must be between 1 and 65534";
+ }
+ } else if (this.state.createPort == "") {
+ all_good = false;
+ errObj['createPort'] = true;
+ } else if (!valid_port(this.state.createPort)) {
+ all_good = false;
+ errObj['createPort'] = true;
+ modal_msg = "Invalid Port number. The port must be between 1 and 65534";
}
-
- let cmd = ["dsctl", "-j", this.state.serverId, action];
- if (action === "remove") {
- cmd = [...cmd, "--do-it"];
+ if (target_id == 'createSecurePort') {
+ if (value == "") {
+ all_good = false;
+ errObj['createSecurePort'] = true;
+ } else if (!valid_port(value)) {
+ all_good = false;
+ errObj['createSecurePort'] = true;
+ modal_msg = "Invalid Secure Port number. Port must be between 1 and 65534";
+ }
+ } else if (this.state.createSecurePort == "") {
+ all_good = false;
+ errObj['createSecurePort'] = true;
}
- log_cmd("operateInstance", `Do ${action} the instance`, cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(_ => {
- if (action === "remove") {
- this.loadInstanceList();
- } else {
- this.loadInstanceList(this.state.serverId, action);
- }
- if (action === "remove") {
- this.addNotification("success", "Instance was successfully removed");
- } else {
- this.addNotification("success", `Instance was successfully ${action}ed`);
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.addNotification(
- "error",
- `Error during instance ${action} operation - ${errMsg.desc}`
- );
- this.loadInstanceList(this.state.serverId, action);
- });
- }
-
- openCreateInstanceModal() {
- this.setState({
- showCreateInstanceModal: true
- });
- }
-
- closeCreateInstanceModal() {
- this.setState({
- showCreateInstanceModal: false
- });
- }
-
- openManageBackupsModal() {
- this.setState({
- showManageBackupsModal: true
- });
- }
-
- closeManageBackupsModal() {
- this.setState({
- showManageBackupsModal: false
- });
- }
-
- openSchemaReloadModal() {
- this.setState({
- showSchemaReloadModal: true
- });
- }
-
- closeSchemaReloadModal() {
- this.setState({
- showSchemaReloadModal: false
- });
- }
-
- showDeleteConfirm() {
- this.setState({
- showDeleteConfirm: true,
- modalSpinning: false,
- modalChecked: false
- });
- }
-
- closeDeleteConfirm() {
- this.setState({
- showDeleteConfirm: false,
- modalSpinning: false,
- modalChecked: false
- });
- }
-
- render() {
- const {
- instList,
- serverId,
- progressValue,
- notifications,
- pageLoadingState,
- loadingOperate
- } = this.state;
-
- let mainContent = "";
- if (pageLoadingState.state === "loading") {
- mainContent = (
- <div id="loading-instances" className="all-pages ds-center">
- <div id="loading-page" className="ds-center ds-loading">
- <h4 id="loading-msg">Loading Directory Server Configuration...</h4>
- <p className="ds-margin-top-lg">
- <span className="spinner spinner-lg spinner-inline" />
- </p>
- <div className="progress ds-margin-top-lg">
- <ProgressBar active now={progressValue} label={`${progressValue}%`} />
- </div>
- </div>
- </div>
- );
- } else if (pageLoadingState.state === "noInsts") {
- mainContent = (
- <div id="noInsts" className="all-pages ds-center">
- {pageLoadingState.jsx}
- <p>
- <Button
- id="no-inst-create-btn"
- bsStyle="primary"
- onClick={this.openCreateInstanceModal}
- >
- Create New Instance
- </Button>
- </p>
- </div>
- );
- } else {
- mainContent = (
- <div id={pageLoadingState.state} className="all-pages ds-center">
- {pageLoadingState.jsx}
- </div>
- );
+ if (target_id == 'createDM') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDM'] = true;
+ }
+ if (!this.validRootDN(value)) {
+ all_good = false;
+ errObj['createDM'] = true;
+ modal_msg = "Invalid DN for Directory Manager";
+ }
+ } else if (this.state.createDM == "") {
+ all_good = false;
+ errObj['createDM'] = true;
+ } else if (!this.validRootDN(this.state.createDM)) {
+ all_good = false;
+ errObj['createDM'] = true;
+ modal_msg = "Invalid DN for Directory Manager";
}
-
- let operateSpinner = "";
- if (loadingOperate) {
- operateSpinner = <Spinner className="ds-operate-spinner" loading inline size="md" />;
+ if (e.target.id == 'createDMPassword') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDMPassword'] = true;
+ } else if (value != this.state.createDMPasswordConfirm) {
+ all_good = false;
+ errObj['createDMPassword'] = true;
+ errObj['createDMPasswordConfirm'] = true;
+ modal_msg = "Passwords Do Not Match";
+ } else if (value.length < 8) {
+ all_good = false;
+ errObj['createDMPassword'] = true;
+ modal_msg = "Directory Manager password must be at least 8 characters long";
+ } else {
+ errObj['createDMPassword'] = false;
+ errObj['createDMPasswordConfirm'] = false;
+ }
+ } else if (this.state.createDMPassword == "") {
+ all_good = false;
+ errObj['createDMPasswordConfirm'] = true;
+ }
+ if (e.target.id == 'createDMPasswordConfirm') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDMPasswordConfirm'] = true;
+ } else if (value != this.state.createDMPassword) {
+ all_good = false;
+ errObj['createDMPassword'] = true;
+ errObj['createDMPasswordConfirm'] = true;
+ modal_msg = "Passwords Do Not Match";
+ } else if (value.length < 8) {
+ all_good = false;
+ errObj['createDMPasswordConfirm'] = true;
+ modal_msg = "Directory Manager password must be at least 8 characters long";
+ } else {
+ errObj['createDMPassword'] = false;
+ errObj['createDMPasswordConfirm'] = false;
+ }
+ } else if (this.state.createDMPasswordConfirm == "") {
+ all_good = false;
+ errObj['createDMPasswordConfirm'] = true;
}
- return (
- <div>
- <NotificationController
- notifications={notifications}
- removeNotificationAction={this.removeNotification}
- />
- {pageLoadingState.state !== "loading" &&
- pageLoadingState.state !== "noInsts" &&
- pageLoadingState.state !== "noPackage" ? (
- <div className="ds-logo" hidden={pageLoadingState.state === "loading"}>
- <h2 className="ds-logo-style" id="main-banner">
- <div className="dropdown ds-server-action">
- <select
- className="btn btn-default dropdown"
- title="Directory Server Instance List"
- id="serverId"
- value={serverId}
- onChange={this.handleServerIdChange}
- >
- {Object.entries(instList).map(([_, inst]) => (
- <option key={inst} value={inst.replace("slapd-", "")}>
- {inst}
- </option>
- ))}
- </select>
- </div>
- {operateSpinner}
- <div className="dropdown ds-float-right">
- <DropdownButton
- pullRight
- id="ds-action"
- className="ds-action-button"
- bsStyle="primary"
- title="Actions"
- >
- <MenuItem
- id="start-ds"
- eventKey="1"
- onClick={this.operateInstance}
- >
- Start Instance
- </MenuItem>
- <MenuItem
- id="stop-ds"
- eventKey="2"
- onClick={this.operateInstance}
- >
- Stop Instance
- </MenuItem>
- <MenuItem
- id="restart-ds"
- eventKey="3"
- onClick={this.operateInstance}
- >
- Restart Instance
- </MenuItem>
- <MenuItem
- id="manage-backup-ds"
- eventKey="4"
- onClick={this.openManageBackupsModal}
- >
- Manage Backups
- </MenuItem>
- <MenuItem
- id="reload-schema-ds"
- eventKey="5"
- onClick={this.openSchemaReloadModal}
- >
- Reload Schema Files
- </MenuItem>
- <MenuItem
- id="remove-ds"
- eventKey="6"
- onClick={this.showDeleteConfirm}
- >
- Remove Instance
- </MenuItem>
- <MenuItem
- id="create-ds"
- eventKey="7"
- onClick={this.openCreateInstanceModal}
- >
- Create Instance
- </MenuItem>
- </DropdownButton>
- </div>
- </h2>
- </div>
- ) : (
- <div />
- )}
- {serverId !== "" &&
- (pageLoadingState.state === "success" || pageLoadingState.state === "loading") ? (
- <div>
- <div hidden={pageLoadingState.state === "loading"}>
- <TabContainer
- id="basic-tabs-pf"
- onSelect={this.handleNavSelect}
- activeKey={this.state.activeKey}
- >
- <div>
- <Nav className="nav nav-tabs nav-tabs-pf collapse navbar-collapse navbar-collapse-5 ds-nav navbar navbar-default">
- <NavItem className="ds-tab-main" eventKey={1}>
- Server Settings
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={2}>
- Database
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={3}>
- Replication
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={4}>
- Schema
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={5}>
- Plugins
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={6}>
- Monitoring
- </NavItem>
- </Nav>
- <TabContent>
- <TabPane eventKey={1}>
- <Server
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={2}>
- <Database
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={3}>
- <Replication
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={4}>
- <Schema
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={5}>
- <Plugins
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={6}>
- <Monitor
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- </TabContent>
- </div>
- </TabContainer>
- </div>
- <div hidden={pageLoadingState.state !== "loading"}>{mainContent}</div>
- </div>
- ) : (
- <div>{mainContent}</div>
- )}
- <CreateInstanceModal
- showModal={this.state.showCreateInstanceModal}
- closeHandler={this.closeCreateInstanceModal}
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- loadInstanceList={this.loadInstanceList}
- />
- <SchemaReloadModal
- showModal={this.state.showSchemaReloadModal}
- closeHandler={this.closeSchemaReloadModal}
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- />
- <ManageBackupsModal
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- showModal={this.state.showManageBackupsModal}
- closeHandler={this.closeManageBackupsModal}
- handleChange={this.handleFieldChange}
- backups={this.state.backupRows}
- reload={this.loadBackups}
- />
- <DoubleConfirmModal
- showModal={this.state.showDeleteConfirm}
- closeHandler={this.closeDeleteConfirm}
- handleChange={this.handleFieldChange}
- actionHandler={this.removeInstance}
- spinning={this.state.modalSpinning}
- item={this.state.serverId}
- checked={this.state.modalChecked}
- mTitle="Remove Instance"
- mMsg="Are you really sure you want to delete this instance?"
- mSpinningMsg="Removing Instance..."
- mBtnName="Remove Instance"
- />
- </div>
- );
- }
-}
-
-class CreateInstanceModal extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- createServerId: "",
- createPort: 389,
- createSecurePort: 636,
- createDM: "cn=Directory Manager",
- createDMPassword: "",
- createDMPasswordConfirm: "",
- createDBSuffix: "",
- createDBName: "",
- createTLSCert: true,
- createInitDB: "noInit",
- loadingCreate: false
- };
-
- this.handleFieldChange = this.handleFieldChange.bind(this);
- this.createInstance = this.createInstance.bind(this);
- }
-
- handleFieldChange(e) {
- let value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
- if (e.target.type === "number") {
- if (e.target.value) {
- value = parseInt(e.target.value);
+ // Optional settings
+ if (target_id == 'createDBCheckbox') {
+ if (!value) {
+ errObj['createDBSuffix'] = false;
+ errObj['createDBName'] = false;
} else {
- value = 1;
+ if (this.state.createDBSuffix == "") {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ } else if (!valid_dn(this.state.createDBSuffix)) {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ modal_msg = "Invalid DN for suffix";
+ }
+ if (this.state.createDBName == "") {
+ all_good = false;
+ errObj['createDBName'] = true;
+ } else if (!valid_dn(this.state.createDBName)) {
+ all_good = false;
+ errObj['createDBName'] = true;
+ modal_msg = "Invalid name for database";
+ }
+ }
+ } else if (this.state.createDBCheckbox) {
+ if (target_id == 'createDBSuffix') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ } else if (!valid_dn(value)) {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ modal_msg = "Invalid DN for suffix";
+ }
+ } else if (this.state.createDBSuffix == "") {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ } else if (!valid_dn(this.state.createDBSuffix)) {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ modal_msg = "Invalid DN for suffix";
}
+ if (target_id == 'createDBName') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDBName'] = true;
+ } else if (/\s/.test(value)) {
+ // name has some kind of white space character
+ all_good = false;
+ errObj['createDBName'] = true;
+ modal_msg = "Database name can not contain any spaces";
+ }
+ } else if (this.state.createDBName == "") {
+ all_good = false;
+ errObj['createDBName'] = true;
+ } else if (/\s/.test(this.state.createDBName)) {
+ all_good = false;
+ errObj['createDBName'] = true;
+ modal_msg = "Invalid database name";
+ }
+ } else {
+ errObj['createDBSuffix'] = false;
+ errObj['createDBName'] = false;
}
+
this.setState({
- [e.target.id]: value
+ [target_id]: value,
+ errObj: errObj,
+ createOK: all_good,
+ modalMsg: modal_msg,
});
}
@@ -764,11 +297,11 @@ class CreateInstanceModal extends React.Component {
createSecurePort,
createDM,
createDMPassword,
- createDMPasswordConfirm,
createDBSuffix,
createDBName,
createTLSCert,
- createInitDB
+ createInitDB,
+ createDBCheckbox
} = this.state;
const { closeHandler, addNotification, loadInstanceList } = this.props;
@@ -788,56 +321,12 @@ class CreateInstanceModal extends React.Component {
// Server ID
let newServerId = createServerId;
- if (newServerId === "") {
- addNotification("warning", "Instance Name is required.");
- return;
- }
newServerId = newServerId.replace(/^slapd-/i, ""); // strip "slapd-"
- if (newServerId === "admin") {
- addNotification("warning", "Instance Name 'admin' is reserved, please choose a different name");
- return;
- }
- if (newServerId.length > 80) {
- addNotification(
- "warning",
- "Instance name is too long, it must not exceed 80 characters"
- );
- return;
- }
- if (newServerId.match(/^[#%:A-Za-z0-9_-]+$/g)) {
- setup_inf = setup_inf.replace("INST_NAME", newServerId);
- } else {
- addNotification(
- "warning",
- "Instance name can only contain letters, numbers, and: # % : - _"
- );
- return;
- }
-
- // Port
- if (createPort < 1 || createPort > 65535) {
- addNotification("warning", "Port must be a number between 1 and 65534!");
- return;
- } else {
- setup_inf = setup_inf.replace("PORT", createPort);
- }
-
- // Secure Port
- if (createSecurePort < 1 || createSecurePort > 65535) {
- addNotification("warning", "Secure Port must be a number between 1 and 65534!");
- return;
- } else {
- setup_inf = setup_inf.replace("SECURE_PORT", createSecurePort);
- }
-
- // Root DN
- if (createDM === "") {
- addNotification("warning", "You must provide a Directory Manager DN");
- return;
- } else {
- setup_inf = setup_inf.replace("ROOTDN", createDM);
- }
-
+ setup_inf = setup_inf.replace("INST_NAME", newServerId);
+ setup_inf = setup_inf.replace("PORT", createPort);
+ setup_inf = setup_inf.replace("SECURE_PORT", createSecurePort);
+ setup_inf = setup_inf.replace("ROOTDN", createDM);
+ setup_inf = setup_inf.replace("ROOTPW", createDMPassword);
// Setup Self-Signed Certs
if (createTLSCert) {
setup_inf = setup_inf.replace("SELF_SIGN", "True");
@@ -845,59 +334,13 @@ class CreateInstanceModal extends React.Component {
setup_inf = setup_inf.replace("SELF_SIGN", "False");
}
- // Root DN password
- if (createDMPassword != createDMPasswordConfirm) {
- addNotification("warning", "Directory Manager passwords do not match!");
- return;
- } else if (createDMPassword == "") {
- addNotification("warning", "Directory Manager password can not be empty!");
- return;
- } else if (createDMPassword.length < 8) {
- addNotification(
- "warning",
- "Directory Manager password must have at least 8 characters"
- );
- return;
- } else {
- setup_inf = setup_inf.replace("ROOTPW", createDMPassword);
- }
-
- // Backend/Suffix
- if (
- (createDBName != "" && createDBSuffix == "") ||
- (createDBName == "" && createDBSuffix != "")
- ) {
- if (createDBName == "") {
- addNotification(
- "warning",
- "If you specify a backend suffix, you must also specify a backend name"
- );
- return;
- } else {
- addNotification(
- "warning",
- "If you specify a backend name, you must also specify a backend suffix"
- );
- return;
- }
- }
- if (createDBName != "") {
- // We definitely have a backend name and suffix, next validate the suffix is a DN
- let dn_regex = new RegExp("^([A-Za-z]+=.*)");
- if (dn_regex.test(createDBSuffix)) {
- // It's valid, add it
- setup_inf += "\n[backend-" + createDBName + "]\nsuffix = " + createDBSuffix + "\n";
- } else {
- // Not a valid DN
- addNotification("warning", "Invalid DN for Backend Suffix");
- return;
- }
-
+ if (createDBCheckbox) {
+ setup_inf += "\n[backend-" + createDBName + "]\nsuffix = " + createDBSuffix + "\n";
if (createInitDB === "createSample") {
- setup_inf += "\nsample_entries = yes\n";
+ setup_inf += "sample_entries = yes\n";
}
if (createInitDB === "createSuffix") {
- setup_inf += "\ncreate_suffix_entry = yes\n";
+ setup_inf += "create_suffix_entry = yes\n";
}
}
@@ -915,7 +358,6 @@ class CreateInstanceModal extends React.Component {
loadingCreate: true
});
cockpit
-
.spawn(["hostnamectl", "status", "--static"], { superuser: true, err: "message" })
.fail(err => {
let errMsg = JSON.parse(err);
@@ -925,89 +367,91 @@ class CreateInstanceModal extends React.Component {
addNotification("error", `Failed to get hostname!", ${errMsg.desc}`);
})
.done(data => {
- /*
- * We have FQDN, so set the hostname in inf file, and create the setup file
- */
+ /*
+ * We have FQDN, so set the hostname in inf file, and create the setup file
+ */
setup_inf = setup_inf.replace("FQDN", data);
let setup_file = "/tmp/389-setup-" + new Date().getTime() + ".inf";
let rm_cmd = ["rm", setup_file];
let create_file_cmd = ["touch", setup_file];
+ log_cmd("createInstance", "Setting FQDN...", create_file_cmd);
cockpit
.spawn(create_file_cmd, { superuser: true, err: "message" })
.fail(err => {
- let errMsg = JSON.parse(err);
this.setState({
loadingCreate: false
});
addNotification(
"error",
- `Failed to create installation file!" ${errMsg.desc}`
+ `Failed to create installation file!" ${err.message}`
);
})
.done(_ => {
/*
- * We have our new setup file, now set permissions on that setup file before we add sensitive data
- */
+ * We have our new setup file, now set permissions on that setup file before we add sensitive data
+ */
let chmod_cmd = ["chmod", "600", setup_file];
+ log_cmd("createInstance", "Setting initial INF file permissions...", chmod_cmd);
cockpit
.spawn(chmod_cmd, { superuser: true, err: "message" })
.fail(err => {
- let errMsg = JSON.parse(err);
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
+ cockpit.spawn(rm_cmd, { superuser: true, err: "message" }); // Remove Inf file with clear text password
this.setState({
loadingCreate: false
});
addNotification(
"error",
- `Failed to set permission on setup file ${setup_file}: ${
- errMsg.desc
- }`
+ `Failed to set permissions on setup file ${setup_file}: ${err.message}`
);
})
.done(_ => {
/*
- * Success we have our setup file and it has the correct permissions.
- * Now populate the setup file...
- */
+ * Success we have our setup file and it has the correct permissions.
+ * Now populate the setup file...
+ */
let cmd = [
"/bin/sh",
"-c",
'/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file
];
+ // Do not log inf file as it contains the DM password
+ log_cmd("createInstance", "Apply changes to INF file...", "");
cockpit
.spawn(cmd, { superuser: true, err: "message" })
.fail(err => {
- let errMsg = JSON.parse(err);
this.setState({
loadingCreate: false
});
addNotification(
"error",
- `Failed to populate installation file! ${errMsg.desc}`
+ `Failed to populate installation file! ${err.message}`
);
})
.done(_ => {
/*
- * Next, create the instance...
- */
- let cmd = ["dscreate", "from-file", setup_file];
+ * Next, create the instance...
+ */
+ let cmd = ["dscreate", "-j", "from-file", setup_file];
+ log_cmd("createInstance", "Creating instance...", cmd);
cockpit
.spawn(cmd, {
superuser: true,
err: "message"
})
- .fail(_ => {
+ .fail(err => {
+ let errMsg = JSON.parse(err);
cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
this.setState({
loadingCreate: false
});
addNotification(
"error",
- "Failed to create instance!"
+ `${errMsg.desc}`
);
})
.done(_ => {
// Success!!! Now cleanup everything up...
+ log_cmd("createInstance", "Instance creation compelete, clean everything up...", rm_cmd);
cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
this.setState({
loadingCreate: false
@@ -1019,6 +463,7 @@ class CreateInstanceModal extends React.Component {
`Successfully created instance: slapd-${createServerId}`
);
closeHandler();
+ this.resetModal();
});
});
});
@@ -1037,12 +482,17 @@ class CreateInstanceModal extends React.Component {
createDM,
createDMPassword,
createDMPasswordConfirm,
+ createDBCheckbox,
createDBSuffix,
createDBName,
createTLSCert,
- createInitDB
+ createInitDB,
+ createOK,
+ modalMsg,
+ errObj,
} = this.state;
-
+ let errMsgClass = "";
+ let errMsg = "";
let createSpinner = "";
if (loadingCreate) {
createSpinner = (
@@ -1055,6 +505,18 @@ class CreateInstanceModal extends React.Component {
);
}
+ if (modalMsg == "") {
+ // No errors, but to keep the modal nice and stable during input
+ // field validation we need "invisible" text to keep the modal form
+ // from jumping up and down.
+ errMsgClass = "ds-clear-text";
+ errMsg = "no errors";
+ } else {
+ // We have error text to report
+ errMsgClass = "ds-modal-error";
+ errMsg = modalMsg;
+ }
+
return (
<Modal show={showModal} onHide={closeHandler}>
<div className="ds-no-horizontal-scrollbar">
@@ -1071,11 +533,16 @@ class CreateInstanceModal extends React.Component {
</Modal.Header>
<Modal.Body>
<Form horizontal>
- <FormGroup controlId="createServerId">
+ <Row>
+ <Col className="ds-center" sm={12}>
+ <p className={errMsgClass}>{errMsg}</p>
+ </Col>
+ </Row>
+ <FormGroup controlId="createServerId" className="ds-margin-top-lg">
<Col
componentClass={ControlLabel}
sm={5}
- title="The instance name, this is what gets appended to 'slapi-'. The instance name can only contain letters, numbers, and: # % : - _"
+ title="The instance name, this is what gets appended to 'slapi-'. The instance name can only contain letters, numbers, and: # @ : - _"
>
Instance Name
</Col>
@@ -1086,6 +553,7 @@ class CreateInstanceModal extends React.Component {
placeholder="Your_Instance_Name"
value={createServerId}
onChange={this.handleFieldChange}
+ className={errObj.createServerId ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1099,11 +567,13 @@ class CreateInstanceModal extends React.Component {
</Col>
<Col sm={7}>
<FormControl
+ id="createPort"
type="number"
min="0"
max="65535"
value={createPort}
onChange={this.handleFieldChange}
+ className={errObj.createPort ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1117,11 +587,13 @@ class CreateInstanceModal extends React.Component {
</Col>
<Col sm={7}>
<FormControl
+ id="createSecurePort"
type="number"
min="0"
max="65535"
value={createSecurePort}
onChange={this.handleFieldChange}
+ className={errObj.createSecurePort ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1155,6 +627,7 @@ class CreateInstanceModal extends React.Component {
id="createDM"
onChange={this.handleFieldChange}
value={createDM}
+ className={errObj.createDM ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1173,6 +646,7 @@ class CreateInstanceModal extends React.Component {
placeholder="Enter password"
onChange={this.handleFieldChange}
value={createDMPassword}
+ className={errObj.createDMPassword ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1187,11 +661,22 @@ class CreateInstanceModal extends React.Component {
placeholder="Confirm password"
onChange={this.handleFieldChange}
value={createDMPasswordConfirm}
+ className={errObj.createDMPasswordConfirm ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
<hr />
- <h5 className="ds-center">Optional Database Settings</h5>
+ <FormGroup controlId="createDBCheckbox">
+ <Col componentClass={ControlLabel} sm={5} title="Confirm password.">
+ <Checkbox
+ id="createDBCheckbox"
+ checked={createDBCheckbox}
+ onChange={this.handleFieldChange}
+ >
+ Create Database
+ </Checkbox>
+ </Col>
+ </FormGroup>
<FormGroup className="ds-margin-top-lg" controlId="createDBSuffix">
<Col
componentClass={ControlLabel}
@@ -1207,6 +692,8 @@ class CreateInstanceModal extends React.Component {
placeholder="e.g. dc=example,dc=com"
onChange={this.handleFieldChange}
value={createDBSuffix}
+ disabled={!createDBCheckbox}
+ className={errObj.createDBSuffix ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1225,61 +712,30 @@ class CreateInstanceModal extends React.Component {
placeholder="e.g. userRoot"
onChange={this.handleFieldChange}
value={createDBName}
+ disabled={!createDBCheckbox}
+ className={errObj.createDBName ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
<FormGroup
- key="createInitDBn"
- controlId="createInitDBn"
- disabled={false}
+ key="createInitDB"
+ controlId="createInitDB"
>
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="noInit"
- name="noInit"
- inline
- checked={createInitDB === "noInit"}
- onChange={this.handleFieldChange}
- >
- Do Not Initialize Database
- </Radio>
+ <Col componentClass={ControlLabel} sm={5}>
+ Database Initialization
</Col>
- </FormGroup>
- <FormGroup
- key="createInitDBs"
- controlId="createInitDBs"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="createSuffix"
- name="createSuffix"
- inline
- checked={createInitDB === "createSuffix"}
- onChange={this.handleFieldChange}
- >
- Create Suffix Entry
- </Radio>
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBp"
- controlId="createInitDBp"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
+ <Col sm={7}>
+ <select
+ className="btn btn-default dropdown"
id="createInitDB"
- value="createSample"
- name="createSample"
- inline
- checked={createInitDB === "createSample"}
onChange={this.handleFieldChange}
+ disabled={!createDBCheckbox}
+ value={createInitDB}
>
- Create Suffix Entry And Add Sample Entries
- </Radio>
+ <option value="noInit">Do Not Initialize Database</option>
+ <option value="createSuffix">Create Suffix Entry</option>
+ <option value="createSample">Create Sample Entries</option>
+ </select>
</Col>
</FormGroup>
</Form>
@@ -1289,7 +745,11 @@ class CreateInstanceModal extends React.Component {
<Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>
Cancel
</Button>
- <Button bsStyle="primary" onClick={this.createInstance}>
+ <Button
+ bsStyle="primary"
+ onClick={this.createInstance}
+ disabled={!createOK}
+ >
Create Instance
</Button>
</Modal.Footer>
@@ -1299,20 +759,6 @@ class CreateInstanceModal extends React.Component {
}
}
-CreateInstanceModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- loadInstanceList: PropTypes.func
-};
-
-CreateInstanceModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- loadInstanceList: noop
-};
-
export class SchemaReloadModal extends React.Component {
constructor(props) {
super(props);
@@ -1422,21 +868,7 @@ export class SchemaReloadModal extends React.Component {
}
}
-SchemaReloadModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- serverId: PropTypes.string
-};
-
-SchemaReloadModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- serverId: ""
-};
-
-class ManageBackupsModal extends React.Component {
+export class ManageBackupsModal extends React.Component {
constructor(props) {
super(props);
this.state = {
@@ -1879,6 +1311,36 @@ class ManageBackupsModal extends React.Component {
}
}
+// Proptyes and defaults
+
+CreateInstanceModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ addNotification: PropTypes.func,
+ loadInstanceList: PropTypes.func
+};
+
+CreateInstanceModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ addNotification: noop,
+ loadInstanceList: noop
+};
+
+SchemaReloadModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ addNotification: PropTypes.func,
+ serverId: PropTypes.string
+};
+
+SchemaReloadModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ addNotification: noop,
+ serverId: ""
+};
+
ManageBackupsModal.propTypes = {
showModal: PropTypes.bool,
closeHandler: PropTypes.func,
@@ -1892,5 +1354,3 @@ ManageBackupsModal.defaultProps = {
addNotification: noop,
serverId: ""
};
-
-export default DSInstance;
diff --git a/src/cockpit/389-console/src/lib/server/ldapi.jsx b/src/cockpit/389-console/src/lib/server/ldapi.jsx
index c004e63..ab6da37 100644
--- a/src/cockpit/389-console/src/lib/server/ldapi.jsx
+++ b/src/cockpit/389-console/src/lib/server/ldapi.jsx
@@ -200,7 +200,6 @@ export class ServerLDAPI extends React.Component {
type="text"
value={this.state['nsslapd-ldapientrysearchbase']}
onChange={this.handleChange}
-
/>
</Col>
</Row>
diff --git a/src/cockpit/389-console/src/lib/tools.jsx b/src/cockpit/389-console/src/lib/tools.jsx
index ea51c73..1a41058 100644
--- a/src/cockpit/389-console/src/lib/tools.jsx
+++ b/src/cockpit/389-console/src/lib/tools.jsx
@@ -148,7 +148,7 @@ export function valid_dn(dn) {
if (dn.endsWith(",")) {
return false;
}
- let dn_regex = new RegExp("^([A-Za-z]+=.*)");
+ let dn_regex = new RegExp("^([A-Za-z])+=\\S.*");
let result = dn_regex.test(dn);
return result;
}
diff --git a/src/lib389/cli/dscreate b/src/lib389/cli/dscreate
index b9c5b48..083c30c 100755
--- a/src/lib389/cli/dscreate
+++ b/src/lib389/cli/dscreate
@@ -1,7 +1,7 @@
#!/usr/bin/python3
# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2018 Red Hat, Inc.
+# Copyright (C) 2020 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
@@ -11,9 +11,9 @@
# PYTHON_ARGCOMPLETE_OK
import argparse, argcomplete
-import logging
import sys
import signal
+import json
from lib389 import DirSrv
from lib389.cli_ctl import instance as cli_instance
from lib389.cli_base import setup_script_logger
@@ -23,6 +23,9 @@ parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose',
help="Display verbose operation tracing during command execution",
action='store_true', default=False, dest='verbose')
+parser.add_argument('-j', '--json',
+ help="Return the result as a json message",
+ action='store_true', default=False, dest='json')
subparsers = parser.add_subparsers(help="action")
fromfile_parser = subparsers.add_parser('from-file', help="Create an instance of Directory Server from an inf answer file")
@@ -76,7 +79,10 @@ if __name__ == '__main__':
except Exception as e:
log.debug(e, exc_info=True)
msg = format_error_to_dict(e)
- log.error("Error: %s" % " - ".join(str(val) for val in msg.values()))
+ if args and args.json:
+ sys.stderr.write(json.dumps(msg))
+ else:
+ log.error("Error: %s" % " - ".join(str(val) for val in msg.values()))
result = False
# Done!
diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py
index 2aca136..7dd454d 100644
--- a/src/lib389/lib389/instance/setup.py
+++ b/src/lib389/lib389/instance/setup.py
@@ -572,7 +572,7 @@ class SetupDs(object):
assert_c(slapd['instance_name'] != 'admin', "Server identifier \"admin\" is reserved, please choose a different identifier")
# Check that valid characters are used
- safe = re.compile(r'^[#%:\w@_-]+$').search
+ safe = re.compile(r'^[:\w@_-]+$').search
assert_c(bool(safe(slapd['instance_name'])), "Server identifier has invalid characters, please choose a different value")
# Check if the instance exists or not.
@@ -669,11 +669,10 @@ class SetupDs(object):
self._install_ds(general, slapd, backends)
except ValueError as e:
if DEBUGGING is False:
- self.log.fatal("Error: " + str(e) + ", removing incomplete installation...")
self._remove_failed_install(slapd['instance_name'])
else:
- self.log.fatal("Error: " + str(e) + ", preserving incomplete installation for analysis...")
- raise ValueError("Instance creation failed!")
+ self.log.fatal(f"Error: {str(e)}, preserving incomplete installation for analysis...")
+ raise ValueError(f"Instance creation failed! {str(e)}")
# Call the child api to do anything it needs.
self._install(extra)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
3 years, 10 months
[389-ds-base] branch 389-ds-base-1.4.3 updated: Issue 51118 - UI - improve modal validation when creating an instance
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.3
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.3 by this push:
new b3315a5 Issue 51118 - UI - improve modal validation when creating an instance
b3315a5 is described below
commit b3315a55fd882a9f2ac2aa35d542988b876e0b72
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Fri May 29 13:42:43 2020 -0400
Issue 51118 - UI - improve modal validation when creating an instance
Description: Do not enable the "create" button until all the fields are
valid (DN's, port numbers, passwords, etc).
Improve layout and handling of optional database settings.
Add a json argument to dscreate so the UI can report any
failure text. Also improve error reporting in dscreate.
relates: https://pagure.io/389-ds-base/issue/51118
Reviewed by: firstyear & spichugi(Thanks!!)
Improve validation error messages
Fix allowed characters
---
src/cockpit/389-console/src/ds.jsx | 1191 +-----------------
.../389-console/src/{ds.jsx => dsModals.jsx} | 1282 ++++++--------------
src/cockpit/389-console/src/lib/server/ldapi.jsx | 1 -
src/cockpit/389-console/src/lib/tools.jsx | 2 +-
src/lib389/cli/dscreate | 12 +-
src/lib389/lib389/instance/setup.py | 7 +-
6 files changed, 388 insertions(+), 2107 deletions(-)
diff --git a/src/cockpit/389-console/src/ds.jsx b/src/cockpit/389-console/src/ds.jsx
index 53aa5cb..691a6f2 100644
--- a/src/cockpit/389-console/src/ds.jsx
+++ b/src/cockpit/389-console/src/ds.jsx
@@ -1,16 +1,15 @@
import cockpit from "cockpit";
import React from "react";
-import PropTypes from "prop-types";
import { Plugins } from "./plugins.jsx";
import { Database } from "./database.jsx";
import { Monitor } from "./monitor.jsx";
import { Schema } from "./schema.jsx";
import { Replication } from "./replication.jsx";
import { Server } from "./server.jsx";
-import { ConfirmPopup, DoubleConfirmModal, NotificationController } from "./lib/notifications.jsx";
-import { BackupTable } from "./lib/database/databaseTables.jsx";
-import { BackupModal, RestoreModal, DeleteBackupModal } from "./lib/database/backups.jsx";
-import { log_cmd, bad_file_name } from "./lib/tools.jsx";
+import { DoubleConfirmModal, NotificationController } from "./lib/notifications.jsx";
+import { ManageBackupsModal, SchemaReloadModal, CreateInstanceModal } from "./dsModals.jsx";
+import { log_cmd } from "./lib/tools.jsx";
+
import {
Nav,
NavItem,
@@ -20,18 +19,7 @@ import {
TabContent,
TabPane,
ProgressBar,
- FormControl,
- FormGroup,
- ControlLabel,
- Radio,
- Form,
- noop,
- Checkbox,
Spinner,
- Row,
- Modal,
- Icon,
- Col,
Button
} from "patternfly-react";
import "./css/ds.css";
@@ -722,1175 +710,4 @@ export class DSInstance extends React.Component {
}
}
-class CreateInstanceModal extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- createServerId: "",
- createPort: 389,
- createSecurePort: 636,
- createDM: "cn=Directory Manager",
- createDMPassword: "",
- createDMPasswordConfirm: "",
- createDBSuffix: "",
- createDBName: "",
- createTLSCert: true,
- createInitDB: "noInit",
- loadingCreate: false
- };
-
- this.handleFieldChange = this.handleFieldChange.bind(this);
- this.createInstance = this.createInstance.bind(this);
- }
-
- handleFieldChange(e) {
- let value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
- if (e.target.type === "number") {
- if (e.target.value) {
- value = parseInt(e.target.value);
- } else {
- value = 1;
- }
- }
- this.setState({
- [e.target.id]: value
- });
- }
-
- createInstance() {
- const {
- createServerId,
- createPort,
- createSecurePort,
- createDM,
- createDMPassword,
- createDMPasswordConfirm,
- createDBSuffix,
- createDBName,
- createTLSCert,
- createInitDB
- } = this.state;
- const { closeHandler, addNotification, loadInstanceList } = this.props;
-
- let setup_inf =
- "[general]\n" +
- "config_version = 2\n" +
- "full_machine_name = FQDN\n\n" +
- "[slapd]\n" +
- "user = dirsrv\n" +
- "group = dirsrv\n" +
- "instance_name = INST_NAME\n" +
- "port = PORT\n" +
- "root_dn = ROOTDN\n" +
- "root_password = ROOTPW\n" +
- "secure_port = SECURE_PORT\n" +
- "self_sign_cert = SELF_SIGN\n";
-
- // Server ID
- let newServerId = createServerId;
- if (newServerId === "") {
- addNotification("warning", "Instance Name is required.");
- return;
- }
- newServerId = newServerId.replace(/^slapd-/i, ""); // strip "slapd-"
- if (newServerId === "admin") {
- addNotification("warning", "Instance Name 'admin' is reserved, please choose a different name");
- return;
- }
- if (newServerId.length > 80) {
- addNotification(
- "warning",
- "Instance name is too long, it must not exceed 80 characters"
- );
- return;
- }
- if (newServerId.match(/^[#%:A-Za-z0-9_-]+$/g)) {
- setup_inf = setup_inf.replace("INST_NAME", newServerId);
- } else {
- addNotification(
- "warning",
- "Instance name can only contain letters, numbers, and: # % : - _"
- );
- return;
- }
-
- // Port
- if (createPort < 1 || createPort > 65535) {
- addNotification("warning", "Port must be a number between 1 and 65534!");
- return;
- } else {
- setup_inf = setup_inf.replace("PORT", createPort);
- }
-
- // Secure Port
- if (createSecurePort < 1 || createSecurePort > 65535) {
- addNotification("warning", "Secure Port must be a number between 1 and 65534!");
- return;
- } else {
- setup_inf = setup_inf.replace("SECURE_PORT", createSecurePort);
- }
-
- // Root DN
- if (createDM === "") {
- addNotification("warning", "You must provide a Directory Manager DN");
- return;
- } else {
- setup_inf = setup_inf.replace("ROOTDN", createDM);
- }
-
- // Setup Self-Signed Certs
- if (createTLSCert) {
- setup_inf = setup_inf.replace("SELF_SIGN", "True");
- } else {
- setup_inf = setup_inf.replace("SELF_SIGN", "False");
- }
-
- // Root DN password
- if (createDMPassword != createDMPasswordConfirm) {
- addNotification("warning", "Directory Manager passwords do not match!");
- return;
- } else if (createDMPassword == "") {
- addNotification("warning", "Directory Manager password can not be empty!");
- return;
- } else if (createDMPassword.length < 8) {
- addNotification(
- "warning",
- "Directory Manager password must have at least 8 characters"
- );
- return;
- } else {
- setup_inf = setup_inf.replace("ROOTPW", createDMPassword);
- }
-
- // Backend/Suffix
- if (
- (createDBName != "" && createDBSuffix == "") ||
- (createDBName == "" && createDBSuffix != "")
- ) {
- if (createDBName == "") {
- addNotification(
- "warning",
- "If you specify a backend suffix, you must also specify a backend name"
- );
- return;
- } else {
- addNotification(
- "warning",
- "If you specify a backend name, you must also specify a backend suffix"
- );
- return;
- }
- }
- if (createDBName != "") {
- // We definitely have a backend name and suffix, next validate the suffix is a DN
- let dn_regex = new RegExp("^([A-Za-z]+=.*)");
- if (dn_regex.test(createDBSuffix)) {
- // It's valid, add it
- setup_inf += "\n[backend-" + createDBName + "]\nsuffix = " + createDBSuffix + "\n";
- } else {
- // Not a valid DN
- addNotification("warning", "Invalid DN for Backend Suffix");
- return;
- }
-
- if (createInitDB === "createSample") {
- setup_inf += "\nsample_entries = yes\n";
- }
- if (createInitDB === "createSuffix") {
- setup_inf += "\ncreate_suffix_entry = yes\n";
- }
- }
-
- /*
- * Here are steps we take to create the instance
- *
- * [1] Get FQDN Name for nsslapd-localhost setting in setup file
- * [2] Create a file for the inf setup parameters
- * [3] Set strict permissions on that file
- * [4] Populate the new setup file with settings (including cleartext password)
- * [5] Create the instance
- * [6] Remove setup file
- */
- this.setState({
- loadingCreate: true
- });
- cockpit
-
- .spawn(["hostnamectl", "status", "--static"], { superuser: true, err: "message" })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.setState({
- loadingCreate: false
- });
- addNotification("error", `Failed to get hostname!", ${errMsg.desc}`);
- })
- .done(data => {
- /*
- * We have FQDN, so set the hostname in inf file, and create the setup file
- */
- setup_inf = setup_inf.replace("FQDN", data);
- let setup_file = "/tmp/389-setup-" + new Date().getTime() + ".inf";
- let rm_cmd = ["rm", setup_file];
- let create_file_cmd = ["touch", setup_file];
- cockpit
- .spawn(create_file_cmd, { superuser: true, err: "message" })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.setState({
- loadingCreate: false
- });
- addNotification(
- "error",
- `Failed to create installation file!" ${errMsg.desc}`
- );
- })
- .done(_ => {
- /*
- * We have our new setup file, now set permissions on that setup file before we add sensitive data
- */
- let chmod_cmd = ["chmod", "600", setup_file];
- cockpit
- .spawn(chmod_cmd, { superuser: true, err: "message" })
- .fail(err => {
- let errMsg = JSON.parse(err);
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
- this.setState({
- loadingCreate: false
- });
- addNotification(
- "error",
- `Failed to set permission on setup file ${setup_file}: ${
- errMsg.desc
- }`
- );
- })
- .done(_ => {
- /*
- * Success we have our setup file and it has the correct permissions.
- * Now populate the setup file...
- */
- let cmd = [
- "/bin/sh",
- "-c",
- '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file
- ];
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.setState({
- loadingCreate: false
- });
- addNotification(
- "error",
- `Failed to populate installation file! ${errMsg.desc}`
- );
- })
- .done(_ => {
- /*
- * Next, create the instance...
- */
- let cmd = ["dscreate", "from-file", setup_file];
- cockpit
- .spawn(cmd, {
- superuser: true,
- err: "message"
- })
- .fail(_ => {
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
- this.setState({
- loadingCreate: false
- });
- addNotification(
- "error",
- "Failed to create instance!"
- );
- })
- .done(_ => {
- // Success!!! Now cleanup everything up...
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
- this.setState({
- loadingCreate: false
- });
-
- loadInstanceList(createServerId);
- addNotification(
- "success",
- `Successfully created instance: slapd-${createServerId}`
- );
- closeHandler();
- });
- });
- });
- });
- });
- }
-
- render() {
- const { showModal, closeHandler } = this.props;
-
- const {
- loadingCreate,
- createServerId,
- createPort,
- createSecurePort,
- createDM,
- createDMPassword,
- createDMPasswordConfirm,
- createDBSuffix,
- createDBName,
- createTLSCert,
- createInitDB
- } = this.state;
-
- let createSpinner = "";
- if (loadingCreate) {
- createSpinner = (
- <Row>
- <div className="ds-margin-top-lg ds-modal-spinner">
- <Spinner loading inline size="lg" />
- Creating instance...
- </div>
- </Row>
- );
- }
-
- return (
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title className="ds-center">Create New Server Instance</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Form horizontal>
- <FormGroup controlId="createServerId">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The instance name, this is what gets appended to 'slapi-'. The instance name can only contain letters, numbers, and: # % : - _"
- >
- Instance Name
- </Col>
- <Col sm={7}>
- <FormControl
- id="createServerId"
- type="text"
- placeholder="Your_Instance_Name"
- value={createServerId}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createPort">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The server port number"
- >
- Port
- </Col>
- <Col sm={7}>
- <FormControl
- type="number"
- min="0"
- max="65535"
- value={createPort}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createSecurePort">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The secure port number for TLS connections"
- >
- Secure Port
- </Col>
- <Col sm={7}>
- <FormControl
- type="number"
- min="0"
- max="65535"
- value={createSecurePort}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createTLSCert">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="Create a self-signed certificate database"
- >
- Create Self-Signed TLS Certificate
- </Col>
- <Col sm={7}>
- <Checkbox
- id="createTLSCert"
- checked={createTLSCert}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDM">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The DN for the unrestricted user"
- >
- Directory Manager DN
- </Col>
- <Col sm={7}>
- <FormControl
- type="text"
- id="createDM"
- onChange={this.handleFieldChange}
- value={createDM}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDMPassword">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="Directory Manager password."
- >
- Directory Manager Password
- </Col>
- <Col sm={7}>
- <FormControl
- id="createDMPassword"
- type="password"
- placeholder="Enter password"
- onChange={this.handleFieldChange}
- value={createDMPassword}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDMPasswordConfirm">
- <Col componentClass={ControlLabel} sm={5} title="Confirm password.">
- Confirm Password
- </Col>
- <Col sm={7}>
- <FormControl
- id="createDMPasswordConfirm"
- type="password"
- placeholder="Confirm password"
- onChange={this.handleFieldChange}
- value={createDMPasswordConfirm}
- />
- </Col>
- </FormGroup>
- <hr />
- <h5 className="ds-center">Optional Database Settings</h5>
- <FormGroup className="ds-margin-top-lg" controlId="createDBSuffix">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN)"
- >
- Database Suffix
- </Col>
- <Col sm={7}>
- <FormControl
- type="text"
- id="createDBSuffix"
- placeholder="e.g. dc=example,dc=com"
- onChange={this.handleFieldChange}
- value={createDBSuffix}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDBName">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends."
- >
- Database Name
- </Col>
- <Col sm={7}>
- <FormControl
- type="text"
- id="createDBName"
- placeholder="e.g. userRoot"
- onChange={this.handleFieldChange}
- value={createDBName}
- />
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBn"
- controlId="createInitDBn"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="noInit"
- name="noInit"
- inline
- checked={createInitDB === "noInit"}
- onChange={this.handleFieldChange}
- >
- Do Not Initialize Database
- </Radio>
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBs"
- controlId="createInitDBs"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="createSuffix"
- name="createSuffix"
- inline
- checked={createInitDB === "createSuffix"}
- onChange={this.handleFieldChange}
- >
- Create Suffix Entry
- </Radio>
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBp"
- controlId="createInitDBp"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="createSample"
- name="createSample"
- inline
- checked={createInitDB === "createSample"}
- onChange={this.handleFieldChange}
- >
- Create Suffix Entry And Add Sample Entries
- </Radio>
- </Col>
- </FormGroup>
- </Form>
- {createSpinner}
- </Modal.Body>
- <Modal.Footer>
- <Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>
- Cancel
- </Button>
- <Button bsStyle="primary" onClick={this.createInstance}>
- Create Instance
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- );
- }
-}
-
-CreateInstanceModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- loadInstanceList: PropTypes.func
-};
-
-CreateInstanceModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- loadInstanceList: noop
-};
-
-export class SchemaReloadModal extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- reloadSchemaDir: "",
- loadingSchemaTask: false
- };
-
- this.reloadSchema = this.reloadSchema.bind(this);
- this.handleFieldChange = this.handleFieldChange.bind(this);
- }
-
- handleFieldChange(e) {
- this.setState({
- [e.target.id]: e.target.value
- });
- }
-
- reloadSchema(e) {
- const { addNotification, serverId, closeHandler } = this.props;
- const { reloadSchemaDir } = this.state;
-
- this.setState({
- loadingSchemaTask: true
- });
-
- let cmd = ["dsconf", "-j", serverId, "schema", "reload", "--wait"];
- if (reloadSchemaDir !== "") {
- cmd = [...cmd, "--schemadir", reloadSchemaDir];
- }
- log_cmd("reloadSchemaDir", "Reload schema files", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(data => {
- addNotification("success", "Successfully reloaded schema");
- this.setState({
- loadingSchemaTask: false
- });
- closeHandler();
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- addNotification("error", `Failed to reload schema files - ${errMsg.desc}`);
- closeHandler();
- });
- }
-
- render() {
- const { loadingSchemaTask, reloadSchemaDir } = this.state;
- const { showModal, closeHandler } = this.props;
-
- let spinner = "";
- if (loadingSchemaTask) {
- spinner = (
- <Row>
- <div className="ds-margin-top ds-modal-spinner">
- <Spinner loading inline size="md" />
- Reloading schema files...
- </div>
- </Row>
- );
- }
-
- return (
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title>Reload Schema Files</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Form horizontal autoComplete="off">
- <Row title="The name of the database link.">
- <Col sm={3}>
- <ControlLabel>Schema File Directory:</ControlLabel>
- </Col>
- <Col sm={9}>
- <FormControl
- type="text"
- id="reloadSchemaDir"
- value={reloadSchemaDir}
- onChange={this.handleFieldChange}
- />
- </Col>
- </Row>
- {spinner}
- </Form>
- </Modal.Body>
- <Modal.Footer>
- <Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>
- Cancel
- </Button>
- <Button bsStyle="primary" onClick={this.reloadSchema}>
- Reload Schema
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- );
- }
-}
-
-SchemaReloadModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- serverId: PropTypes.string
-};
-
-SchemaReloadModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- serverId: ""
-};
-
-class ManageBackupsModal extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- activeKey: 1,
- showConfirmBackupDelete: false,
- showConfirmBackup: false,
- showConfirmRestore: false,
- showConfirmRestoreReplace: false,
- showConfirmLDIFReplace: false,
- showRestoreSpinningModal: false,
- showDelBackupSpinningModal: false,
- showBackupModal: false,
- backupSpinning: false,
- backupName: "",
- deleteBackup: "",
- errObj: {}
- };
-
- this.handleNavSelect = this.handleNavSelect.bind(this);
- this.handleChange = this.handleChange.bind(this);
-
- // Backups
- this.doBackup = this.doBackup.bind(this);
- this.deleteBackup = this.deleteBackup.bind(this);
- this.restoreBackup = this.restoreBackup.bind(this);
- this.showConfirmRestore = this.showConfirmRestore.bind(this);
- this.closeConfirmRestore = this.closeConfirmRestore.bind(this);
- this.showConfirmBackup = this.showConfirmBackup.bind(this);
- this.closeConfirmBackup = this.closeConfirmBackup.bind(this);
- this.showConfirmBackupDelete = this.showConfirmBackupDelete.bind(this);
- this.closeConfirmBackupDelete = this.closeConfirmBackupDelete.bind(this);
- this.showBackupModal = this.showBackupModal.bind(this);
- this.closeBackupModal = this.closeBackupModal.bind(this);
- this.showRestoreSpinningModal = this.showRestoreSpinningModal.bind(this);
- this.closeRestoreSpinningModal = this.closeRestoreSpinningModal.bind(this);
- this.showDelBackupSpinningModal = this.showDelBackupSpinningModal.bind(this);
- this.closeDelBackupSpinningModal = this.closeDelBackupSpinningModal.bind(this);
- this.validateBackup = this.validateBackup.bind(this);
- this.closeConfirmRestoreReplace = this.closeConfirmRestoreReplace.bind(this);
- }
-
- closeExportModal() {
- this.setState({
- showExportModal: false
- });
- }
-
- showDelBackupSpinningModal() {
- this.setState({
- showDelBackupSpinningModal: true
- });
- }
-
- closeDelBackupSpinningModal() {
- this.setState({
- showDelBackupSpinningModal: false
- });
- }
-
- showRestoreSpinningModal() {
- this.setState({
- showRestoreSpinningModal: true
- });
- }
-
- closeRestoreSpinningModal() {
- this.setState({
- showRestoreSpinningModal: false
- });
- }
-
- showBackupModal() {
- this.setState({
- showBackupModal: true,
- backupSpinning: false,
- backupName: ""
- });
- }
-
- closeBackupModal() {
- this.setState({
- showBackupModal: false
- });
- }
-
- showConfirmBackup(item) {
- // call deleteLDIF
- this.setState({
- showConfirmBackup: true,
- backupName: item.name
- });
- }
-
- closeConfirmBackup() {
- // call importLDIF
- this.setState({
- showConfirmBackup: false
- });
- }
-
- showConfirmRestore(item) {
- this.setState({
- showConfirmRestore: true,
- backupName: item.name
- });
- }
-
- closeConfirmRestore() {
- // call importLDIF
- this.setState({
- showConfirmRestore: false
- });
- }
-
- showConfirmBackupDelete(item) {
- // calls deleteBackup
- this.setState({
- showConfirmBackupDelete: true,
- backupName: item.name
- });
- }
-
- closeConfirmBackupDelete() {
- // call importLDIF
- this.setState({
- showConfirmBackupDelete: false
- });
- }
-
- closeConfirmRestoreReplace() {
- this.setState({
- showConfirmRestoreReplace: false
- });
- }
-
- validateBackup() {
- for (let i = 0; i < this.props.backups.length; i++) {
- if (this.state.backupName == this.props.backups[i]["name"]) {
- this.setState({
- showConfirmRestoreReplace: true
- });
- return;
- }
- }
- this.doBackup();
- }
-
- doBackup() {
- this.setState({
- backupSpinning: true
- });
-
- let cmd = ["dsctl", "-j", this.props.serverId, "status"];
- cockpit
- .spawn(cmd, { superuser: true })
- .done(status_data => {
- let status_json = JSON.parse(status_data);
- if (status_json.running == true) {
- let cmd = [
- "dsconf",
- "-j",
- "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
- "backup",
- "create"
- ];
- if (this.state.backupName != "") {
- if (bad_file_name(this.state.backupName)) {
- this.props.addNotification(
- "warning",
- `Backup name should not be a path. All backups are stored in the server's backup directory`
- );
- return;
- }
- cmd.push(this.state.backupName);
- }
-
- log_cmd("doBackup", "Add backup task online", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- this.props.reload();
- this.closeBackupModal();
- this.props.addNotification("success", `Server has been backed up`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.props.reload();
- this.closeBackupModal();
- this.props.addNotification(
- "error",
- `Failure backing up server - ${errMsg.desc}`
- );
- });
- } else {
- const cmd = ["dsctl", "-j", this.props.serverId, "db2bak"];
- if (this.state.backupName != "") {
- if (bad_file_name(this.state.backupName)) {
- this.props.addNotification(
- "warning",
- `Backup name should not be a path. All backups are stored in the server's backup directory`
- );
- return;
- }
- cmd.push(this.state.backupName);
- }
- log_cmd("doBackup", "Doing backup of the server offline", cmd);
- cockpit
- .spawn(cmd, { superuser: true })
- .done(content => {
- this.props.reload();
- this.closeBackupModal();
- this.props.addNotification("success", `Server has been backed up`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.props.reload();
- this.closeBackupModal();
- this.props.addNotification(
- "error",
- `Failure backing up server - ${errMsg.desc}`
- );
- });
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("Failed to check the server status", errMsg.desc);
- });
- }
-
- restoreBackup() {
- this.showRestoreSpinningModal();
- let cmd = ["dsctl", "-j", this.props.serverId, "status"];
- cockpit
- .spawn(cmd, { superuser: true })
- .done(status_data => {
- let status_json = JSON.parse(status_data);
- if (status_json.running == true) {
- const cmd = [
- "dsconf",
- "-j",
- "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
- "backup",
- "restore",
- this.state.backupName
- ];
- log_cmd("restoreBackup", "Restoring server online", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- this.closeRestoreSpinningModal();
- this.props.addNotification("success", `Server has been restored`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.closeRestoreSpinningModal();
- this.props.addNotification(
- "error",
- `Failure restoring up server - ${errMsg.desc}`
- );
- });
- } else {
- const cmd = [
- "dsctl",
- "-j",
- this.props.serverId,
- "bak2db",
- this.state.backupName
- ];
- log_cmd("restoreBackup", "Restoring server offline", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- this.closeRestoreSpinningModal();
- this.props.addNotification("success", `Server has been restored`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.closeRestoreSpinningModal();
- this.props.addNotification(
- "error",
- `Failure restoring up server - ${errMsg.desc}`
- );
- });
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("Failed to check the server status", errMsg.desc);
- });
- }
-
- deleteBackup(e) {
- // Show confirmation
- this.showDelBackupSpinningModal();
-
- const cmd = [
- "dsctl",
- "-j",
- this.props.serverId,
- "backups",
- "--delete",
- this.state.backupName
- ];
- log_cmd("deleteBackup", "Deleting backup", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- this.props.reload();
- this.closeDelBackupSpinningModal();
- this.props.addNotification("success", `Backup was successfully deleted`);
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.props.reload();
- this.closeDelBackupSpinningModal();
- this.props.addNotification("error", `Failure deleting backup - ${errMsg.desc}`);
- });
- }
-
- handleNavSelect(key) {
- this.setState({ activeKey: key });
- }
-
- handleChange(e) {
- const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
- let valueErr = false;
- let errObj = this.state.errObj;
- if (value == "") {
- valueErr = true;
- }
- errObj[e.target.id] = valueErr;
- this.setState({
- [e.target.id]: value,
- errObj: errObj
- });
- }
-
- render() {
- const { showModal, closeHandler, backups, reload, loadingBackup } = this.props;
-
- let backupSpinner = "";
- if (loadingBackup) {
- backupSpinner = (
- <Row>
- <div className="ds-margin-top-lg ds-modal-spinner">
- <Spinner loading inline size="lg" />
- Creating instance...
- </div>
- </Row>
- );
- }
-
- return (
- <div>
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title>Manage Backups</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <div className="ds-margin-top-xlg">
- <BackupTable
- rows={backups}
- confirmRestore={this.showConfirmRestore}
- confirmDelete={this.showConfirmBackupDelete}
- />
- </div>
- {backupSpinner}
- </Modal.Body>
- <Modal.Footer>
- <Button
- bsStyle="primary"
- onClick={this.showBackupModal}
- className="ds-margin-top"
- >
- Create Backup
- </Button>
- <Button
- bsStyle="default"
- onClick={reload}
- className="ds-left-margin ds-margin-top"
- >
- Refresh Backups
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- <BackupModal
- showModal={this.state.showBackupModal}
- closeHandler={this.closeBackupModal}
- handleChange={this.handleChange}
- saveHandler={this.validateBackup}
- spinning={this.state.backupSpinning}
- error={this.state.errObj}
- />
- <RestoreModal
- showModal={this.state.showRestoreSpinningModal}
- closeHandler={this.closeRestoreSpinningModal}
- msg={this.state.backupName}
- />
- <DeleteBackupModal
- showModal={this.state.showDelBackupSpinningModal}
- closeHandler={this.closeDelBackupSpinningModal}
- msg={this.state.backupName}
- />
- <ConfirmPopup
- showModal={this.state.showConfirmRestore}
- closeHandler={this.closeConfirmRestore}
- actionFunc={this.restoreBackup}
- actionParam={this.state.backupName}
- msg="Are you sure you want to restore this backup?"
- msgContent={this.state.backupName}
- />
- <ConfirmPopup
- showModal={this.state.showConfirmBackupDelete}
- closeHandler={this.closeConfirmBackupDelete}
- actionFunc={this.deleteBackup}
- actionParam={this.state.backupName}
- msg="Are you sure you want to delete this backup?"
- msgContent={this.state.backupName}
- />
- <ConfirmPopup
- showModal={this.state.showConfirmRestoreReplace}
- closeHandler={this.closeConfirmRestoreReplace}
- actionFunc={this.doBackup}
- msg="Replace Existing Backup"
- msgContent="A backup already eixsts with the same name, do you want to replace it?"
- />
- </div>
- );
- }
-}
-
-ManageBackupsModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- serverId: PropTypes.string
-};
-
-ManageBackupsModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- serverId: ""
-};
-
export default DSInstance;
diff --git a/src/cockpit/389-console/src/ds.jsx b/src/cockpit/389-console/src/dsModals.jsx
similarity index 53%
copy from src/cockpit/389-console/src/ds.jsx
copy to src/cockpit/389-console/src/dsModals.jsx
index 53aa5cb..8f59e08 100644
--- a/src/cockpit/389-console/src/ds.jsx
+++ b/src/cockpit/389-console/src/dsModals.jsx
@@ -1,29 +1,15 @@
import cockpit from "cockpit";
import React from "react";
import PropTypes from "prop-types";
-import { Plugins } from "./plugins.jsx";
-import { Database } from "./database.jsx";
-import { Monitor } from "./monitor.jsx";
-import { Schema } from "./schema.jsx";
-import { Replication } from "./replication.jsx";
-import { Server } from "./server.jsx";
-import { ConfirmPopup, DoubleConfirmModal, NotificationController } from "./lib/notifications.jsx";
+import { ConfirmPopup } from "./lib/notifications.jsx";
import { BackupTable } from "./lib/database/databaseTables.jsx";
import { BackupModal, RestoreModal, DeleteBackupModal } from "./lib/database/backups.jsx";
-import { log_cmd, bad_file_name } from "./lib/tools.jsx";
+import { log_cmd, bad_file_name, valid_dn, valid_port } from "./lib/tools.jsx";
+
import {
- Nav,
- NavItem,
- DropdownButton,
- MenuItem,
- TabContainer,
- TabContent,
- TabPane,
- ProgressBar,
FormControl,
FormGroup,
ControlLabel,
- Radio,
Form,
noop,
Checkbox,
@@ -35,725 +21,272 @@ import {
Button
} from "patternfly-react";
import "./css/ds.css";
-import "./css/branding.css";
-
-const staticStates = {
- noPackage: (
- <h3>
- There is no <b>389-ds-base</b> package installed on this system. Sorry there is nothing
- to manage...
- </h3>
- ),
- noInsts: <h3>There are no Directory Server instances to manage</h3>,
- notRunning: (
- <h3>
- This server instance is not running, either start it from the <b>Actions</b> dropdown
- menu, or choose a different instance
- </h3>
- ),
- notConnecting: (
- <h3>
- This server instance is running, but we can not connect to it. Check LDAPI is properly
- configured on this instance.
- </h3>
- )
-};
-
-export class DSInstance extends React.Component {
- componentDidMount() {
- this.loadInstanceList();
- this.updateProgress(25);
- }
+export class CreateInstanceModal extends React.Component {
constructor(props) {
super(props);
this.state = {
- pageLoadingState: { state: "loading", jsx: "" },
- serverId: "",
- instList: [],
- backupRows: [],
- notifications: [],
- activeKey: 1,
- wasActiveList: [1],
- progressValue: 0,
- loadingOperate: false,
-
- showDeleteConfirm: false,
- modalSpinning: false,
- modalChecked: false,
-
- showSchemaReloadModal: false,
- showManageBackupsModal: false,
- showCreateInstanceModal: false
+ createServerId: "",
+ createPort: 389,
+ createSecurePort: 636,
+ createDM: "cn=Directory Manager",
+ createDMPassword: "",
+ createDMPasswordConfirm: "",
+ createDBCheckbox: false,
+ createDBSuffix: "",
+ createDBName: "",
+ createTLSCert: true,
+ createInitDB: "noInit",
+ loadingCreate: false,
+ createOK: false,
+ modalMsg: "",
+ errObj: {},
};
- this.handleServerIdChange = this.handleServerIdChange.bind(this);
this.handleFieldChange = this.handleFieldChange.bind(this);
- this.addNotification = this.addNotification.bind(this);
- this.removeNotification = this.removeNotification.bind(this);
- this.handleNavSelect = this.handleNavSelect.bind(this);
- this.loadInstanceList = this.loadInstanceList.bind(this);
- this.loadBackups = this.loadBackups.bind(this);
- this.setServerId = this.setServerId.bind(this);
- this.updateProgress = this.updateProgress.bind(this);
- this.openCreateInstanceModal = this.openCreateInstanceModal.bind(this);
- this.closeCreateInstanceModal = this.closeCreateInstanceModal.bind(this);
- this.operateInstance = this.operateInstance.bind(this);
- this.openManageBackupsModal = this.openManageBackupsModal.bind(this);
- this.closeManageBackupsModal = this.closeManageBackupsModal.bind(this);
- this.openSchemaReloadModal = this.openSchemaReloadModal.bind(this);
- this.closeSchemaReloadModal = this.closeSchemaReloadModal.bind(this);
- this.removeInstance = this.removeInstance.bind(this);
- this.showDeleteConfirm = this.showDeleteConfirm.bind(this);
- this.closeDeleteConfirm = this.closeDeleteConfirm.bind(this);
- }
-
- updateProgress(value) {
- this.setState(
- prevState => ({
- progressValue: prevState.progressValue + value
- }),
- () => {
- if (this.state.progressValue > 100) {
- this.setState(prevState => ({
- pageLoadingState: {
- ...prevState.pageLoadingState,
- state: "success"
- }
- }));
- }
- }
- );
- }
-
- setServerId(serverId, action) {
- // First we need to check if the instance is alive and well
- let cmd = ["dsctl", "-j", serverId, "status"];
- log_cmd("setServerId", "Test if instance is running ", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(status_data => {
- let status_json = JSON.parse(status_data);
- if (status_json.running) {
- this.updateProgress(25);
- let cmd = [
- "dsconf",
- "-j",
- "ldapi://%2fvar%2frun%2fslapd-" + serverId + ".socket",
- "backend",
- "suffix",
- "list",
- "--suffix"
- ];
- log_cmd("setServerId", "Test if instance is alive ", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(_ => {
- this.updateProgress(25);
- this.setState(
- {
- serverId: serverId
- },
- () => {
- this.loadBackups();
- }
- );
- if (action === "restart") {
- this.setState(
- {
- serverId: ""
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("setServerId failed: ", errMsg.desc);
- this.setState(
- {
- pageLoadingState: {
- state: "notConnecting",
- jsx: staticStates["notConnecting"]
- }
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- });
- } else {
- this.setState(
- {
- pageLoadingState: {
- state: "notRunning",
- jsx: staticStates["notRunning"]
- }
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("setServerId failed: ", errMsg.desc);
- this.setState(
- {
- pageLoadingState: {
- state: "notConnecting",
- jsx: staticStates["notConnecting"]
- }
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- });
- }
-
- loadInstanceList(serverId, action) {
- if (serverId === undefined) {
- this.setState(prevState => ({
- pageLoadingState: {
- ...prevState.pageLoadingState,
- state: "loading"
- }
- }));
- }
- let cmd = ["dsctl", "-l", "-j"];
- log_cmd("loadInstanceList", "Load the instance list select", cmd);
- cockpit
- .spawn(cmd, { superuser: true })
- .done(data => {
- this.updateProgress(25);
- let myObject = JSON.parse(data);
- this.setState({
- instList: myObject.insts,
- loadingOperate: false
- });
- // Set default value for the inst select
- if (serverId !== undefined && serverId !== "") {
- this.setState({
- wasActiveList: [this.state.activeKey]
- });
- this.setServerId(serverId, action);
- } else {
- if (myObject.insts.length > 0) {
- this.setState({
- wasActiveList: [this.state.activeKey]
- });
- this.setServerId(myObject.insts[0].replace("slapd-", ""), action);
- } else {
- this.setState({
- serverId: "",
- pageLoadingState: {
- state: "noInsts",
- jsx: staticStates["noInsts"]
- }
- });
- }
- }
- })
- .fail(_ => {
- this.setState({
- instList: [],
- serverId: "",
- loadingOperate: false,
- pageLoadingState: {
- state: "noInsts",
- jsx: staticStates["noInsts"]
- }
- });
- });
- }
-
- loadBackups() {
- const cmd = ["dsctl", "-j", this.state.serverId, "backups"];
- log_cmd("loadBackupsDSInstance", "Load Backups", cmd);
- cockpit.spawn(cmd, { superuser: true, err: "message" }).done(content => {
- this.updateProgress(25);
- const config = JSON.parse(content);
- let rows = [];
- for (let row of config.items) {
- rows.push({ name: row[0], date: [row[1]], size: [row[2]] });
- }
- this.setState({
- backupRows: rows,
- });
- });
+ this.createInstance = this.createInstance.bind(this);
+ this.validInstName = this.validInstName.bind(this);
+ this.validRootDN = this.validRootDN.bind(this);
+ this.resetModal = this.resetModal.bind(this);
}
- addNotification(type, message, timerdelay, persistent) {
- this.setState(prevState => ({
- notifications: [
- ...prevState.notifications,
- {
- key: prevState.notifications.length + 1,
- type: type,
- persistent: persistent,
- timerdelay: timerdelay,
- message: message
- }
- ]
- }));
+ componentDidMount() {
+ this.resetModal();
}
- removeNotification(notificationToRemove) {
+ resetModal() {
this.setState({
- notifications: this.state.notifications.filter(
- notification => notificationToRemove.key !== notification.key
- )
+ createServerId: "",
+ createPort: 389,
+ createSecurePort: 636,
+ createDM: "cn=Directory Manager",
+ createDMPassword: "",
+ createDMPasswordConfirm: "",
+ createDBCheckbox: false,
+ createDBSuffix: "",
+ createDBName: "",
+ createTLSCert: true,
+ createInitDB: "noInit",
+ loadingCreate: false,
+ createOK: false,
+ modalMsg: "",
+ errObj: {
+ createServerId: true,
+ createDMPassword: true,
+ createDMPasswordConfirm: true,
+ createDBSuffix: false,
+ createDBName: false,
+ },
});
}
- handleNavSelect(key) {
- this.setState({
- activeKey: key
- });
- const { wasActiveList } = this.state;
- if (!wasActiveList.includes(key)) {
- let newList = wasActiveList.concat(key);
- this.setState({
- wasActiveList: newList
- });
- }
+ validInstName(name) {
+ return /^[\w@_:-]*$/.test(name);
}
- handleServerIdChange(e) {
- this.setState({
- pageLoadingState: { state: "loading", jsx: "" },
- progressValue: 25,
- serverId: e.target.value
- });
- this.loadInstanceList(e.target.value);
+ validRootDN(dn) {
+ // Validate a DN for Directory Manager. We have to be stricter than
+ // valid_dn() and only allow stand ascii characters for the value
+ if (dn.endsWith(",")) {
+ return false;
+ }
+ // Check that the attr is only letters [A-Za-z]+ and the value is standard
+ // ascii [ -~]+ that it does not start with a space \\S
+ let dn_regex = new RegExp("^([A-Za-z]+=\\S[ -~]+$)");
+ let result = dn_regex.test(dn);
+ return result;
}
handleFieldChange(e) {
let value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
- if (e.target.type === "number") {
- if (e.target.value) {
- value = parseInt(e.target.value);
- } else {
- value = 1;
+ let target_id = e.target.id;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ let all_good = true;
+ let modal_msg = "";
+
+ errObj[target_id] = valueErr;
+ if (target_id == 'createServerId') {
+ if (value == "") {
+ all_good = false;
+ errObj['createServerId'] = true;
+ } else if (value > 80) {
+ all_good = false;
+ errObj['createServerId'] = true;
+ modal_msg = "Instance name must be less than 80 characters";
+ } else if (!this.validInstName(value)) {
+ all_good = false;
+ errObj['createServerId'] = true;
+ modal_msg = "Instance name can only contain letters, numbers, and these 4 characters: - @ : _";
}
+ } else if (this.state.createServerId == "") {
+ all_good = false;
+ errObj['createServerId'] = true;
+ } else if (!this.validInstName(this.state.createServerId)) {
+ all_good = false;
+ errObj['createServerId'] = true;
+ modal_msg = "Not all required fields have values";
}
- this.setState({
- [e.target.id]: value
- });
- }
-
- removeInstance() {
- this.operateInstance();
- this.closeDeleteConfirm();
- }
-
- operateInstance(e) {
- this.setState({
- loadingOperate: true
- });
-
- let action = "remove";
- if (e !== undefined) {
- action = e.target.id.split("-")[0];
+ if (target_id == 'createPort') {
+ if (value == "") {
+ all_good = false;
+ errObj['createPort'] = true;
+ } else if (!valid_port(value)) {
+ all_good = false;
+ errObj['createPort'] = true;
+ modal_msg = "Invalid Port number. The port must be between 1 and 65534";
+ }
+ } else if (this.state.createPort == "") {
+ all_good = false;
+ errObj['createPort'] = true;
+ } else if (!valid_port(this.state.createPort)) {
+ all_good = false;
+ errObj['createPort'] = true;
+ modal_msg = "Invalid Port number. The port must be between 1 and 65534";
}
-
- let cmd = ["dsctl", "-j", this.state.serverId, action];
- if (action === "remove") {
- cmd = [...cmd, "--do-it"];
+ if (target_id == 'createSecurePort') {
+ if (value == "") {
+ all_good = false;
+ errObj['createSecurePort'] = true;
+ } else if (!valid_port(value)) {
+ all_good = false;
+ errObj['createSecurePort'] = true;
+ modal_msg = "Invalid Secure Port number. Port must be between 1 and 65534";
+ }
+ } else if (this.state.createSecurePort == "") {
+ all_good = false;
+ errObj['createSecurePort'] = true;
}
- log_cmd("operateInstance", `Do ${action} the instance`, cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(_ => {
- if (action === "remove") {
- this.loadInstanceList();
- } else {
- this.loadInstanceList(this.state.serverId, action);
- }
- if (action === "remove") {
- this.addNotification("success", "Instance was successfully removed");
- } else {
- this.addNotification("success", `Instance was successfully ${action}ed`);
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.addNotification(
- "error",
- `Error during instance ${action} operation - ${errMsg.desc}`
- );
- this.loadInstanceList(this.state.serverId, action);
- });
- }
-
- openCreateInstanceModal() {
- this.setState({
- showCreateInstanceModal: true
- });
- }
-
- closeCreateInstanceModal() {
- this.setState({
- showCreateInstanceModal: false
- });
- }
-
- openManageBackupsModal() {
- this.setState({
- showManageBackupsModal: true
- });
- }
-
- closeManageBackupsModal() {
- this.setState({
- showManageBackupsModal: false
- });
- }
-
- openSchemaReloadModal() {
- this.setState({
- showSchemaReloadModal: true
- });
- }
-
- closeSchemaReloadModal() {
- this.setState({
- showSchemaReloadModal: false
- });
- }
-
- showDeleteConfirm() {
- this.setState({
- showDeleteConfirm: true,
- modalSpinning: false,
- modalChecked: false
- });
- }
-
- closeDeleteConfirm() {
- this.setState({
- showDeleteConfirm: false,
- modalSpinning: false,
- modalChecked: false
- });
- }
-
- render() {
- const {
- instList,
- serverId,
- progressValue,
- notifications,
- pageLoadingState,
- loadingOperate
- } = this.state;
-
- let mainContent = "";
- if (pageLoadingState.state === "loading") {
- mainContent = (
- <div id="loading-instances" className="all-pages ds-center">
- <div id="loading-page" className="ds-center ds-loading">
- <h4 id="loading-msg">Loading Directory Server Configuration...</h4>
- <p className="ds-margin-top-lg">
- <span className="spinner spinner-lg spinner-inline" />
- </p>
- <div className="progress ds-margin-top-lg">
- <ProgressBar active now={progressValue} label={`${progressValue}%`} />
- </div>
- </div>
- </div>
- );
- } else if (pageLoadingState.state === "noInsts") {
- mainContent = (
- <div id="noInsts" className="all-pages ds-center">
- {pageLoadingState.jsx}
- <p>
- <Button
- id="no-inst-create-btn"
- bsStyle="primary"
- onClick={this.openCreateInstanceModal}
- >
- Create New Instance
- </Button>
- </p>
- </div>
- );
- } else {
- mainContent = (
- <div id={pageLoadingState.state} className="all-pages ds-center">
- {pageLoadingState.jsx}
- </div>
- );
+ if (target_id == 'createDM') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDM'] = true;
+ }
+ if (!this.validRootDN(value)) {
+ all_good = false;
+ errObj['createDM'] = true;
+ modal_msg = "Invalid DN for Directory Manager";
+ }
+ } else if (this.state.createDM == "") {
+ all_good = false;
+ errObj['createDM'] = true;
+ } else if (!this.validRootDN(this.state.createDM)) {
+ all_good = false;
+ errObj['createDM'] = true;
+ modal_msg = "Invalid DN for Directory Manager";
}
-
- let operateSpinner = "";
- if (loadingOperate) {
- operateSpinner = <Spinner className="ds-operate-spinner" loading inline size="md" />;
+ if (e.target.id == 'createDMPassword') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDMPassword'] = true;
+ } else if (value != this.state.createDMPasswordConfirm) {
+ all_good = false;
+ errObj['createDMPassword'] = true;
+ errObj['createDMPasswordConfirm'] = true;
+ modal_msg = "Passwords Do Not Match";
+ } else if (value.length < 8) {
+ all_good = false;
+ errObj['createDMPassword'] = true;
+ modal_msg = "Directory Manager password must be at least 8 characters long";
+ } else {
+ errObj['createDMPassword'] = false;
+ errObj['createDMPasswordConfirm'] = false;
+ }
+ } else if (this.state.createDMPassword == "") {
+ all_good = false;
+ errObj['createDMPasswordConfirm'] = true;
+ }
+ if (e.target.id == 'createDMPasswordConfirm') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDMPasswordConfirm'] = true;
+ } else if (value != this.state.createDMPassword) {
+ all_good = false;
+ errObj['createDMPassword'] = true;
+ errObj['createDMPasswordConfirm'] = true;
+ modal_msg = "Passwords Do Not Match";
+ } else if (value.length < 8) {
+ all_good = false;
+ errObj['createDMPasswordConfirm'] = true;
+ modal_msg = "Directory Manager password must be at least 8 characters long";
+ } else {
+ errObj['createDMPassword'] = false;
+ errObj['createDMPasswordConfirm'] = false;
+ }
+ } else if (this.state.createDMPasswordConfirm == "") {
+ all_good = false;
+ errObj['createDMPasswordConfirm'] = true;
}
- return (
- <div>
- <NotificationController
- notifications={notifications}
- removeNotificationAction={this.removeNotification}
- />
- {pageLoadingState.state !== "loading" &&
- pageLoadingState.state !== "noInsts" &&
- pageLoadingState.state !== "noPackage" ? (
- <div className="ds-logo" hidden={pageLoadingState.state === "loading"}>
- <h2 className="ds-logo-style" id="main-banner">
- <div className="dropdown ds-server-action">
- <select
- className="btn btn-default dropdown"
- title="Directory Server Instance List"
- id="serverId"
- value={serverId}
- onChange={this.handleServerIdChange}
- >
- {Object.entries(instList).map(([_, inst]) => (
- <option key={inst} value={inst.replace("slapd-", "")}>
- {inst}
- </option>
- ))}
- </select>
- </div>
- {operateSpinner}
- <div className="dropdown ds-float-right">
- <DropdownButton
- pullRight
- id="ds-action"
- className="ds-action-button"
- bsStyle="primary"
- title="Actions"
- >
- <MenuItem
- id="start-ds"
- eventKey="1"
- onClick={this.operateInstance}
- >
- Start Instance
- </MenuItem>
- <MenuItem
- id="stop-ds"
- eventKey="2"
- onClick={this.operateInstance}
- >
- Stop Instance
- </MenuItem>
- <MenuItem
- id="restart-ds"
- eventKey="3"
- onClick={this.operateInstance}
- >
- Restart Instance
- </MenuItem>
- <MenuItem
- id="manage-backup-ds"
- eventKey="4"
- onClick={this.openManageBackupsModal}
- >
- Manage Backups
- </MenuItem>
- <MenuItem
- id="reload-schema-ds"
- eventKey="5"
- onClick={this.openSchemaReloadModal}
- >
- Reload Schema Files
- </MenuItem>
- <MenuItem
- id="remove-ds"
- eventKey="6"
- onClick={this.showDeleteConfirm}
- >
- Remove Instance
- </MenuItem>
- <MenuItem
- id="create-ds"
- eventKey="7"
- onClick={this.openCreateInstanceModal}
- >
- Create Instance
- </MenuItem>
- </DropdownButton>
- </div>
- </h2>
- </div>
- ) : (
- <div />
- )}
- {serverId !== "" &&
- (pageLoadingState.state === "success" || pageLoadingState.state === "loading") ? (
- <div>
- <div hidden={pageLoadingState.state === "loading"}>
- <TabContainer
- id="basic-tabs-pf"
- onSelect={this.handleNavSelect}
- activeKey={this.state.activeKey}
- >
- <div>
- <Nav className="nav nav-tabs nav-tabs-pf collapse navbar-collapse navbar-collapse-5 ds-nav navbar navbar-default">
- <NavItem className="ds-tab-main" eventKey={1}>
- Server Settings
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={2}>
- Database
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={3}>
- Replication
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={4}>
- Schema
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={5}>
- Plugins
- </NavItem>
- <NavItem className="ds-tab-main" eventKey={6}>
- Monitoring
- </NavItem>
- </Nav>
- <TabContent>
- <TabPane eventKey={1}>
- <Server
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={2}>
- <Database
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={3}>
- <Replication
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={4}>
- <Schema
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={5}>
- <Plugins
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={6}>
- <Monitor
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- </TabContent>
- </div>
- </TabContainer>
- </div>
- <div hidden={pageLoadingState.state !== "loading"}>{mainContent}</div>
- </div>
- ) : (
- <div>{mainContent}</div>
- )}
- <CreateInstanceModal
- showModal={this.state.showCreateInstanceModal}
- closeHandler={this.closeCreateInstanceModal}
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- loadInstanceList={this.loadInstanceList}
- />
- <SchemaReloadModal
- showModal={this.state.showSchemaReloadModal}
- closeHandler={this.closeSchemaReloadModal}
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- />
- <ManageBackupsModal
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- showModal={this.state.showManageBackupsModal}
- closeHandler={this.closeManageBackupsModal}
- handleChange={this.handleFieldChange}
- backups={this.state.backupRows}
- reload={this.loadBackups}
- />
- <DoubleConfirmModal
- showModal={this.state.showDeleteConfirm}
- closeHandler={this.closeDeleteConfirm}
- handleChange={this.handleFieldChange}
- actionHandler={this.removeInstance}
- spinning={this.state.modalSpinning}
- item={this.state.serverId}
- checked={this.state.modalChecked}
- mTitle="Remove Instance"
- mMsg="Are you really sure you want to delete this instance?"
- mSpinningMsg="Removing Instance..."
- mBtnName="Remove Instance"
- />
- </div>
- );
- }
-}
-
-class CreateInstanceModal extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- createServerId: "",
- createPort: 389,
- createSecurePort: 636,
- createDM: "cn=Directory Manager",
- createDMPassword: "",
- createDMPasswordConfirm: "",
- createDBSuffix: "",
- createDBName: "",
- createTLSCert: true,
- createInitDB: "noInit",
- loadingCreate: false
- };
-
- this.handleFieldChange = this.handleFieldChange.bind(this);
- this.createInstance = this.createInstance.bind(this);
- }
-
- handleFieldChange(e) {
- let value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
- if (e.target.type === "number") {
- if (e.target.value) {
- value = parseInt(e.target.value);
+ // Optional settings
+ if (target_id == 'createDBCheckbox') {
+ if (!value) {
+ errObj['createDBSuffix'] = false;
+ errObj['createDBName'] = false;
} else {
- value = 1;
+ if (this.state.createDBSuffix == "") {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ } else if (!valid_dn(this.state.createDBSuffix)) {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ modal_msg = "Invalid DN for suffix";
+ }
+ if (this.state.createDBName == "") {
+ all_good = false;
+ errObj['createDBName'] = true;
+ } else if (!valid_dn(this.state.createDBName)) {
+ all_good = false;
+ errObj['createDBName'] = true;
+ modal_msg = "Invalid name for database";
+ }
+ }
+ } else if (this.state.createDBCheckbox) {
+ if (target_id == 'createDBSuffix') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ } else if (!valid_dn(value)) {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ modal_msg = "Invalid DN for suffix";
+ }
+ } else if (this.state.createDBSuffix == "") {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ } else if (!valid_dn(this.state.createDBSuffix)) {
+ all_good = false;
+ errObj['createDBSuffix'] = true;
+ modal_msg = "Invalid DN for suffix";
}
+ if (target_id == 'createDBName') {
+ if (value == "") {
+ all_good = false;
+ errObj['createDBName'] = true;
+ } else if (/\s/.test(value)) {
+ // name has some kind of white space character
+ all_good = false;
+ errObj['createDBName'] = true;
+ modal_msg = "Database name can not contain any spaces";
+ }
+ } else if (this.state.createDBName == "") {
+ all_good = false;
+ errObj['createDBName'] = true;
+ } else if (/\s/.test(this.state.createDBName)) {
+ all_good = false;
+ errObj['createDBName'] = true;
+ modal_msg = "Invalid database name";
+ }
+ } else {
+ errObj['createDBSuffix'] = false;
+ errObj['createDBName'] = false;
}
+
this.setState({
- [e.target.id]: value
+ [target_id]: value,
+ errObj: errObj,
+ createOK: all_good,
+ modalMsg: modal_msg,
});
}
@@ -764,11 +297,11 @@ class CreateInstanceModal extends React.Component {
createSecurePort,
createDM,
createDMPassword,
- createDMPasswordConfirm,
createDBSuffix,
createDBName,
createTLSCert,
- createInitDB
+ createInitDB,
+ createDBCheckbox
} = this.state;
const { closeHandler, addNotification, loadInstanceList } = this.props;
@@ -788,56 +321,12 @@ class CreateInstanceModal extends React.Component {
// Server ID
let newServerId = createServerId;
- if (newServerId === "") {
- addNotification("warning", "Instance Name is required.");
- return;
- }
newServerId = newServerId.replace(/^slapd-/i, ""); // strip "slapd-"
- if (newServerId === "admin") {
- addNotification("warning", "Instance Name 'admin' is reserved, please choose a different name");
- return;
- }
- if (newServerId.length > 80) {
- addNotification(
- "warning",
- "Instance name is too long, it must not exceed 80 characters"
- );
- return;
- }
- if (newServerId.match(/^[#%:A-Za-z0-9_-]+$/g)) {
- setup_inf = setup_inf.replace("INST_NAME", newServerId);
- } else {
- addNotification(
- "warning",
- "Instance name can only contain letters, numbers, and: # % : - _"
- );
- return;
- }
-
- // Port
- if (createPort < 1 || createPort > 65535) {
- addNotification("warning", "Port must be a number between 1 and 65534!");
- return;
- } else {
- setup_inf = setup_inf.replace("PORT", createPort);
- }
-
- // Secure Port
- if (createSecurePort < 1 || createSecurePort > 65535) {
- addNotification("warning", "Secure Port must be a number between 1 and 65534!");
- return;
- } else {
- setup_inf = setup_inf.replace("SECURE_PORT", createSecurePort);
- }
-
- // Root DN
- if (createDM === "") {
- addNotification("warning", "You must provide a Directory Manager DN");
- return;
- } else {
- setup_inf = setup_inf.replace("ROOTDN", createDM);
- }
-
+ setup_inf = setup_inf.replace("INST_NAME", newServerId);
+ setup_inf = setup_inf.replace("PORT", createPort);
+ setup_inf = setup_inf.replace("SECURE_PORT", createSecurePort);
+ setup_inf = setup_inf.replace("ROOTDN", createDM);
+ setup_inf = setup_inf.replace("ROOTPW", createDMPassword);
// Setup Self-Signed Certs
if (createTLSCert) {
setup_inf = setup_inf.replace("SELF_SIGN", "True");
@@ -845,59 +334,13 @@ class CreateInstanceModal extends React.Component {
setup_inf = setup_inf.replace("SELF_SIGN", "False");
}
- // Root DN password
- if (createDMPassword != createDMPasswordConfirm) {
- addNotification("warning", "Directory Manager passwords do not match!");
- return;
- } else if (createDMPassword == "") {
- addNotification("warning", "Directory Manager password can not be empty!");
- return;
- } else if (createDMPassword.length < 8) {
- addNotification(
- "warning",
- "Directory Manager password must have at least 8 characters"
- );
- return;
- } else {
- setup_inf = setup_inf.replace("ROOTPW", createDMPassword);
- }
-
- // Backend/Suffix
- if (
- (createDBName != "" && createDBSuffix == "") ||
- (createDBName == "" && createDBSuffix != "")
- ) {
- if (createDBName == "") {
- addNotification(
- "warning",
- "If you specify a backend suffix, you must also specify a backend name"
- );
- return;
- } else {
- addNotification(
- "warning",
- "If you specify a backend name, you must also specify a backend suffix"
- );
- return;
- }
- }
- if (createDBName != "") {
- // We definitely have a backend name and suffix, next validate the suffix is a DN
- let dn_regex = new RegExp("^([A-Za-z]+=.*)");
- if (dn_regex.test(createDBSuffix)) {
- // It's valid, add it
- setup_inf += "\n[backend-" + createDBName + "]\nsuffix = " + createDBSuffix + "\n";
- } else {
- // Not a valid DN
- addNotification("warning", "Invalid DN for Backend Suffix");
- return;
- }
-
+ if (createDBCheckbox) {
+ setup_inf += "\n[backend-" + createDBName + "]\nsuffix = " + createDBSuffix + "\n";
if (createInitDB === "createSample") {
- setup_inf += "\nsample_entries = yes\n";
+ setup_inf += "sample_entries = yes\n";
}
if (createInitDB === "createSuffix") {
- setup_inf += "\ncreate_suffix_entry = yes\n";
+ setup_inf += "create_suffix_entry = yes\n";
}
}
@@ -915,7 +358,6 @@ class CreateInstanceModal extends React.Component {
loadingCreate: true
});
cockpit
-
.spawn(["hostnamectl", "status", "--static"], { superuser: true, err: "message" })
.fail(err => {
let errMsg = JSON.parse(err);
@@ -925,89 +367,91 @@ class CreateInstanceModal extends React.Component {
addNotification("error", `Failed to get hostname!", ${errMsg.desc}`);
})
.done(data => {
- /*
- * We have FQDN, so set the hostname in inf file, and create the setup file
- */
+ /*
+ * We have FQDN, so set the hostname in inf file, and create the setup file
+ */
setup_inf = setup_inf.replace("FQDN", data);
let setup_file = "/tmp/389-setup-" + new Date().getTime() + ".inf";
let rm_cmd = ["rm", setup_file];
let create_file_cmd = ["touch", setup_file];
+ log_cmd("createInstance", "Setting FQDN...", create_file_cmd);
cockpit
.spawn(create_file_cmd, { superuser: true, err: "message" })
.fail(err => {
- let errMsg = JSON.parse(err);
this.setState({
loadingCreate: false
});
addNotification(
"error",
- `Failed to create installation file!" ${errMsg.desc}`
+ `Failed to create installation file!" ${err.message}`
);
})
.done(_ => {
/*
- * We have our new setup file, now set permissions on that setup file before we add sensitive data
- */
+ * We have our new setup file, now set permissions on that setup file before we add sensitive data
+ */
let chmod_cmd = ["chmod", "600", setup_file];
+ log_cmd("createInstance", "Setting initial INF file permissions...", chmod_cmd);
cockpit
.spawn(chmod_cmd, { superuser: true, err: "message" })
.fail(err => {
- let errMsg = JSON.parse(err);
- cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
+ cockpit.spawn(rm_cmd, { superuser: true, err: "message" }); // Remove Inf file with clear text password
this.setState({
loadingCreate: false
});
addNotification(
"error",
- `Failed to set permission on setup file ${setup_file}: ${
- errMsg.desc
- }`
+ `Failed to set permissions on setup file ${setup_file}: ${err.message}`
);
})
.done(_ => {
/*
- * Success we have our setup file and it has the correct permissions.
- * Now populate the setup file...
- */
+ * Success we have our setup file and it has the correct permissions.
+ * Now populate the setup file...
+ */
let cmd = [
"/bin/sh",
"-c",
'/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file
];
+ // Do not log inf file as it contains the DM password
+ log_cmd("createInstance", "Apply changes to INF file...", "");
cockpit
.spawn(cmd, { superuser: true, err: "message" })
.fail(err => {
- let errMsg = JSON.parse(err);
this.setState({
loadingCreate: false
});
addNotification(
"error",
- `Failed to populate installation file! ${errMsg.desc}`
+ `Failed to populate installation file! ${err.message}`
);
})
.done(_ => {
/*
- * Next, create the instance...
- */
- let cmd = ["dscreate", "from-file", setup_file];
+ * Next, create the instance...
+ */
+ let cmd = ["dscreate", "-j", "from-file", setup_file];
+ log_cmd("createInstance", "Creating instance...", cmd);
cockpit
.spawn(cmd, {
superuser: true,
err: "message"
})
- .fail(_ => {
+ .fail(err => {
+ let errMsg = JSON.parse(err);
cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
this.setState({
loadingCreate: false
});
addNotification(
"error",
- "Failed to create instance!"
+ `${errMsg.desc}`
);
})
.done(_ => {
// Success!!! Now cleanup everything up...
+ log_cmd("createInstance", "Instance creation compelete, clean everything up...", rm_cmd);
cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password
this.setState({
loadingCreate: false
@@ -1019,6 +463,7 @@ class CreateInstanceModal extends React.Component {
`Successfully created instance: slapd-${createServerId}`
);
closeHandler();
+ this.resetModal();
});
});
});
@@ -1037,12 +482,17 @@ class CreateInstanceModal extends React.Component {
createDM,
createDMPassword,
createDMPasswordConfirm,
+ createDBCheckbox,
createDBSuffix,
createDBName,
createTLSCert,
- createInitDB
+ createInitDB,
+ createOK,
+ modalMsg,
+ errObj,
} = this.state;
-
+ let errMsgClass = "";
+ let errMsg = "";
let createSpinner = "";
if (loadingCreate) {
createSpinner = (
@@ -1055,6 +505,18 @@ class CreateInstanceModal extends React.Component {
);
}
+ if (modalMsg == "") {
+ // No errors, but to keep the modal nice and stable during input
+ // field validation we need "invisible" text to keep the modal form
+ // from jumping up and down.
+ errMsgClass = "ds-clear-text";
+ errMsg = "no errors";
+ } else {
+ // We have error text to report
+ errMsgClass = "ds-modal-error";
+ errMsg = modalMsg;
+ }
+
return (
<Modal show={showModal} onHide={closeHandler}>
<div className="ds-no-horizontal-scrollbar">
@@ -1071,11 +533,16 @@ class CreateInstanceModal extends React.Component {
</Modal.Header>
<Modal.Body>
<Form horizontal>
- <FormGroup controlId="createServerId">
+ <Row>
+ <Col className="ds-center" sm={12}>
+ <p className={errMsgClass}>{errMsg}</p>
+ </Col>
+ </Row>
+ <FormGroup controlId="createServerId" className="ds-margin-top-lg">
<Col
componentClass={ControlLabel}
sm={5}
- title="The instance name, this is what gets appended to 'slapi-'. The instance name can only contain letters, numbers, and: # % : - _"
+ title="The instance name, this is what gets appended to 'slapi-'. The instance name can only contain letters, numbers, and: # @ : - _"
>
Instance Name
</Col>
@@ -1086,6 +553,7 @@ class CreateInstanceModal extends React.Component {
placeholder="Your_Instance_Name"
value={createServerId}
onChange={this.handleFieldChange}
+ className={errObj.createServerId ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1099,11 +567,13 @@ class CreateInstanceModal extends React.Component {
</Col>
<Col sm={7}>
<FormControl
+ id="createPort"
type="number"
min="0"
max="65535"
value={createPort}
onChange={this.handleFieldChange}
+ className={errObj.createPort ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1117,11 +587,13 @@ class CreateInstanceModal extends React.Component {
</Col>
<Col sm={7}>
<FormControl
+ id="createSecurePort"
type="number"
min="0"
max="65535"
value={createSecurePort}
onChange={this.handleFieldChange}
+ className={errObj.createSecurePort ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1155,6 +627,7 @@ class CreateInstanceModal extends React.Component {
id="createDM"
onChange={this.handleFieldChange}
value={createDM}
+ className={errObj.createDM ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1173,6 +646,7 @@ class CreateInstanceModal extends React.Component {
placeholder="Enter password"
onChange={this.handleFieldChange}
value={createDMPassword}
+ className={errObj.createDMPassword ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1187,11 +661,22 @@ class CreateInstanceModal extends React.Component {
placeholder="Confirm password"
onChange={this.handleFieldChange}
value={createDMPasswordConfirm}
+ className={errObj.createDMPasswordConfirm ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
<hr />
- <h5 className="ds-center">Optional Database Settings</h5>
+ <FormGroup controlId="createDBCheckbox">
+ <Col componentClass={ControlLabel} sm={5} title="Confirm password.">
+ <Checkbox
+ id="createDBCheckbox"
+ checked={createDBCheckbox}
+ onChange={this.handleFieldChange}
+ >
+ Create Database
+ </Checkbox>
+ </Col>
+ </FormGroup>
<FormGroup className="ds-margin-top-lg" controlId="createDBSuffix">
<Col
componentClass={ControlLabel}
@@ -1207,6 +692,8 @@ class CreateInstanceModal extends React.Component {
placeholder="e.g. dc=example,dc=com"
onChange={this.handleFieldChange}
value={createDBSuffix}
+ disabled={!createDBCheckbox}
+ className={errObj.createDBSuffix ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1225,61 +712,30 @@ class CreateInstanceModal extends React.Component {
placeholder="e.g. userRoot"
onChange={this.handleFieldChange}
value={createDBName}
+ disabled={!createDBCheckbox}
+ className={errObj.createDBName ? "ds-input-bad" : ""}
/>
</Col>
</FormGroup>
<FormGroup
- key="createInitDBn"
- controlId="createInitDBn"
- disabled={false}
+ key="createInitDB"
+ controlId="createInitDB"
>
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="noInit"
- name="noInit"
- inline
- checked={createInitDB === "noInit"}
- onChange={this.handleFieldChange}
- >
- Do Not Initialize Database
- </Radio>
+ <Col componentClass={ControlLabel} sm={5}>
+ Database Initialization
</Col>
- </FormGroup>
- <FormGroup
- key="createInitDBs"
- controlId="createInitDBs"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="createSuffix"
- name="createSuffix"
- inline
- checked={createInitDB === "createSuffix"}
- onChange={this.handleFieldChange}
- >
- Create Suffix Entry
- </Radio>
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBp"
- controlId="createInitDBp"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
+ <Col sm={7}>
+ <select
+ className="btn btn-default dropdown"
id="createInitDB"
- value="createSample"
- name="createSample"
- inline
- checked={createInitDB === "createSample"}
onChange={this.handleFieldChange}
+ disabled={!createDBCheckbox}
+ value={createInitDB}
>
- Create Suffix Entry And Add Sample Entries
- </Radio>
+ <option value="noInit">Do Not Initialize Database</option>
+ <option value="createSuffix">Create Suffix Entry</option>
+ <option value="createSample">Create Sample Entries</option>
+ </select>
</Col>
</FormGroup>
</Form>
@@ -1289,7 +745,11 @@ class CreateInstanceModal extends React.Component {
<Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>
Cancel
</Button>
- <Button bsStyle="primary" onClick={this.createInstance}>
+ <Button
+ bsStyle="primary"
+ onClick={this.createInstance}
+ disabled={!createOK}
+ >
Create Instance
</Button>
</Modal.Footer>
@@ -1299,20 +759,6 @@ class CreateInstanceModal extends React.Component {
}
}
-CreateInstanceModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- loadInstanceList: PropTypes.func
-};
-
-CreateInstanceModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- loadInstanceList: noop
-};
-
export class SchemaReloadModal extends React.Component {
constructor(props) {
super(props);
@@ -1422,21 +868,7 @@ export class SchemaReloadModal extends React.Component {
}
}
-SchemaReloadModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- addNotification: PropTypes.func,
- serverId: PropTypes.string
-};
-
-SchemaReloadModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- addNotification: noop,
- serverId: ""
-};
-
-class ManageBackupsModal extends React.Component {
+export class ManageBackupsModal extends React.Component {
constructor(props) {
super(props);
this.state = {
@@ -1879,6 +1311,36 @@ class ManageBackupsModal extends React.Component {
}
}
+// Proptyes and defaults
+
+CreateInstanceModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ addNotification: PropTypes.func,
+ loadInstanceList: PropTypes.func
+};
+
+CreateInstanceModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ addNotification: noop,
+ loadInstanceList: noop
+};
+
+SchemaReloadModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ addNotification: PropTypes.func,
+ serverId: PropTypes.string
+};
+
+SchemaReloadModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ addNotification: noop,
+ serverId: ""
+};
+
ManageBackupsModal.propTypes = {
showModal: PropTypes.bool,
closeHandler: PropTypes.func,
@@ -1892,5 +1354,3 @@ ManageBackupsModal.defaultProps = {
addNotification: noop,
serverId: ""
};
-
-export default DSInstance;
diff --git a/src/cockpit/389-console/src/lib/server/ldapi.jsx b/src/cockpit/389-console/src/lib/server/ldapi.jsx
index c004e63..ab6da37 100644
--- a/src/cockpit/389-console/src/lib/server/ldapi.jsx
+++ b/src/cockpit/389-console/src/lib/server/ldapi.jsx
@@ -200,7 +200,6 @@ export class ServerLDAPI extends React.Component {
type="text"
value={this.state['nsslapd-ldapientrysearchbase']}
onChange={this.handleChange}
-
/>
</Col>
</Row>
diff --git a/src/cockpit/389-console/src/lib/tools.jsx b/src/cockpit/389-console/src/lib/tools.jsx
index ea51c73..1a41058 100644
--- a/src/cockpit/389-console/src/lib/tools.jsx
+++ b/src/cockpit/389-console/src/lib/tools.jsx
@@ -148,7 +148,7 @@ export function valid_dn(dn) {
if (dn.endsWith(",")) {
return false;
}
- let dn_regex = new RegExp("^([A-Za-z]+=.*)");
+ let dn_regex = new RegExp("^([A-Za-z])+=\\S.*");
let result = dn_regex.test(dn);
return result;
}
diff --git a/src/lib389/cli/dscreate b/src/lib389/cli/dscreate
index b9c5b48..083c30c 100755
--- a/src/lib389/cli/dscreate
+++ b/src/lib389/cli/dscreate
@@ -1,7 +1,7 @@
#!/usr/bin/python3
# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2018 Red Hat, Inc.
+# Copyright (C) 2020 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
@@ -11,9 +11,9 @@
# PYTHON_ARGCOMPLETE_OK
import argparse, argcomplete
-import logging
import sys
import signal
+import json
from lib389 import DirSrv
from lib389.cli_ctl import instance as cli_instance
from lib389.cli_base import setup_script_logger
@@ -23,6 +23,9 @@ parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose',
help="Display verbose operation tracing during command execution",
action='store_true', default=False, dest='verbose')
+parser.add_argument('-j', '--json',
+ help="Return the result as a json message",
+ action='store_true', default=False, dest='json')
subparsers = parser.add_subparsers(help="action")
fromfile_parser = subparsers.add_parser('from-file', help="Create an instance of Directory Server from an inf answer file")
@@ -76,7 +79,10 @@ if __name__ == '__main__':
except Exception as e:
log.debug(e, exc_info=True)
msg = format_error_to_dict(e)
- log.error("Error: %s" % " - ".join(str(val) for val in msg.values()))
+ if args and args.json:
+ sys.stderr.write(json.dumps(msg))
+ else:
+ log.error("Error: %s" % " - ".join(str(val) for val in msg.values()))
result = False
# Done!
diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py
index f5fc549..06d9043 100644
--- a/src/lib389/lib389/instance/setup.py
+++ b/src/lib389/lib389/instance/setup.py
@@ -573,7 +573,7 @@ class SetupDs(object):
assert_c(slapd['instance_name'] != 'admin', "Server identifier \"admin\" is reserved, please choose a different identifier")
# Check that valid characters are used
- safe = re.compile(r'^[#%:\w@_-]+$').search
+ safe = re.compile(r'^[:\w@_-]+$').search
assert_c(bool(safe(slapd['instance_name'])), "Server identifier has invalid characters, please choose a different value")
# Check if the instance exists or not.
@@ -670,11 +670,10 @@ class SetupDs(object):
self._install_ds(general, slapd, backends)
except ValueError as e:
if DEBUGGING is False:
- self.log.fatal("Error: " + str(e) + ", removing incomplete installation...")
self._remove_failed_install(slapd['instance_name'])
else:
- self.log.fatal("Error: " + str(e) + ", preserving incomplete installation for analysis...")
- raise ValueError("Instance creation failed!")
+ self.log.fatal(f"Error: {str(e)}, preserving incomplete installation for analysis...")
+ raise ValueError(f"Instance creation failed! {str(e)}")
# Call the child api to do anything it needs.
self._install(extra)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
3 years, 10 months