[luci] Support the "use same password..." checkbox properly on the backend
by Ryan McCabe
commit 2521388c51a0d0db135680e8b311b3f33b62330e
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Tue Sep 27 14:34:07 2011 -0400
Support the "use same password..." checkbox
properly on the backend
luci/validation/validate_cluster_prop.py | 17 +++++++++-
luci/validation/validate_create_cluster_form.py | 40 ++++++++++++++++++++--
2 files changed, 52 insertions(+), 5 deletions(-)
---
diff --git a/luci/validation/validate_cluster_prop.py b/luci/validation/validate_cluster_prop.py
index 6f13b78..eb071a9 100644
--- a/luci/validation/validate_cluster_prop.py
+++ b/luci/validation/validate_cluster_prop.py
@@ -121,6 +121,7 @@ def validate_add_existing(**kw):
cluster_name = kw.get('clustername')
num_nodes = kw.get('num_nodes')
+ all_passwd_same = kw.get('allSameCheckBox') is not None
if not cluster_name:
errors.append(_("No cluster name was given"))
@@ -143,6 +144,15 @@ def validate_add_existing(**kw):
node_triples = []
node_list = []
+
+ first_passwd = None
+ if all_passwd_same is True:
+ for i in xrange(num_nodes):
+ cur_passwd = kw.get('node%d_passwd' % i)
+ if cur_passwd and not cur_passwd.isspace():
+ first_passwd = cur_passwd
+ break
+
for i in xrange(num_nodes):
nodename = kw.get('node%d_host' % i)
if not nodename:
@@ -164,9 +174,14 @@ def validate_add_existing(**kw):
except ValueError:
errors.append(_("An invalid port was given for node %s: %s")
% (nodename, kw.get('node%d_port' % i)))
+
node_pass = kw.get('node%d_passwd' % i)
if not node_pass:
- errors.append(_("No password was given for node %s") % nodename)
+ if all_passwd_same and first_passwd is not None:
+ node_pass = first_passwd
+ else:
+ errors.append(_("No password was given for node %s") % nodename)
+
node_triples.append(((ricci_host, ricci_port), RicciCommunicator.auth, [ node_pass ]))
node_list.append((nodename, ricci_host, ricci_port))
diff --git a/luci/validation/validate_create_cluster_form.py b/luci/validation/validate_create_cluster_form.py
index d78192f..3d7ae2f 100644
--- a/luci/validation/validate_create_cluster_form.py
+++ b/luci/validation/validate_create_cluster_form.py
@@ -50,6 +50,7 @@ def validate_create_cluster_form(self, username, **kw):
enable_storage = kw.get('shared_storage') is not None
reboot_nodes = kw.get('reboot_nodes') is not None
download_pkgs = kw.get('download_pkgs') == 'download'
+ all_passwd_same = kw.get('allSameCheckBox') is not None
num_nodes = kw.get('num_nodes')
if num_nodes:
@@ -63,6 +64,14 @@ def validate_create_cluster_form(self, username, **kw):
return
nodes = []
+ first_passwd = None
+ if all_passwd_same is True:
+ for cur in xrange(num_nodes):
+ cur_passwd = kw.get('password%d' % cur)
+ if cur_passwd and not cur_passwd.isspace():
+ first_passwd = cur_passwd
+ break
+
for cur in xrange(num_nodes):
cur_nodename = kw.get('hostname%d' % cur)
cur_ricci_host = kw.get('riccihost%d' % cur)
@@ -75,9 +84,13 @@ def validate_create_cluster_form(self, username, **kw):
cur_ricci_host = cur_nodename
if not cur_port or cur_port.isspace():
cur_port = str(app_globals.DEFAULT_RICCI_PORT)
+
if not cur_passwd:
- errors.append(_('No password was provided for node "%s"') % cur_nodename)
- continue
+ if all_passwd_same and first_passwd is not None:
+ cur_passwd = first_passwd
+ else:
+ errors.append(_('No password was provided for node "%s"') % cur_nodename)
+ continue
nodes.append([ cur_nodename, cur_ricci_host, cur_passwd, cur_port ])
@@ -188,12 +201,18 @@ def validate_create_cluster_form(self, username, **kw):
def validate_node_add_form(model, db_obj, **kw):
errors = []
+
+ if not model:
+ flash(_('Unable to obtain the cluster configuration'), 'error')
+ return
+
cluster_name = model.getClusterName()
enable_storage = kw.get('shared_storage') is not None
reboot_nodes = kw.get('reboot_nodes') is not None
download_pkgs = kw.get('download_pkgs') == 'download'
num_nodes = kw.get('num_nodes')
+ all_passwd_same = kw.get('allSameCheckBox') is not None
if num_nodes:
try:
@@ -206,6 +225,15 @@ def validate_node_add_form(model, db_obj, **kw):
return
nodes = []
+ first_passwd = None
+
+ if all_passwd_same is True:
+ for cur in xrange(num_nodes):
+ cur_passwd = kw.get('password%d' % cur)
+ if cur_passwd and not cur_passwd.isspace():
+ first_passwd = cur_passwd
+ break
+
for cur in xrange(num_nodes):
cur_nodename = kw.get('hostname%d' % cur)
cur_ricci_host = kw.get('riccihost%d' % cur)
@@ -218,9 +246,13 @@ def validate_node_add_form(model, db_obj, **kw):
cur_ricci_host = cur_nodename
if not cur_port or cur_port.isspace():
cur_port = str(app_globals.DEFAULT_RICCI_PORT)
+
if not cur_passwd:
- errors.append(_('No password was provided for node "%s"') % cur_nodename)
- continue
+ if all_passwd_same and first_passwd is not None:
+ cur_passwd = first_passwd
+ else:
+ errors.append(_('No password was provided for node "%s"') % cur_nodename)
+ continue
node_db_obj = get_node_by_host(cur_ricci_host)
if node_db_obj is not None:
12 years, 7 months
[luci] Fix input names for corosync subsystem log files
by Ryan McCabe
commit 3788d53b38ead18d0489af3c5641c19d6d12eebf
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Tue Sep 27 14:15:35 2011 -0400
Fix input names for corosync subsystem log files
luci/templates/configure.html | 20 ++++++++++----------
1 files changed, 10 insertions(+), 10 deletions(-)
---
diff --git a/luci/templates/configure.html b/luci/templates/configure.html
index 039292f..ae86085 100644
--- a/luci/templates/configure.html
+++ b/luci/templates/configure.html
@@ -1074,7 +1074,7 @@
</div>
<div class="row">
<label class="wide">corosync CLM Log File Path</label>
- <input name="corosync CLM_logfile" type="text" class="text"
+ <input name="corosync_CLM_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
@@ -1190,7 +1190,7 @@
</div>
<div class="row">
<label class="wide">corosync CPG Log File Path</label>
- <input name="corosync CPG_logfile" type="text" class="text"
+ <input name="corosync_CPG_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
@@ -1306,7 +1306,7 @@
</div>
<div class="row">
<label class="wide">corosync MAIN Log File Path</label>
- <input name="corosync MAIN_logfile" type="text" class="text"
+ <input name="corosync_MAIN_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
@@ -1422,7 +1422,7 @@
</div>
<div class="row">
<label class="wide">corosync SERV Log File Path</label>
- <input name="corosync SERV_logfile" type="text" class="text"
+ <input name="corosync_SERV_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
@@ -1538,7 +1538,7 @@
</div>
<div class="row">
<label class="wide">corosync CMAN Log File Path</label>
- <input name="corosync CMAN_logfile" type="text" class="text"
+ <input name="corosync_CMAN_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
@@ -1654,7 +1654,7 @@
</div>
<div class="row">
<label class="wide">corosync TOTEM Log File Path</label>
- <input name="corosync TOTEM_logfile" type="text" class="text"
+ <input name="corosync_TOTEM_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
@@ -1770,7 +1770,7 @@
</div>
<div class="row">
<label class="wide">corosync QUORUM Log File Path</label>
- <input name="corosync QUORUM_logfile" type="text" class="text"
+ <input name="corosync_QUORUM_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
@@ -1886,7 +1886,7 @@
</div>
<div class="row">
<label class="wide">corosync CONFDB Log File Path</label>
- <input name="corosync CONFDB_logfile" type="text" class="text"
+ <input name="corosync_CONFDB_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
@@ -2002,7 +2002,7 @@
</div>
<div class="row">
<label class="wide">corosync CKPT Log File Path</label>
- <input name="corosync CKPT_logfile" type="text" class="text"
+ <input name="corosync_CKPT_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
@@ -2118,7 +2118,7 @@
</div>
<div class="row">
<label class="wide">corosync EVT Log File Path</label>
- <input name="corosync EVT_logfile" type="text" class="text"
+ <input name="corosync_EVT_logfile" type="text" class="text"
py:attrs="cur_log and {'value':cur_log.getLogfilePath()} or {}"/>
</div>
<div class="row">
12 years, 7 months
[luci] Add a note to the permissions page explaining that users without cluster service permissions may cau
by Ryan McCabe
commit 048caaf9e494b368ba865a1d5e05d417ce761a30
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Mon Sep 26 11:24:09 2011 -0400
Add a note to the permissions page explaining that users without cluster
service permissions may cause service state changes if they can modify the
cluster configuration.
luci/templates/admin.html | 3 +++
1 files changed, 3 insertions(+), 0 deletions(-)
---
diff --git a/luci/templates/admin.html b/luci/templates/admin.html
index 1aef694..2d1b5bd 100644
--- a/luci/templates/admin.html
+++ b/luci/templates/admin.html
@@ -65,6 +65,9 @@
</ul>
<h3 py:if="clusters">Cluster-specific Permissions</h3>
+ <p style="width:50%">
+ <small>Note: Users who can change the cluster configuration can cause existing service groups to stop by deleting them, and can cause new service groups to start automatically when creating them. Additionally, service groups may be restarted as a result of edits made to them or to their child resources while they are running.</small>
+ </p>
<div py:for="cluster_name in clusters">
Cluster: <strong>${cluster_name}</strong><br/>
<ul class="vanilla">
12 years, 7 months
[luci] Disable closing lightbox dialogs when the escape key is pressed
by Ryan McCabe
commit de395118a8f8a6447dd2df0a00d65ba6d931a90f
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Mon Sep 26 11:23:29 2011 -0400
Disable closing lightbox dialogs when the escape key is pressed
luci/public/js/add_nodes.js | 6 +++++-
luci/public/js/cluster_list.js | 10 +++++++++-
luci/public/js/failover_form.js | 6 +++++-
luci/public/js/fence.js | 12 ++++++++++--
luci/public/js/homebase.js | 6 +++++-
luci/public/js/node.js | 16 ++++++++++++++--
luci/public/js/resource.js | 6 +++++-
luci/public/js/service.js | 10 +++++++++-
8 files changed, 62 insertions(+), 10 deletions(-)
---
diff --git a/luci/public/js/add_nodes.js b/luci/public/js/add_nodes.js
index 526d6f8..a132603 100644
--- a/luci/public/js/add_nodes.js
+++ b/luci/public/js/add_nodes.js
@@ -5,6 +5,10 @@ $(function() {
width: '720px',
autoOpen: false,
draggable: false,
- resizable: true,
+ resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
})
diff --git a/luci/public/js/cluster_list.js b/luci/public/js/cluster_list.js
index ff3ee04..d10c136 100644
--- a/luci/public/js/cluster_list.js
+++ b/luci/public/js/cluster_list.js
@@ -5,7 +5,11 @@ $(function() {
width: '720px',
autoOpen: false,
draggable: false,
- resizable: false,
+ resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
})
@@ -17,5 +21,9 @@ $(function() {
autoOpen: false,
draggable: false,
resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
})
diff --git a/luci/public/js/failover_form.js b/luci/public/js/failover_form.js
index 71f8b93..9bc865d 100644
--- a/luci/public/js/failover_form.js
+++ b/luci/public/js/failover_form.js
@@ -5,6 +5,10 @@ $(function() {
width: '520px',
autoOpen: false,
draggable: false,
- resizable: false,
+ resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
})
diff --git a/luci/public/js/fence.js b/luci/public/js/fence.js
index d654aea..78c23b3 100644
--- a/luci/public/js/fence.js
+++ b/luci/public/js/fence.js
@@ -5,7 +5,11 @@ $(function() {
width: '520px',
autoOpen: false,
draggable: false,
- resizable: false,
+ resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
$('#create_fencemethod_dialog').dialog({
@@ -14,7 +18,11 @@ $(function() {
width: '520px',
autoOpen: false,
draggable: false,
- resizable: false,
+ resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
})
diff --git a/luci/public/js/homebase.js b/luci/public/js/homebase.js
index fb8474f..854b8bb 100644
--- a/luci/public/js/homebase.js
+++ b/luci/public/js/homebase.js
@@ -5,6 +5,10 @@ $(function() {
width: '720px',
autoOpen: false,
draggable: false,
- resizable: false,
+ resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
})
diff --git a/luci/public/js/node.js b/luci/public/js/node.js
index 7c2a306..2b8692b 100644
--- a/luci/public/js/node.js
+++ b/luci/public/js/node.js
@@ -4,6 +4,10 @@ $(function() {
autoOpen: false,
draggable: false,
resizable: false,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
buttons: {
"Proceed": function() {
$(this).dialog("close");
@@ -19,7 +23,11 @@ $(function() {
var targetUrl = $(this).attr("href");
$("#confirm_remove_node_dialog").dialog({
- buttons : {
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
+ closeOnEscape: false,
+ buttons: {
"Proceed": function() {
window.location.href = targetUrl;
$(this).dialog("close");
@@ -46,7 +54,11 @@ $(function() {
e.preventDefault();
var ret = $("#confirm_remove_node_dialog").dialog({
- buttons : {
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
+ buttons: {
"Proceed": function() {
$(this).dialog("close");
$(cur_form).append('<input type="hidden" name="MultiAction" value="Delete"/>');
diff --git a/luci/public/js/resource.js b/luci/public/js/resource.js
index 22fded5..f04a508 100644
--- a/luci/public/js/resource.js
+++ b/luci/public/js/resource.js
@@ -5,7 +5,11 @@ $(function() {
width: '520px',
autoOpen: false,
draggable: false,
- resizable: false,
+ resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
})
diff --git a/luci/public/js/service.js b/luci/public/js/service.js
index 7014120..7ce3739 100644
--- a/luci/public/js/service.js
+++ b/luci/public/js/service.js
@@ -6,6 +6,10 @@ $(function() {
autoOpen: false,
draggable: false,
resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
})
@@ -16,7 +20,11 @@ $(function() {
width: '520px',
autoOpen: false,
draggable: false,
- resizable: false,
+ resizable: true,
+ closeOnEscape: false,
+ open: function(event, ui) {
+ $(this).parent().children().children('.ui-dialog-titlebar-close').hide();
+ },
});
})
12 years, 7 months
[luci] Create cluster roles properly after a delete/recreate
by Ryan McCabe
commit 9b1fdc1f374e275cf8d50d531f318613a371891a
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Mon Sep 19 16:38:06 2011 -0400
Create cluster roles properly after a delete/recreate
luci/validation/validate_create_cluster_form.py | 8 ++++----
1 files changed, 4 insertions(+), 4 deletions(-)
---
diff --git a/luci/validation/validate_create_cluster_form.py b/luci/validation/validate_create_cluster_form.py
index 209a561..d78192f 100644
--- a/luci/validation/validate_create_cluster_form.py
+++ b/luci/validation/validate_create_cluster_form.py
@@ -15,7 +15,7 @@ from luci.model.objects import Node, Cluster, Task
from luci.lib.ricci_helpers import send_batch_parallel, update_cluster_conf
from luci.lib.ricci_communicator import RicciCommunicator
-from luci.lib.db_helpers import get_node_by_host, grant_all_cluster_roles
+from luci.lib.db_helpers import get_node_by_host, grant_all_cluster_roles, db_create_cluster_roles
from luci.lib.ClusterConf.ClusterNode import ClusterNode
import luci.lib.ricci_queries as rq
@@ -161,8 +161,7 @@ def validate_create_cluster_form(self, username, **kw):
cur_task_obj = task_db_obj.get(node_db_obj[i].hostname)
if cur_task_obj:
node_db_obj[i].tasks = [ cur_task_obj ]
- else:
- print "NO TASK OBJ FOR %s" % i
+ db_create_cluster_roles(cluster_db_obj)
DBSession.add(cluster_db_obj)
except Exception, e:
log.exception('Error updating luci DB for cluster %s' % cluster_name)
@@ -177,7 +176,8 @@ def validate_create_cluster_form(self, username, **kw):
log.exception('Error flushing DB while creating %s' % cluster_name)
try:
- grant_all_cluster_roles(username, cluster_name)
+ if username != 'root':
+ grant_all_cluster_roles(username, cluster_name)
except:
log.exception("grant all cluster roles")
flash(_('Creating the cluster "%s"...') % cluster_name, 'info')
12 years, 8 months
[luci] Fix permission checks for actions using checkbox selection from the nodes list.
by Ryan McCabe
commit d08fa3e2f0239b4a63c0988c29b3eb5a4977bdab
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Fri Sep 16 12:40:07 2011 -0400
Fix permission checks for actions using checkbox
selection from the nodes list.
luci/controllers/cluster.py | 49 ++++++++++++++++++++++++++----------------
1 files changed, 30 insertions(+), 19 deletions(-)
---
diff --git a/luci/controllers/cluster.py b/luci/controllers/cluster.py
index 06fa612..e593e01 100644
--- a/luci/controllers/cluster.py
+++ b/luci/controllers/cluster.py
@@ -377,25 +377,6 @@ class IndividualClusterController(BaseController):
# This processes all of the commands that we can apply to a node
@expose("luci.templates.node")
def nodes_cmd(self, command=None, **kw):
- if command == 'Delete':
- try:
- permission_membership(self.name)
- except NotAuthorizedError, e:
- flash(e, status="warning")
- redirect(tmpl_context.cluster_url)
- elif command == 'Update Attributes':
- try:
- permission_config(self.name)
- except NotAuthorizedError, e:
- flash(e, status="warning")
- redirect(tmpl_context.cluster_url)
- else:
- try:
- permission_node_cmd(self.name)
- except NotAuthorizedError, e:
- flash(e, status="warning")
- redirect(tmpl_context.cluster_url)
-
self.get_model()
if not self.model:
flash(_('Unable to contact any nodes in this cluster'),
@@ -417,24 +398,48 @@ class IndividualClusterController(BaseController):
redirect(tmpl_context.cluster_url)
if command == 'Reboot':
+ try:
+ permission_node_cmd(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
log.info('User "%s" rebooted "%s" in cluster "%s"'
% (self.username, ', '.join(cur_list), self.name))
flash(_("Rebooting %s") % ', '.join(cur_list),
status='info')
rh.cluster_node_reboot(self.name, cur_list)
elif command == 'Join Cluster':
+ try:
+ permission_node_cmd(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
log.info('User "%s" started nodes "%s" in cluster "%s"'
% (self.username, ', '.join(cur_list), self.name))
flash(_("Starting nodes: %s") % ', '.join(cur_list),
status='info')
rh.cluster_node_start(self.name, cur_list)
elif command == 'Leave Cluster':
+ try:
+ permission_node_cmd(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
log.info('User "%s" stopped nodes "%s" in cluster "%s"'
% (self.username, ', '.join(cur_list), self.name))
flash(_("Stopping nodes: %s") % ', '.join(cur_list),
status='info')
rh.cluster_node_stop(self.name, cur_list)
elif command == 'Delete':
+ try:
+ permission_membership(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
if len(cur_list) == len(self.model.getNodes()):
log.info('User "%s" deleted cluster "%s"'
% (self.username, self.name))
@@ -459,6 +464,12 @@ class IndividualClusterController(BaseController):
flash(_("Nodes must be stopped prior to being deleted"),
status='error')
elif command == 'Update Attributes':
+ try:
+ permission_config(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
vret = vcp.validate_node_prop_settings_form(cur_list[0], self.model, **kw)
if vret[0] is True:
log.info('User "%s" changed properties for node "%s" from cluster "%s"'
12 years, 8 months
[luci] Display a sensible error message if a user tries to add an existing cluster that has already been ad
by Ryan McCabe
commit 632c69290d36fef17aa8e87daa46a1fd0e1fe3c6
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Fri Sep 16 11:54:25 2011 -0400
Display a sensible error message if a user tries to
add an existing cluster that has already been added
luci/lib/async_helpers.py | 6 +++++-
luci/validation/validate_cluster_prop.py | 6 +++++-
2 files changed, 10 insertions(+), 2 deletions(-)
---
diff --git a/luci/lib/async_helpers.py b/luci/lib/async_helpers.py
index a2adb4f..7696bd4 100644
--- a/luci/lib/async_helpers.py
+++ b/luci/lib/async_helpers.py
@@ -11,7 +11,7 @@ from luci.lib.ricci_communicator import RicciCommunicator
import luci.lib.ricci_queries as rq
from luci.lib.cluster_conf_helpers import get_cluster_conf_nodes
-from luci.lib.db_helpers import get_cluster_task_status
+from luci.lib.db_helpers import get_cluster_task_status, get_cluster_db_obj
import logging
log = logging.getLogger(__name__)
@@ -30,6 +30,10 @@ def get_node_list(host, port, passwd):
if not cluster_name:
return { 'errors': [_('Host "%s" is not a member of a cluster') % host ] }
+ db_obj = get_cluster_db_obj(cluster_name)
+ if db_obj is not None:
+ return { 'errors': [_('A cluster named "%s" has already been added') % cluster_name ] }
+
conf_xml = rq.getClusterConf(rc)
if conf_xml:
node_names = get_cluster_conf_nodes(conf_xml)
diff --git a/luci/validation/validate_cluster_prop.py b/luci/validation/validate_cluster_prop.py
index 207cb71..6f13b78 100644
--- a/luci/validation/validate_cluster_prop.py
+++ b/luci/validation/validate_cluster_prop.py
@@ -15,7 +15,7 @@ from luci.lib.ClusterConf.Method import Method
from luci.lib.ClusterConf.Logging import Logging
from luci.lib.ClusterConf.LoggingDaemon import LoggingDaemon
-from luci.lib.db_helpers import create_cluster_obj, get_node_by_name
+from luci.lib.db_helpers import create_cluster_obj, get_node_by_name, get_cluster_db_obj
from luci.lib.ricci_communicator import RicciCommunicator
from luci.lib.ricci_helpers import send_batch_parallel
@@ -125,6 +125,10 @@ def validate_add_existing(**kw):
if not cluster_name:
errors.append(_("No cluster name was given"))
+ db_obj = get_cluster_db_obj(cluster_name)
+ if db_obj is not None:
+ errors.append(_('A cluster named "%s" has already been added') % cluster_name)
+
if not num_nodes:
errors.append(_("The number of cluster nodes was not given"))
else:
12 years, 8 months
[luci] Admin screen for setting user ACLs. Could use improving.
by Ryan McCabe
commit 89636067750ba17d83d524eaa49a0fc51cb25af9
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Fri Sep 16 03:27:05 2011 -0400
Admin screen for setting user ACLs. Could use improving.
luci/templates/admin.html | 147 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 147 insertions(+), 0 deletions(-)
---
diff --git a/luci/templates/admin.html b/luci/templates/admin.html
new file mode 100644
index 0000000..1aef694
--- /dev/null
+++ b/luci/templates/admin.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<xi:include href="master.html"/>
+
+<?python
+ from luci.lib import db_helpers
+?>
+
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title>User Permissions</title>
+</head>
+
+<body>
+ <div class="mainpage">
+ <h2>User Permissions</h2>
+ <div id="cluster_perms">
+ <form name="user_select" method="get" action="${tg.url('/admin')}">
+ <p>
+ <select name="name"
+ onchange="this.form.submit()"
+ py:with="usernames = db_helpers.get_user_names() or []">
+ <option value=""
+ py:attrs="{'selected': (not defined('name') or not name) and 'selected' or None}">Select a User</option>
+ <py:for each="uname in usernames">
+ <option py:if="uname != 'root'"
+ py:attrs="{'value': uname,
+ 'selected': (defined('name') and name == uname) and 'selected' or None}"
+ py:content="uname"/>
+ </py:for>
+ </select>
+ </p>
+ </form>
+
+ <py:if test="defined('name') and name">
+ <div py:with="roles = db_helpers.get_user_roles(name);
+ clusters = db_helpers.get_cluster_list()">
+ <form name="manage_edit" method="post" action="${tg.url('admin_cmd')}">
+ <input type="hidden" name="name" value="${name}"/>
+ <ul class="vanilla">
+ <li>
+ <label for="managers" class="wide">Luci Administrator</label>
+ <input type="checkbox" class="checkbox" name="role:managers"
+ id="managers"
+ py:attrs="{'checked': 'managers' in roles and 'checked' or None}"/>
+ </li>
+ <li>
+ <br/>
+ <label for="create_cluster" class="wide">Can Create Clusters</label>
+ <input type="checkbox" class="checkbox" name="role:create_cluster"
+ id="create_cluster"
+ py:attrs="{'checked': 'create_cluster' in roles and 'checked' or None}"/>
+ </li>
+ <li>
+ <br/>
+ <label for="import_cluster" class="wide">Can Import Existing Clusters</label>
+ <input type="checkbox" class="checkbox" name="role:import_cluster"
+ id="import_cluster"
+ py:attrs="{'checked': 'import_cluster' in roles and 'checked' or None}"/><br/>
+ </li>
+ </ul>
+
+ <h3 py:if="clusters">Cluster-specific Permissions</h3>
+ <div py:for="cluster_name in clusters">
+ Cluster: <strong>${cluster_name}</strong><br/>
+ <ul class="vanilla">
+ <li class="vanilla">
+ <label class="wide" for="${'view_%s' % cluster_name}">Can View This Cluster</label>
+ <input type="checkbox" class="checkbox"
+ py:attrs="{
+ 'id': 'view_%s' % cluster_name,
+ 'name': 'role:view_%s' % cluster_name,
+ 'checked': 'view_%s' % cluster_name in roles and 'checked' or None}"/>
+ </li>
+ <li class="vanilla">
+ <br/>
+ <label class="wide" for="${'config_%s' % cluster_name}">
+ Can Change the Cluster Configuration
+ </label>
+ <input type="checkbox" class="checkbox"
+ py:attrs="{
+ 'id': 'config_%s' % cluster_name,
+ 'name': 'role:config_%s' % cluster_name,
+ 'checked': 'config_%s' % cluster_name in roles and 'checked' or None}"/>
+ </li>
+ <li class="vanilla">
+ <br/>
+ <label class="wide" for="${'service_cmd_%s' % cluster_name}">
+ Can Enable, Disable, Relocate, and Migrate Service Groups
+ </label>
+ <input type="checkbox" class="checkbox"
+ py:attrs="{
+ 'id': 'service_cmd_%s' % cluster_name,
+ 'name': 'role:service_cmd_%s' % cluster_name,
+ 'checked': 'service_cmd_%s' % cluster_name in roles and 'checked' or None}"/>
+ </li>
+ <li class="vanilla">
+ <br/>
+ <label class="wide" for="${'node_cmd_%s' % cluster_name}">
+ Can Stop, Start, and Reboot Cluster Nodes
+ </label>
+ <input type="checkbox" class="checkbox"
+ py:attrs="{
+ 'id': 'node_cmd_%s' % cluster_name,
+ 'name': 'role:node_cmd_%s' % cluster_name,
+ 'checked': 'node_cmd_%s' % cluster_name in roles and 'checked' or None}"/>
+ </li>
+ <li class="vanilla">
+ <br/>
+ <label class="wide" for="${'membership_%s' % cluster_name}">
+ Can Add and Delete Nodes
+ </label>
+ <input type="checkbox" class="checkbox"
+ py:attrs="{
+ 'id': 'membership_%s' % cluster_name,
+ 'name': 'role:membership_%s' % cluster_name,
+ 'checked': 'membership_%s' % cluster_name in roles and 'checked' or None}"/>
+ </li>
+ <li class="vanilla">
+ <br/>
+ <label class="wide" for="${'remove_%s' % cluster_name}">
+ Can Remove This Cluster From Luci
+ </label>
+ <input type="checkbox" class="checkbox"
+ py:attrs="{
+ 'id': 'remove_%s' % cluster_name,
+ 'name': 'role:remove_%s' % cluster_name,
+ 'checked': 'remove_%s' % cluster_name in roles and 'checked' or None}"/>
+ </li>
+ </ul>
+ </div>
+ <div>
+ <br/>
+ <input type="reset" class="formsubmit button blue" value="Reset"/>
+ <input type="submit" class="formsubmit button blue" value="Submit"/>
+ </div>
+ </form>
+ </div>
+ </py:if>
+ </div>
+ </div>
+</body>
+</html>
12 years, 8 months
[luci] User ACL support
by Ryan McCabe
commit 6dd353cdbef3322e0035b19304de8f7ab2ed5b3d
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Fri Sep 16 03:26:48 2011 -0400
User ACL support
luci/config/app_cfg.py | 20 ++-
luci/controllers/async.py | 8 +
luci/controllers/cluster.py | 184 +++++++++++++++++++++--
luci/controllers/root.py | 59 ++++++-
luci/lib/cluster_permissions.py | 36 +++++
luci/lib/db_helpers.py | 134 ++++++++++++++++-
luci/lib/plugin_sasl2auth.py | 57 +++++---
luci/model/auth.py | 7 +-
luci/public/css/shared.css | 7 +
luci/templates/header.html | 1 +
luci/validation/validate_cluster_prop.py | 39 +++++
luci/validation/validate_create_cluster_form.py | 8 +-
luci/websetup.py | 24 ++--
13 files changed, 522 insertions(+), 62 deletions(-)
---
diff --git a/luci/config/app_cfg.py b/luci/config/app_cfg.py
index b7a4b42..a5a23bd 100644
--- a/luci/config/app_cfg.py
+++ b/luci/config/app_cfg.py
@@ -11,11 +11,13 @@ convert them into boolean, for example, you should use the
"""
-from tg.configuration import AppConfig
+from tg.configuration import AppConfig, config
import luci
+import sqlalchemy
from luci import model
from luci.lib import app_globals, helpers
+from luci.lib.db_helpers import update_db_objects
class LuciAppConfig(AppConfig):
def __init__(self):
@@ -23,11 +25,22 @@ class LuciAppConfig(AppConfig):
super(LuciAppConfig, self).__init__()
def after_init_config(self):
- from pylons import config
+ from pylons import config as pylons_config
for key, item in self.pylons_extra.iteritems():
- config[key] = item
+ pylons_config[key] = item
del self.pylons_extra
+ def setup_sqlalchemy(self):
+ from pylons import config as pylons_config
+ from sqlalchemy import engine_from_config
+ engine = engine_from_config(pylons_config, 'sqlalchemy.')
+ config['pylons.app_globals'].sa_engine = engine
+ self.package.model.init_model(engine)
+ try:
+ update_db_objects()
+ except:
+ pass
+
base_config = LuciAppConfig()
base_config.package = luci
base_config.renderers = []
@@ -41,5 +54,6 @@ base_config.pylons_extra['templating.genshi.method'] = 'xhtml'
# Configure the base SQLAlchemy Setup
base_config.use_sqlalchemy = True
+base_config.sqlalchemy = {"poolclass": sqlalchemy.pool.NullPool}
base_config.model = luci.model
base_config.DBSession = luci.model.DBSession
diff --git a/luci/controllers/async.py b/luci/controllers/async.py
index aa4a567..66f0e68 100644
--- a/luci/controllers/async.py
+++ b/luci/controllers/async.py
@@ -9,6 +9,8 @@ from tg import redirect, expose
from luci.lib.helpers import ugettext as _
from repoze.what.predicates import not_anonymous
+from luci.lib.cluster_permissions import permission_view
+
from luci.lib.base import BaseController
__all__ = ['AsyncController']
@@ -43,6 +45,12 @@ class AsyncController(BaseController):
def is_cluster_busy(self, **kw):
from luci.lib.async_helpers import cluster_is_busy
cluster_name = kw.get('cluster')
+
+ try:
+ permission_view(cluster_name)
+ except Exception, e:
+ return {'code': -2, 'data': {'status': str(e)}}
+
if not cluster_name:
return {'code': -2, 'data': {'status': _('No cluster name was given')}}
return cluster_is_busy(cluster_name)
diff --git a/luci/controllers/cluster.py b/luci/controllers/cluster.py
index 4498335..06fa612 100644
--- a/luci/controllers/cluster.py
+++ b/luci/controllers/cluster.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2010 Red Hat, Inc.
+# Copyright (C) 2009-2011 Red Hat, Inc.
#
# This program is free software; you can redistribute
# it and/or modify it under the terms of version 2 of the
@@ -9,15 +9,16 @@
"""Cluster controller module"""
# turbogears imports
-from tg import request, expose, redirect, flash, app_globals, tmpl_context
+from tg import request, expose, redirect, flash, app_globals, tmpl_context, require
# third party imports
-from repoze.what.predicates import not_anonymous
+from repoze.what.predicates import not_anonymous, in_any_group, is_user, Any, NotAuthorizedError
from luci.lib.helpers import ugettext as _
# project specific imports
from luci.lib.base import BaseController
from luci.lib.db_helpers import get_cluster_list, get_model_for_cluster, get_status_for_cluster, db_remove_cluster, get_agent_for_cluster, get_cluster_db_obj, reconcile_db_with_conf
+from luci.lib.cluster_permissions import permission_remove, permission_svc_cmd, permission_config, permission_membership, permission_node_cmd, permission_view
import luci.lib.ricci_helpers as rh
from luci.lib.flash2 import flash2
import luci.validation.validate_cluster_prop as vcp
@@ -45,8 +46,17 @@ class ClusterController(BaseController):
return dict(page='cluster', clusterpage='clusterpage')
@expose('luci.templates.cluster_list')
+ @require(Any(is_user('root'),
+ in_any_group('managers', 'create_cluster'),
+ msg=_("You must be an administrator or be granted access to create clusters")))
def create_cmd(self, command=None, **kw):
- validate_create_cluster_form(self, **kw)
+ try:
+ identity = request.environ.get('repoze.who.identity')
+ username = identity['repoze.who.userid']
+ except:
+ username = None
+ log.exception("Error getting current username")
+ validate_create_cluster_form(self, username, **kw)
return dict(use_referrer=False, page='cluster', clusterpage='clusterpage')
@expose('luci.templates.cluster_list')
@@ -55,7 +65,11 @@ class ClusterController(BaseController):
command = kw['MultiAction']
for key, element in kw.items():
if element == "on":
- db_remove_cluster(unicode(key))
+ try:
+ permission_remove(key)
+ db_remove_cluster(unicode(key))
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
redirect('/cluster/')
@expose()
@@ -64,8 +78,12 @@ class ClusterController(BaseController):
cluster_list = get_cluster_list() or []
for i in cluster_list:
if i.name == name:
- icc = IndividualClusterController(name, data)
- return icc, args
+ try:
+ permission_view(name)
+ icc = IndividualClusterController(name, data)
+ return icc, args
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
cc = ClusterController
#flash(("No cluster named \"%s\" is managed by luci") % name,
# status='error')
@@ -95,6 +113,12 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.node")
def index(self):
+ try:
+ permission_view(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
db = get_cluster_db_obj(self.name)
return dict(page='nodes', name=self.nodename, base_url='/nodes', nodes=db.nodes)
@@ -151,11 +175,23 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.node")
def nodes(self):
+ try:
+ permission_view(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
db = get_cluster_db_obj(self.name)
return dict(use_referrer=False, page='nodes', name=self.nodename, base_url='/nodes', nodes=db.nodes)
@expose("luci.templates.node")
def add_nodes_cmd(self, command=None, **kw):
+ try:
+ permission_membership(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect('%s%s' % (tmpl_context.cluster_url, 'nodes'))
+
self.get_model()
db = get_cluster_db_obj(self.name)
vret = validate_node_add_form(self.model, db, **kw)
@@ -163,12 +199,19 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.node")
def nodes_fence_cmd(self, command=None, **kw):
+ cur_nodename = kw.get('node')
+
+ try:
+ permission_config(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect('%s%s' % (tmpl_context.cluster_url, cur_nodename))
+
self.get_model()
if not self.model:
flash(_('Unable to contact any nodes in this cluster'),
status="error")
redirect(tmpl_context.cluster_url)
- cur_nodename = kw.get('node')
if kw.get("method_to_remove"):
node = self.model.retrieveNodeByName(cur_nodename)
@@ -334,6 +377,25 @@ class IndividualClusterController(BaseController):
# This processes all of the commands that we can apply to a node
@expose("luci.templates.node")
def nodes_cmd(self, command=None, **kw):
+ if command == 'Delete':
+ try:
+ permission_membership(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+ elif command == 'Update Attributes':
+ try:
+ permission_config(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+ else:
+ try:
+ permission_node_cmd(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
self.get_model()
if not self.model:
flash(_('Unable to contact any nodes in this cluster'),
@@ -422,6 +484,12 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.resource")
def resources(self, *args):
+ try:
+ permission_view(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
base_url = '/cluster/%s/resources' % self.name
resources_cmd = '/cluster/%s/resources_cmd' % self.name
@@ -433,9 +501,15 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.resource")
def resources_cmd(self, command=None, **kw):
- self.get_model()
tmpl_context.cluster_url = '/cluster/%s/resources' % self.name
+ try:
+ permission_config(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
+ self.get_model()
if not self.model:
flash(_('Unable to contact any nodes in this cluster'),
status="error")
@@ -523,6 +597,12 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.service")
def services(self, *args):
+ try:
+ permission_view(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
base_url = '/cluster/%s/services' % self.name
services_cmd = '/cluster/%s/services_cmd' % self.name
@@ -534,9 +614,15 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.service")
def services_cmd(self, command=None, **kw):
- self.get_model()
tmpl_context.cluster_url = '/cluster/%s/services' % self.name
+ try:
+ permission_view(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
+ self.get_model()
if not self.model:
flash(_('Unable to contact any nodes in this cluster'),
status="error")
@@ -560,6 +646,12 @@ class IndividualClusterController(BaseController):
redirect(tmpl_context.cluster_url)
if command == 'Delete':
+ try:
+ permission_config(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
log.info('User "%s" deleted service "%s" in cluster "%s"'
% (self.username, ', '.join(cur_list), self.name))
flash(_("Deleting services %s") % ', '.join(cur_list),
@@ -570,6 +662,12 @@ class IndividualClusterController(BaseController):
rh.update_cluster_conf(self.model)
redirect(tmpl_context.cluster_url)
if command == 'Start':
+ try:
+ permission_svc_cmd(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
move_action = kw.get('move_action')
if move_action == 'migrate':
vm_name = cur_list[0]
@@ -592,6 +690,12 @@ class IndividualClusterController(BaseController):
for i in cur_list:
rh.cluster_svc_start(self.name, i, preferred_node)
elif command == 'Disable':
+ try:
+ permission_svc_cmd(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
log.info('User "%s" disabled service "%s" in cluster "%s"'
% (self.username, ', '.join(cur_list), self.name))
flash(_("Disabling services %s") % ', '.join(cur_list),
@@ -599,6 +703,12 @@ class IndividualClusterController(BaseController):
for i in cur_list:
rh.cluster_svc_disable(self.name, i)
elif command == 'Restart':
+ try:
+ permission_svc_cmd(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
log.info('User "%s" restarted service "%s" in cluster "%s"'
% (self.username, ', '.join(cur_list), self.name))
flash(_("Restarting services %s") % ', '.join(cur_list),
@@ -606,6 +716,12 @@ class IndividualClusterController(BaseController):
for i in cur_list:
rh.cluster_svc_restart(self.name, i)
elif command in ('Create', 'Edit'):
+ try:
+ permission_config(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
if command == 'Create':
cur_action = _('Created')
else:
@@ -637,6 +753,12 @@ class IndividualClusterController(BaseController):
def failovers(self, *args):
failovers_cmd = '/cluster/%s/failovers_cmd' % self.name
+ try:
+ permission_view(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
if len(args) == 1:
failovername = args[0]
else:
@@ -645,9 +767,15 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.failover")
def failovers_cmd(self, command=None, **kw):
- self.get_model()
tmpl_context.cluster_url = '/cluster/%s/failovers' % self.name
+ try:
+ permission_config(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
+ self.get_model()
if not self.model:
flash(_('Unable to contact any nodes in this cluster'),
status="error")
@@ -734,6 +862,12 @@ class IndividualClusterController(BaseController):
def fences(self, *args):
fences_cmd = '/cluster/%s/fences_cmd' % self.name
+ try:
+ permission_view(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
if len(args) == 1:
fencename = args[0]
else:
@@ -742,9 +876,15 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.fence")
def fences_cmd(self, command=None, **kw):
- self.get_model()
tmpl_context.cluster_url = '/cluster/%s/fences' % self.name
+ try:
+ permission_config(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
+ self.get_model()
if not self.model:
flash(_('Unable to contact any nodes in this cluster'),
status="error")
@@ -807,14 +947,26 @@ class IndividualClusterController(BaseController):
@expose("luci.templates.configure")
def configure(self, *args):
+ try:
+ permission_view(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
configure_cmd = '/cluster/%s/configure_cmd' % self.name
return dict(use_referrer=False, page='nodes', name='configure', base_url='/cluster/' + self.name + '/configure', configure_cmd=configure_cmd)
@expose("luci.templates.configure")
def configure_cmd(self, command=None, *args, **kw):
- self.get_model()
tmpl_context.cluster_url = '/cluster/%s/configure' % self.name
+ try:
+ permission_config(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect(tmpl_context.cluster_url)
+
+ self.get_model()
if not self.model:
flash(_('Unable to contact any nodes in this cluster'),
status="error")
@@ -840,6 +992,12 @@ class IndividualClusterController(BaseController):
@expose()
def lookup(self, nodename, *args):
+ try:
+ permission_view(self.name)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+ redirect("/")
+
inc = IndividualNodeController(self.name, self.data, unquote_plus(nodename))
return inc, args
diff --git a/luci/controllers/root.py b/luci/controllers/root.py
index 6bfceb9..3892a3c 100644
--- a/luci/controllers/root.py
+++ b/luci/controllers/root.py
@@ -2,10 +2,11 @@
"""Main Controller"""
from tg import expose, flash, require, url, request, redirect, tmpl_context
-from repoze.what import predicates
+from repoze.what.predicates import not_anonymous, Any, All, NotAuthorizedError, is_user, in_any_group, in_group
from luci.lib.helpers import ugettext as _
from luci.lib.base import BaseController
+from luci.lib.cluster_permissions import permission_remove
from luci.lib.flash2 import Flash2, flash2
from luci.controllers.error import ErrorController
@@ -13,7 +14,7 @@ from luci.controllers.cluster import ClusterController
from luci.controllers.async import AsyncController
import luci.validation.validate_cluster_prop as vcp
-from luci.lib.db_helpers import db_remove_cluster
+from luci.lib.db_helpers import db_remove_cluster, grant_all_cluster_roles
__all__ = ['RootController']
@@ -72,8 +73,28 @@ class RootController(BaseController):
"""Handle the user preferences page."""
return dict(page='prefs')
+ @expose('luci.templates.admin')
+ @require(Any(is_user('root'), in_group('managers'),
+ msg=_("User must be an administrator to access this area")))
+ def admin(self, name=None):
+ """Handle the user permissions page."""
+ return dict(page='admin',name=name)
+
+ @expose('luci.templates.admin')
+ @require(Any(is_user('root'), in_group('managers'),
+ msg=_("User must be an administrator to access this area")))
+ def admin_cmd(self, name=None, **args):
+ """Handle the user permissions form submissions."""
+ ret = vcp.validate_admin(name, **args)
+ if ret[0] is False:
+ if ret[1].has_key('errors'):
+ flash(','.join(ret[1]['errors']), status="error")
+ else:
+ flash("Settings Applied")
+ return dict(page='admin',name=name)
+
@expose('luci.templates.homebase')
- @require(predicates.not_anonymous())
+ @require(not_anonymous())
def homebase(self, homebasepage='homebasepage', **args):
tmpl_context.show_sidebar = True
tmpl_context.homebase = True
@@ -82,10 +103,25 @@ class RootController(BaseController):
base_url='/cluster')
@expose('luci.templates.homebase')
- @require(predicates.not_anonymous())
+ @require(All(not_anonymous(),
+ Any(is_user('root'),
+ in_any_group('managers', 'import_cluster')),
+ msg=_("You must be an administrator or be granted access to import clusters")))
def add_existing_cmd(self, **args):
+ username = None
+ try:
+ identity = request.environ.get('repoze.who.identity')
+ username = identity['repoze.who.userid']
+ except:
+ username = None
+
vret = vcp.validate_add_existing(**args)
if vret[0] is True:
+ if username and username != 'root':
+ try:
+ grant_all_cluster_roles(username, args.get('clustername'))
+ except:
+ pass
msgs = vret[1].get('flash')
if msgs and len(msgs) > 0:
flash(', '.join(msgs))
@@ -96,15 +132,20 @@ class RootController(BaseController):
redirect('/homebase')
@expose('luci.templates.homebase')
- @require(predicates.has_permission('manage'))
+ @require(not_anonymous())
def manage_remove_cmd(self, **args):
errors = []
success = []
for k, v in args.items():
- if db_remove_cluster(k) is not True:
- errors.append(k)
- else:
- success.append(k)
+ try:
+ permission_remove(k)
+ if db_remove_cluster(k) is not True:
+ errors.append(k)
+ else:
+ success.append(k)
+ except NotAuthorizedError, e:
+ flash(e, status="warning")
+
if len(errors) != 0:
flash(_("Unable to remove cluster %s from the luci interface")
% (', '.join(errors)), status="error")
diff --git a/luci/lib/cluster_permissions.py b/luci/lib/cluster_permissions.py
new file mode 100644
index 0000000..03bfc8e
--- /dev/null
+++ b/luci/lib/cluster_permissions.py
@@ -0,0 +1,36 @@
+# Copyright (C) 2011 Red Hat, Inc.
+#
+# This program is free software; you can redistribute
+# it and/or modify it under the terms of version 2 of the
+# GNU General Public License as published by the
+# Free Software Foundation.
+
+# -*- coding: utf-8 -*-
+from tg import request
+from repoze.what.predicates import in_any_group, is_user, Any
+
+from luci.lib.helpers import ugettext as _
+
+def permission_view(name):
+ Any(is_user('root'), in_any_group('managers', 'view_%s' % name),
+ msg=_("You must be an administrator or be granted view access to this cluster")).check_authorization(request.environ)
+
+def permission_node_cmd(name):
+ Any(is_user('root'), in_any_group('managers', 'node_cmd_%s' % name),
+ msg=_("You must be an administrator or be granted access to modify cluster node state")).check_authorization(request.environ)
+
+def permission_membership(name):
+ Any(is_user('root'), in_any_group('managers', 'membership_%s' % name),
+ msg=_("You must be an administrator or be granted access to modify cluster membership")).check_authorization(request.environ)
+
+def permission_config(name):
+ Any(is_user('root'), in_any_group('managers', 'config_%s' % name),
+ msg=_("You must be an administrator or be granted access to modify cluster configuration")).check_authorization(request.environ)
+
+def permission_svc_cmd(name):
+ Any(is_user('root'), in_any_group('managers', 'service_cmd_%s' % name),
+ msg=_("You must be an administrator or be granted access to modify service group state")).check_authorization(request.environ)
+
+def permission_remove(name):
+ Any(is_user('root'), in_any_group('managers', 'remove_%s' % name),
+ msg=_("You must be an administrator or be granted access to remove clusters from the management interface")).check_authorization(request.environ)
diff --git a/luci/lib/db_helpers.py b/luci/lib/db_helpers.py
index 680a211..9bf25e2 100644
--- a/luci/lib/db_helpers.py
+++ b/luci/lib/db_helpers.py
@@ -9,13 +9,17 @@
from luci.lib.helpers import ugettext as _
from sqlalchemy.orm.exc import NoResultFound
+#from sqlalchemy.exc import InvalidRequestError
from sqlalchemy.sql.expression import or_
from datetime import datetime
from luci.model import DBSession
+import transaction
from luci.lib.cluster_status import ClusterStatus
from luci.model.objects import Node, Cluster, Task
+from luci.model.auth import Group, User
import luci.lib.luci_tasks as lt
+from luci.lib.cluster_permissions import permission_view
from luci.lib.ricci_communicator import RicciCommunicator
from luci.lib.ricci_defines import RICCI_BATCH_FAILURES_MAX, DEFAULT_RICCI_PORT
@@ -24,6 +28,8 @@ import luci.lib.ricci_queries as rq
import logging
log = logging.getLogger(__name__)
+cluster_group_roles = [ 'view', 'membership', 'config', 'remove', 'node_cmd', 'service_cmd' ]
+
def get_cluster_db_obj(cluster_name):
db_obj = None
try:
@@ -170,6 +176,11 @@ def get_cluster_list_status(delegate=None):
db_objs = get_cluster_list()
for i in db_objs:
cluster_name = i.name
+ try:
+ permission_view(cluster_name)
+ except:
+ continue
+
# Delegate, if it exists, will be an instance of
# luci.controllers.cluster.IndividualClusterController
if delegate and delegate.name == cluster_name:
@@ -195,6 +206,10 @@ def get_cluster_list_full():
for i in db_objs:
cluster_name = i.name
+ try:
+ permission_view(cluster_name)
+ except:
+ continue
rc = get_agent_for_cluster(cluster_name)
if rc is None:
cluster_list[cluster_name] = {
@@ -220,6 +235,7 @@ def create_cluster_obj(cluster_name, node_list):
port=node_port,
cluster=[db_obj])
DBSession.add(node_obj)
+ db_create_cluster_roles(db_obj)
DBSession.flush()
#transaction.commit()
except:
@@ -243,10 +259,15 @@ def db_remove_cluster(cluster_name):
DBSession.delete(i)
DBSession.delete(db_obj)
- DBSession.flush()
- #transaction.commit()
+ for role in cluster_group_roles:
+ cur_group = Group.by_group_name('%s_%s' % (role, cluster_name))
+ if cur_group:
+ DBSession.delete(cur_group)
+
+ #DBSession.flush()
+ transaction.commit()
except:
- log.exception("Error removing cluster %s")
+ log.exception("Error removing cluster %s" % cluster_name)
DBSession.rollback()
return False
return True
@@ -285,6 +306,36 @@ def db_remove_task(task_obj):
DBSession.rollback()
return False
+def db_create_cluster_roles(cluster_obj):
+ cluster_name = cluster_obj.name
+
+ action_group = Group(group_name=u'view_%s' % cluster_name,
+ display_name=u'View/Monitor %s' % cluster_name)
+ mem_group = Group(group_name=u'membership_%s' % cluster_name,
+ display_name=u'Add/Delete Nodes in %s' % cluster_name)
+ config_group = Group(group_name=u'config_%s' % cluster_name,
+ display_name=u'Edit the Configuration for %s' % cluster_name)
+ node_group = Group(group_name=u'node_cmd_%s' % cluster_name,
+ display_name=u'Start/Stop/Reboot Nodes in %s' % cluster_name)
+ service_group = Group(group_name=u'service_cmd_%s' % cluster_name,
+ display_name=u'Enable/Disable/Relocate/Migrate Service Groups for %s' % cluster_name)
+ remove_group = Group(group_name=u'remove_%s' % cluster_name,
+ display_name=u'Remove %s from the luci' % cluster_name)
+
+ try:
+ DBSession.add(action_group)
+ DBSession.add(mem_group)
+ DBSession.add(config_group)
+ DBSession.add(node_group)
+ DBSession.add(service_group)
+ DBSession.add(remove_group)
+ transaction.commit()
+ except Exception, e:
+ log.exception("Error adding group roles")
+ DBSession.rollback()
+ return False
+ return True
+
def db_add_cluster_node(cluster_obj, name, ricci_host=None, ricci_port=None):
if ricci_host is None:
ricci_host = name
@@ -313,7 +364,7 @@ def reconcile_db_with_conf(cluster_name, conf_nodelist):
# If there are pending node deletions, hold off on removing what
# appear to be stale nodes
try:
- pending_deletes = DBSession.query(Task).with_parent(cluster_obj).filter(or_(Task.task_type == lt.TASK_CLUSTER_DEL_NODE,Task.task_type == lt.TASK_CLUSTER_DELETE)).count()
+ pending_deletes = DBSession.query(Task).with_parent(cluster_obj).filter(or_(Task.task_type == lt.TASK_CLUSTER_DEL_NODE, Task.task_type == lt.TASK_CLUSTER_DELETE)).count()
if pending_deletes > 0:
return True
except:
@@ -479,3 +530,78 @@ def get_cluster_task_status(cluster_name):
log.exception('Error updating status objects')
return [block_cluster, task_status]
+
+def update_db_objects():
+ # Update the current database, if it needs updating
+ create_group = Group.by_group_name('create_cluster')
+ if not create_group:
+ create_group = Group(group_name=u'create_cluster',
+ display_name=u'create cluster role')
+ DBSession.add(create_group)
+
+ import_group = Group.by_group_name('import_cluster')
+ if not import_group:
+ import_group = Group(group_name=u'import_cluster',
+ display_name=u'import cluster role')
+ DBSession.add(import_group)
+
+ clusters = get_cluster_list()
+ for obj in clusters:
+ cur_name = obj.name
+ for role in cluster_group_roles:
+ try:
+ grole = Group.by_group_name('%s_%s' % (role, cur_name))
+ except:
+ grole = None
+ if not grole:
+ grole = Group(group_name=u'%s_%s' % (role, cur_name),
+ display_name=u'%s role for %s' % (role, cur_name))
+ DBSession.add(grole)
+ transaction.commit()
+
+def get_user_names():
+ ret = []
+ try:
+ ret = [u.user_name for u in DBSession.query(User).all()]
+ except:
+ log.exception("Getting user names")
+ return ret
+
+def get_user_roles(username):
+ db_user = User.by_user_name(username)
+ if not db_user:
+ return []
+ return [g.group_name for g in db_user.groups]
+
+def grant_all_cluster_roles(username, clustername):
+ if username == 'root':
+ return True
+
+ db_user = User.by_user_name(username)
+ if not db_user:
+ return False
+
+ new_roles = []
+ for role in cluster_group_roles:
+ try:
+ grole = Group.by_group_name('%s_%s' % (role, clustername))
+ new_roles.append(grole)
+ except:
+ log.exception("Unknown group %s_%s" % (role, clustename))
+
+ if len(new_roles) > 0:
+ try:
+ db_user.groups.extend(new_roles)
+ DBSession.flush()
+ transaction.commit()
+ return True
+ except:
+ return False
+ else:
+ return False
+
+def get_cluster_names():
+ db_objs = get_cluster_list()
+ if db_objs:
+ return [c.name for c in db_objs]
+ return []
diff --git a/luci/lib/plugin_sasl2auth.py b/luci/lib/plugin_sasl2auth.py
index 0e0158c..5143174 100644
--- a/luci/lib/plugin_sasl2auth.py
+++ b/luci/lib/plugin_sasl2auth.py
@@ -8,16 +8,18 @@
from zope.interface import implements
from repoze.who.interfaces import IAuthenticator, IMetadataProvider
-from repoze.who.utils import resolveDotted
from luci.model import DBSession
-from luci.model.auth import User
+from luci.model.auth import User, Group
import transaction
from luci import sasl2auth
+import logging
+log = logging.getLogger(__name__)
+
class Sasl2AuthPlugin(object):
- implements(IAuthenticator,IMetadataProvider)
+ implements(IAuthenticator, IMetadataProvider)
def __init__(self,
server_fqdn=None,
@@ -39,26 +41,46 @@ class Sasl2AuthPlugin(object):
self.user_realm, self.iplocalport, self.ipremoteport
):
username = identity['login']
- db_user = User.by_user_name(username)
- if not db_user:
- db_user = User(
- user_name=username,
- display_name=username,
- email_address='',
- )
- try:
- DBSession.add(db_user)
- transaction.commit()
- except Exception, e:
- DBSession.rollback()
+ try:
+ db_user = User.by_user_name(username)
+ if not db_user:
+ db_user = User(
+ user_name=username,
+ display_name=username,
+ email_address=username,
+ )
+ try:
+ DBSession.add(db_user)
+ transaction.commit()
+ except:
+ transaction.abort()
+ try:
+ DBSession.rollback()
+ except:
+ log.exception("rollback")
+ log.exception("Creating DB object for user %s" % username)
+ except:
+ log.exception("Looking up username %s" % username)
+
+
return identity['login']
return None
def add_metadata(self, environ, identity):
+ permissions = []
+ groups = []
+
username = identity.get('repoze.who.userid')
- groups = ['managers']
- permissions = ['manage']
+ try:
+ db_user = User.by_user_name(username)
+ if db_user:
+ groups = [g.group_name for g in db_user.groups]
+ except:
+ log.exception("Assembling group list for %s" % username)
+
+ if username == 'root':
+ groups.append('managers')
identity['user'] = username
identity['groups'] = groups
identity['permissions'] = permissions
@@ -70,6 +92,5 @@ class Sasl2AuthPlugin(object):
userid = identity['repoze.who.userid']
environ['repoze.what.credentials']['repoze.what.userid'] = userid
-
from repoze.what.plugins.pylonshq import booleanize_predicates
booleanize_predicates()
diff --git a/luci/model/auth.py b/luci/model/auth.py
index 4303c93..cff8f7b 100644
--- a/luci/model/auth.py
+++ b/luci/model/auth.py
@@ -79,6 +79,11 @@ class Group(DeclarativeBase):
#{ Special methods
+ @classmethod
+ def by_group_name(cls, groupname):
+ """Return the group object whose group name is ``groupname``."""
+ return DBSession.query(cls).filter(cls.group_name==groupname).first()
+
def __repr__(self):
return '<Group: name=%s>' % self.group_name
@@ -107,7 +112,7 @@ class User(DeclarativeBase):
user_name = Column(Unicode(16), unique=True, nullable=False)
- email_address = Column(Unicode(255), unique=True, nullable=False,
+ email_address = Column(Unicode(255), nullable=False,
info={'rum': {'field':'Email'}})
display_name = Column(Unicode(255))
diff --git a/luci/public/css/shared.css b/luci/public/css/shared.css
index 4e9d134..fec709c 100644
--- a/luci/public/css/shared.css
+++ b/luci/public/css/shared.css
@@ -534,3 +534,10 @@ td.svc_content {
border-left-color: gray;
*/
}
+
+ul.vanilla, li.vanilla {
+ list-style-type: none ! important;
+ list-style-image: none !important;
+ margin-left: 0 ! important;
+}
+
diff --git a/luci/templates/header.html b/luci/templates/header.html
index 568d9fb..3d33717 100644
--- a/luci/templates/header.html
+++ b/luci/templates/header.html
@@ -8,6 +8,7 @@
<li><a href="${tg.url('/about')}" class="${('', 'active')[defined('page') and page==page=='about']}">About</a></li>
<span py:if="tg.auth_stack_enabled" py:strip="True">
<py:if test="request.identity">
+ <li class="loginlogout"><a href="${tg.url('/admin')}" class="${('', 'active')[defined('page') and page==page=='admin']}">Admin</a></li>
<li class="loginlogout"><a href="${tg.url('/prefs')}" class="${('', 'active')[defined('page') and page==page=='prefs']}">Preferences</a></li>
<li id="login" class="loginlogout"><a href="${tg.url('/logout_handler')}">Logout</a></li>
</py:if>
diff --git a/luci/validation/validate_cluster_prop.py b/luci/validation/validate_cluster_prop.py
index 894511c..207cb71 100644
--- a/luci/validation/validate_cluster_prop.py
+++ b/luci/validation/validate_cluster_prop.py
@@ -19,6 +19,9 @@ from luci.lib.db_helpers import create_cluster_obj, get_node_by_name
from luci.lib.ricci_communicator import RicciCommunicator
from luci.lib.ricci_helpers import send_batch_parallel
+from luci.model import DBSession
+from luci.model.auth import User, Group
+
from validate_fence import validateFenceDevice, validateNewFenceDevice, validate_fenceinstance
from xml.dom import minidom
@@ -1187,3 +1190,39 @@ def validate_node_prop_settings_form(nodename, model, **kw):
node.delWeight()
return (len(errors) == 0, {'errors': errors, 'warnings': warnings})
+
+def validate_admin(name, **kw):
+ errors = []
+
+ user = name
+ if not user:
+ errors.append(_('No user was specified'))
+ return (False, {'errors': errors})
+
+ try:
+ user = User.by_user_name(user)
+ except:
+ log.exception('Looking up user object for %s' % user)
+ user = None
+
+ if not user:
+ errors.append(_('Unknown user: %s') % user)
+ return (False, {'errors': errors})
+
+ new_group_list = []
+ for k in kw:
+ if k[:5] == 'role:':
+ gname = k[5:]
+ try:
+ gobj = Group.by_group_name(gname)
+ except:
+ gobj = None
+ log.exception('group %s' % gname)
+ if not gobj:
+ errors.append(_('Unknown permission: %s') % gname)
+ continue
+ new_group_list.append(gobj)
+ user.groups = new_group_list
+ DBSession.flush()
+ DBSession.refresh(user)
+ return (True, {})
diff --git a/luci/validation/validate_create_cluster_form.py b/luci/validation/validate_create_cluster_form.py
index 77807ed..209a561 100644
--- a/luci/validation/validate_create_cluster_form.py
+++ b/luci/validation/validate_create_cluster_form.py
@@ -15,7 +15,7 @@ from luci.model.objects import Node, Cluster, Task
from luci.lib.ricci_helpers import send_batch_parallel, update_cluster_conf
from luci.lib.ricci_communicator import RicciCommunicator
-from luci.lib.db_helpers import get_node_by_host
+from luci.lib.db_helpers import get_node_by_host, grant_all_cluster_roles
from luci.lib.ClusterConf.ClusterNode import ClusterNode
import luci.lib.ricci_queries as rq
@@ -25,7 +25,7 @@ import luci.lib.luci_tasks as luci_tasks
import logging
log = logging.getLogger(__name__)
-def validate_create_cluster_form(self, **kw):
+def validate_create_cluster_form(self, username, **kw):
errors = []
cluster_name = kw.get('cluster_name')
@@ -176,6 +176,10 @@ def validate_create_cluster_form(self, **kw):
except:
log.exception('Error flushing DB while creating %s' % cluster_name)
+ try:
+ grant_all_cluster_roles(username, cluster_name)
+ except:
+ log.exception("grant all cluster roles")
flash(_('Creating the cluster "%s"...') % cluster_name, 'info')
redirect("/cluster/%s" % cluster_name)
diff --git a/luci/websetup.py b/luci/websetup.py
index 1d45353..484cf51 100644
--- a/luci/websetup.py
+++ b/luci/websetup.py
@@ -20,25 +20,25 @@ def setup_app(command, conf, vars):
print "Creating tables"
model.metadata.create_all(bind=config['pylons.app_globals'].sa_engine)
- manager = model.User()
- manager.user_name = u'admin'
- manager.display_name = u'Luci administrator user'
- manager.email_address = u'admin@localhost'
- manager.password = u'changeme'
-
- model.DBSession.add(manager)
-
group = model.Group()
group.group_name = u'managers'
- group.display_name = u'Managers Group'
-
- group.users.append(manager)
+ group.display_name = u'Administrators'
model.DBSession.add(group)
+ create_group = model.Group()
+ create_group.group_name = u'create_cluster'
+ create_group.display_name = u'May Create New Clusters'
+ model.DBSession.add(create_group)
+
+ import_group = model.Group()
+ import_group.group_name = u'import_cluster'
+ import_group.display_name = u'May Import Existing Clusters'
+ model.DBSession.add(import_group)
+
permission = model.Permission()
permission.permission_name = u'manage'
- permission.description = u'This permission give an administrative right to the bearer'
+ permission.description = u'This permission gives an administrative right to the bearer'
permission.groups.append(group)
model.DBSession.add(permission)
12 years, 8 months
[luci] Fix editing of VMs so that clearing a field causes the applicable attribute to be removed from the c
by Ryan McCabe
commit e52d483176f7a35a8da8ffa787a841c7606365aa
Author: Ryan McCabe <rmccabe(a)redhat.com>
Date: Fri Sep 16 01:40:05 2011 -0400
Fix editing of VMs so that clearing a field causes the applicable
attribute to be removed from the cluster.conf
luci/lib/ClusterConf/Vm.py | 17 +++++++++++++++++
luci/validation/validate_resource.py | 13 +++++++++++--
2 files changed, 28 insertions(+), 2 deletions(-)
---
diff --git a/luci/lib/ClusterConf/Vm.py b/luci/lib/ClusterConf/Vm.py
index 62009a8..17e4600 100644
--- a/luci/lib/ClusterConf/Vm.py
+++ b/luci/lib/ClusterConf/Vm.py
@@ -10,7 +10,24 @@ from BaseResource import BaseResource
TAG_NAME = "vm"
+vm_attributes = ('migration_mapping', 'xmlfile', 'migrate',
+ 'path', 'snapshot', 'hypervisor_uri',
+ 'migration_uri', 'status_program')
+
class Vm(Service, BaseResource):
def __init__(self):
Service.__init__(self)
self.TAG_NAME = TAG_NAME
+
+ def getResourceAttributes(self):
+ attrs = self.getAttributes()
+ for k in attrs.keys():
+ # include subtree attributes, too
+ if k[:2] != '__' and not k in vm_attributes:
+ del attrs[k]
+ return attrs
+
+ def delResourceAttributes(self):
+ for i in vm_attributes:
+ self.removeAttribute(i)
+ self.delSubtreeProperties()
diff --git a/luci/validation/validate_resource.py b/luci/validation/validate_resource.py
index 9ec9812..dd3e864 100644
--- a/luci/validation/validate_resource.py
+++ b/luci/validation/validate_resource.py
@@ -539,7 +539,7 @@ def validate_clusvc_form(model, **kw):
errors.append(_('The service "%s" could not be found for editing') % service_name)
return (False, {'errors': errors})
new_service.getAttributes().update(cur_service.getAttributes())
- model.deleteService(service_name)
+ model.deleteService(old_name)
elif action == 'create':
cur_service = model.retrieveServiceByName(service_name)
if cur_service is not None:
@@ -821,9 +821,18 @@ def validate_clusvc_form(model, **kw):
if len(svc_children) != 1:
errors.append(_('VMs can have no children and cannot be children of resources'))
else:
+ # Because we're treating vm like a resource from the point of
+ # view of the user, we need to use the service level attributes
+ # from "new_service" and the resource level attributes from its
+ # first child
new_vm = Vm()
new_vm.getAttributes().update(new_service.getAttributes())
- new_vm.getAttributes().update(svc_children[0].getAttributes())
+ new_vm.delResourceAttributes()
+
+ form_vm = Vm()
+ form_vm.getAttributes().update(svc_children[0].getAttributes())
+
+ new_vm.getAttributes().update(form_vm.getResourceAttributes())
model.resourcemanager_ptr.addChild(new_vm)
else:
model.resourcemanager_ptr.addChild(new_service)
12 years, 8 months