r4962 - trunk/cumin/python/cumin/inventory
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-12 19:49:40 +0000 (Mon, 12 Sep 2011)
New Revision: 4962
Modified:
trunk/cumin/python/cumin/inventory/system.py
Log:
First whack at adding wallaby node information (tags and last checkin time) to the inventory page.
Modified: trunk/cumin/python/cumin/inventory/system.py
===================================================================
--- trunk/cumin/python/cumin/inventory/system.py 2011-09-12 19:26:35 UTC (rev 4961)
+++ trunk/cumin/python/cumin/inventory/system.py 2011-09-12 19:49:40 UTC (rev 4962)
@@ -1,13 +1,18 @@
from cumin.grid.slot import SlotMap, SlotMapPage
-from cumin.objectselector import ObjectSelector, ObjectLinkColumn, ObjectTable
+from cumin.objectselector import ObjectSelector, ObjectLinkColumn, ObjectTable, ObjectTableColumn
from cumin.objectframe import ObjectFrame
from cumin.stat import StatSet, StatFlashChart
-from cumin.formats import fmt_bytes
+from cumin.formats import fmt_bytes, fmt_datetime
from cumin.model import CuminStatistic
from cumin.parameters import RosemaryObjectParameter
+from cumin.sqladapter import ObjectSqlAdapter
+from cumin.util import *
+from sage.wallaby.wallabyoperations import WallabyOperations, WBTypes
+
from wooly import Session, Widget
+from wooly.datatable import *
from wooly.util import StringCatalog
strings = StringCatalog(__file__)
@@ -25,6 +30,8 @@
super(SystemSelector, self).__init__(app, name, cls)
+ self.table.adapter = WallabyAndSqlAdapter(app, cls)
+
frame = "main.inventory.system"
col = ObjectLinkColumn(app, "name", cls.nodeName, cls._id, frame)
self.add_column(col)
@@ -34,9 +41,15 @@
self.add_attribute_column(cls.machine)
self.add_attribute_column(cls.memFree)
self.add_attribute_column(cls.loadAverage1Min)
+
+ col = SystemTagsColumn(app, "Tags", app.model.com_redhat_cumin_grid.Node.Tags, "Tags")
+ self.add_column(col)
+ col = SystemCheckinColumn(app, "Checkin", app.model.com_redhat_cumin_grid.Node.Tags, "Checkin")
+ self.add_column(col)
+
self.enable_csv_export()
-
+
class SystemFrame(ObjectFrame):
def __init__(self, app, name):
cls = app.model.com_redhat_sesame.Sysimage
@@ -163,3 +176,106 @@
sysimage = self.sysimage.get(session)
self.slots.add_where_expr(session, "\"System\" = '%s'", sysimage.nodeName)
+
+class WallabyAndSqlAdapter(ObjectSqlAdapter):
+ def get_data(self, values, options):
+ #first, fetch all the sql data
+ sqldata = super(WallabyAndSqlAdapter, self).get_data(values, options)
+
+ #now get the wallaby data
+ wallaby_nodes = self.app.wallaby.get_data(WBTypes.NODES)
+
+ #now merge them
+ for i, node in enumerate(wallaby_nodes):
+ match_index = [i for i, y in enumerate(sqldata) if y[1] == node.name]
+ if len(match_index) > 0:
+ ## merge-in the wallaby data to the matched node entry in sqldata
+ new_record = list(sqldata[match_index[0]])
+ new_record.append(self.app.wallaby.get_tag_names(node))
+ new_record.append(node.last_checkin)
+ sqldata[match_index[0]] = tuple(new_record)
+
+ return sqldata
+
+
+class DerivedTableColumn(ObjectTableColumn):
+ def __init__(self, app, name):
+ super(DerivedTableColumn, self).__init__(app, name, None)
+
+ def init(self):
+ # avoid ObjectTableColumn's init() since we are setting up our own field
+ super(ObjectTableColumn, self).init()
+
+ self.field = self.get_field()
+
+ def get_field(self):
+ raise "Not Implemented"
+
+class TagsField(DataAdapterField):
+ def __init__(self, adapter, column):
+ super(TagsField, self).__init__(adapter, column, str)
+
+ def get_content(self, session, record):
+ value = ""
+ try:
+ value = record[self.index]
+ value = ",".join(value)
+ value = truncate_text(value, 50, True)
+ except Exception, e:
+ pass
+ return value
+
+ def get_title(self, session):
+ return "Tags"
+
+
+class SystemTagsColumn(DerivedTableColumn):
+ def __init__(self, app, name, attr, title):
+ super(SystemTagsColumn, self).__init__(app, name)
+
+ self.attr = attr
+ self.title = title
+ self.format_method = getattr(attr, "unit", None)
+
+ def get_field(self):
+ field = TagsField(self.table.adapter, self.name)
+ return field
+
+ def render_text_align(self, session):
+ return "right"
+
+class CheckinField(DataAdapterField):
+ def __init__(self, adapter, column):
+ super(CheckinField, self).__init__(adapter, column, str)
+
+ def get_content(self, session, record):
+ value = ""
+ try:
+ value = record[self.index]
+ if value > 0:
+ checkin_value = datetime.fromtimestamp(float(value/1000000)) # the timestamps we get are too large
+ value = fmt_datetime(checkin_value, sec=True)
+ else:
+ value = "Never"
+ except Exception, e:
+ pass
+ return value
+
+ def get_title(self, session):
+ return "Last checkin"
+
+class SystemCheckinColumn(DerivedTableColumn):
+ def __init__(self, app, name, attr, title):
+ super(SystemCheckinColumn, self).__init__(app, name)
+
+ self.attr = attr
+ self.title = title
+ self.format_method = getattr(attr, "unit", None)
+
+ def get_field(self):
+ field = CheckinField(self.table.adapter, self.name)
+ return field
+
+ def render_text_align(self, session):
+ return "right"
+
12 years, 8 months
r4961 - trunk/cumin/python/cumin/grid
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-12 19:26:35 +0000 (Mon, 12 Sep 2011)
New Revision: 4961
Modified:
trunk/cumin/python/cumin/grid/pool.py
trunk/cumin/python/cumin/grid/tags.py
Log:
Removing the nodes radio button from the "Tags" tab. Also, renaming the "Tags" tab to "Configuration", it now houses what was the old Tags::Tags functionality, no radio button since it is the only mode right now.
Modified: trunk/cumin/python/cumin/grid/pool.py
===================================================================
--- trunk/cumin/python/cumin/grid/pool.py 2011-09-12 15:28:31 UTC (rev 4960)
+++ trunk/cumin/python/cumin/grid/pool.py 2011-09-12 19:26:35 UTC (rev 4961)
@@ -7,7 +7,7 @@
from cumin.objectframe import ObjectFrame, ObjectView
from cumin.stat import StatFlashChart, StatSet
from cumin.grid.dashboard import PoolDashboard
-from cumin.grid.tags import TagsEditor, TagsNodeEditTask, TagsFrame
+from cumin.grid.tags import TagsEditor, TagsNodeEditTask, TagsFrame, TagInventory
from submission import SubmissionFrame, PoolSubmissionJoinSelector
from slot import SlotFrame
@@ -106,7 +106,7 @@
self.view.add_tab(self.limits)
self.edit_node_tags = TagsNodeEditTask(app, self)
- config_editor = TagsEditor(app, "wallaby")
+ config_editor = TagInventory(app, "tagi")
self.view.add_tab(config_editor)
self.top_tab = True
Modified: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py 2011-09-12 15:28:31 UTC (rev 4960)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-09-12 19:26:35 UTC (rev 4961)
@@ -180,7 +180,7 @@
RemoveNodeTags(app, self, "removetags")
def render_title(self, session):
- return "Tags"
+ return "Configuration"
class TagColumn(ObjectTableColumn):
def render_cell_content(self, session, data):
@@ -1181,7 +1181,7 @@
self.add_tab(tag_inventory_tab)
def render_title(self, session):
- return "Tags"
+ return "Configuration"
def fetchTags(self, session):
12 years, 8 months
r4960 - trunk/cumin/python/cumin/grid
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-09-12 15:28:31 +0000 (Mon, 12 Sep 2011)
New Revision: 4960
Modified:
trunk/cumin/python/cumin/grid/job.py
Log:
Fix up parsing of GlobalJobId field for Aviary value (no qdate)
Modified: trunk/cumin/python/cumin/grid/job.py
===================================================================
--- trunk/cumin/python/cumin/grid/job.py 2011-09-12 14:55:17 UTC (rev 4959)
+++ trunk/cumin/python/cumin/grid/job.py 2011-09-12 15:28:31 UTC (rev 4960)
@@ -160,10 +160,12 @@
val = None
if column.name == "JobId":
# GlobalJobId should look like localhost6.localdomain6#94.1#1284091602
+ # (Coming from Aviary, the qdate won't be present
+ # (the bit after the last #)
try:
gjid = record["GlobalJobId"]
parts = gjid.split("#")
- if len(parts) == 3:
+ if len(parts) in (2,3):
val = parts[1]
except Exception:
pass
12 years, 8 months
r4959 - trunk/sage/python/sage/aviary
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-09-12 14:55:17 +0000 (Mon, 12 Sep 2011)
New Revision: 4959
Modified:
trunk/sage/python/sage/aviary/aviaryoperations.py
Log:
Remove qdate portion of GlobalJobId value, related to following BZ.
BZ732528
Modified: trunk/sage/python/sage/aviary/aviaryoperations.py
===================================================================
--- trunk/sage/python/sage/aviary/aviaryoperations.py 2011-09-09 19:34:53 UTC (rev 4958)
+++ trunk/sage/python/sage/aviary/aviaryoperations.py 2011-09-12 14:55:17 UTC (rev 4959)
@@ -430,9 +430,12 @@
j["ClusterId"] = int(cluster)
j["Cmd"] = str(job.cmd)
j["EnteredCurrentStatus"] = to_int_seconds(job.last_update)
+
+ # Note, GlobalJobId here will not match the same value from
+ # QMF because the qdate portion of the name is missing
j["GlobalJobId"] = job.id.scheduler + \
- "#" + job.id.job + \
- "#" + str(to_int_seconds(job.queued))
+ "#" + job.id.job
+
j["JobStatus"] = str(job.job_status)
j["ProcId"] = int(proc)
j["QDate"] = to_int_seconds(job.queued)
12 years, 8 months
r4958 - in trunk: cumin/python/cumin cumin/python/cumin/grid cumin/resources wooly/resources
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-09 19:34:53 +0000 (Fri, 09 Sep 2011)
New Revision: 4958
Modified:
trunk/cumin/python/cumin/grid/tags.py
trunk/cumin/python/cumin/widgets.py
trunk/cumin/python/cumin/widgets.strings
trunk/cumin/resources/app.css
trunk/wooly/resources/mootools.js
Log:
Changing the PageableSelect widget to use scrolling rather than paging.
Modified: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py 2011-09-09 17:17:26 UTC (rev 4957)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-09-09 19:34:53 UTC (rev 4958)
@@ -621,6 +621,15 @@
if num_items < self.items_per_page:
height = height - ((self.items_per_page - num_items) * row_height)
return "%dpx" % height
+
+ def render_listcontainer_height(self, session):
+ ''' scales-down the container height for lists that will never fill the max size '''
+ height = self.listcontainer_height
+ row_height = 18
+ num_items = self.get_items_count(session)
+ if num_items < self.items_per_page:
+ height = height - ((self.items_per_page - num_items) * row_height) + 25
+ return "%dpx" % height
def render_title(self, session):
return "Update tags"
@@ -745,6 +754,15 @@
height = height - ((self.items_per_page - num_items) * row_height)
return "%dpx" % height
+ def render_listcontainer_height(self, session):
+ ''' scales-down the container height for lists that will never fill the max size '''
+ height = self.listcontainer_height
+ row_height = 18
+ num_items = self.get_items_count(session)
+ if num_items < self.items_per_page:
+ height = height - ((self.items_per_page - num_items) * row_height) + 25
+ return "%dpx" % height
+
def render_title(self, session):
return "Update hosts"
Modified: trunk/cumin/python/cumin/widgets.py
===================================================================
--- trunk/cumin/python/cumin/widgets.py 2011-09-09 17:17:26 UTC (rev 4957)
+++ trunk/cumin/python/cumin/widgets.py 2011-09-09 19:34:53 UTC (rev 4958)
@@ -1583,6 +1583,7 @@
self.container_width = 250
self.width = 400
self.items_per_page = 15
+ self.listcontainer_height = 270
def render_items(self, session):
items = self.do_get_items(session)
@@ -1616,5 +1617,8 @@
def render_container_height(self, session):
return "%dpx" % self.container_height
+
+ def render_listcontainer_height(self, session):
+ return "%dpx" % self.listcontainer_height
\ No newline at end of file
Modified: trunk/cumin/python/cumin/widgets.strings
===================================================================
--- trunk/cumin/python/cumin/widgets.strings 2011-09-09 17:17:26 UTC (rev 4957)
+++ trunk/cumin/python/cumin/widgets.strings 2011-09-09 19:34:53 UTC (rev 4958)
@@ -931,7 +931,8 @@
'paginator_on_bottom':'true',
'items_per_page':{items_per_page},
'case_sensitive':false,
- 'setview':'total'});
+ 'setview':'total',
+ 'listcontainer_height':'{listcontainer_height}'});
});
});
Modified: trunk/cumin/resources/app.css
===================================================================
--- trunk/cumin/resources/app.css 2011-09-09 17:17:26 UTC (rev 4957)
+++ trunk/cumin/resources/app.css 2011-09-09 19:34:53 UTC (rev 4958)
@@ -674,6 +674,10 @@
background-color:#E8F7E8;
}
+.mtmultiselect ol {
+ background-color:#FFFFFF;
+}
+
.mtmultiselect ol .selected {
background-color:#E8F7E8;
background-image:url(resource?name=check-mark.png);
@@ -683,6 +687,7 @@
.mtmultiselect ul li {
display:inline;
+ padding:0px 15px 0px 15px;
}
.disabled {
@@ -691,9 +696,16 @@
.mtms_filterbox {
float:left;
- padding:5px;
+ padding:5px 5px 0px 5px;
}
+.listcontainer {
+ overflow: auto;
+ border: none;
+ background-color: #FFFFFF;
+ padding: 0px;
+}
+
.mtms_filterbox input {
background:#FFF;
border:1px solid #141212;
Modified: trunk/wooly/resources/mootools.js
===================================================================
--- trunk/wooly/resources/mootools.js 2011-09-09 17:17:26 UTC (rev 4957)
+++ trunk/wooly/resources/mootools.js 2011-09-09 19:34:53 UTC (rev 4958)
@@ -1570,9 +1570,7 @@
handleFilterEvent: function(list){
this.curlist = list;
- this.paginator.setpagenum(1);
- var page = this.paginator.getpage(list);
- this.displaylist.build(page);
+ this.displaylist.build(this.curlist);
},
handlePaginatorEvent: function(){
@@ -1596,17 +1594,14 @@
options.numselected = this.displaylist.numselected();
this.filterform = new FilterForm(options);
- this.paginator = new Paginator(options);
this.curlist = this.initialdata(options)
this.displaylist.addEvent('rebuild', this.handleDisplayEvent.bind(this));
this.filterform.addEvent('rebuild', this.handleFilterEvent.bind(this));
- this.paginator.addEvent('rebuild', this.handlePaginatorEvent.bind(this));
-
- var page = this.paginator.getpage(this.curlist);
+
this.filterform.build();
- this.displaylist.build(page);
+ this.displaylist.build(this.curlist);
this.setview(this.filterform, this.displaylist, options)
},
@@ -1646,7 +1641,8 @@
datasrc: null, // a multiple select dom element
view: null, // A parent or wrapper dom element where this element lives
paginator_on_bottom: true, // determines location of paginator
- represent: null
+ represent: null,
+ listcontainer_height: '200px'
},
initialize: function(options){
@@ -1656,13 +1652,18 @@
build: function(opts){
// If there's already an ol, remove it.
- var old = this.options.view.getElement('ol');
+ var old = this.options.view.getElementById('thelist');
if(old !== null) old.destroy();
+
+ container = new Element('div', {'id':'thelist',
+ 'class':'listcontainer',
+ 'style': 'height:' + this.options.listcontainer_height });
+ this.options.view.grab(container);
// create the list to hold the visible elements
list = new Element('ol');
place = this.options.paginator_on_bottom ? 'before' : 'after';
- list.inject(this.options.view.getLast(), place);
+ container.grab(list);
//this.options.view.grab(list);
opts.each(function(item){
@@ -1706,135 +1707,6 @@
});
/*
- Paginator
-*/
-
-var Paginator = new Class({
- Implements: [Options, Events],
-
- options: {
- 'items_per_page': 10,
- 'list': [], // the list the paginator will use
- 'displaylist': null // the view element for the results
- },
-
- initialize: function(options){
- this.setOptions(options);
- this.items_per_page = this.options.items_per_page;
- this.page = 1;
- // create the view element
- this.controls = new Element('div', {'class':'mtms_paginator'});
- this.options.view.grab(this.controls);
- },
-
- numpages: function(list){
- return Math.ceil(list.length / this.items_per_page );
- },
-
- getpage: function(list){
- var start = ((this.page - 1) * this.items_per_page);
- var end = start + this.items_per_page;
- this.updateControls(list);
- return list.slice(start, end);
- },
-
- pageup: function(){
- this.page++;
- },
-
- pagedown: function(){
- this.page--;
- },
-
- pagefirst: function() {
- this.page=1;
- },
-
- pagelast: function(lastpagenum) {
- this.page=lastpagenum;
- },
-
- updateControls: function(list){
-
- var numpages = this.numpages(list);
- this.controls.empty();
-
- var firstbtn = new Element('a', {'text':'First',
- 'href':'javascript:void(0)'});
- this.controls.grab(firstbtn, 'top');
- firstbtn.addClass('disabled');
-
- if(this.page > 1){
- firstbtn.removeClass('disabled');
- firstbtn.addEvent('click', function(evt){
- this.pagefirst();
- this.fireEvent('rebuild', list);
- }.bind(this));
- }
-
- var prevbtn = new Element('a', {'text':'Prev',
- 'href':'javascript:void(0)'});
- this.controls.grab(prevbtn, 'bottom');
- prevbtn.addClass('disabled');
-
- if(this.page > 1){
- prevbtn.removeClass('disabled');
- prevbtn.addEvent('click', function(evt){
- this.pagedown();
- this.fireEvent('rebuild', list);
- }.bind(this));
- }
-
- for(var i = this.page; i <= this.page + 6 && i <= numpages; i++){
- this.controls.grab(new Element('a', {'text': i,
- 'href': '',
- 'class': (i == this.page) ? 'selected' : '',
- 'events': {
- 'click': function(evt){
- this.setpagenum(evt.target.innerHTML);
- this.fireEvent('rebuild', [list]); // wrapped list
- return false;
- }.bind(this)}
- }), 'bottom');
- }
-
- var nextbtn = new Element('a', {'text':'Next',
- 'href':''});
- this.controls.grab(nextbtn);
- nextbtn.addClass('disabled');
-
- if(this.page < numpages){
- nextbtn.removeClass('disabled');
- nextbtn.addEvent('click', function(evt){
- this.pageup();
- this.fireEvent('rebuild');
- return false;
- }.bind(this));
- }
-
- var lastbtn = new Element('a', {'text':'Last',
- 'href':''});
- this.controls.grab(lastbtn);
- lastbtn.addClass('disabled');
-
- if(this.page < numpages){
- lastbtn.removeClass('disabled');
- lastbtn.addEvent('click', function(evt){
- this.pagelast(numpages);
- this.fireEvent('rebuild');
- return false;
- }.bind(this));
- }
-
-
- },
-
- setpagenum: function(pagenum){
- this.page = Number(pagenum);
- }
-});
-
-/*
FilterForm
*/
@@ -1890,6 +1762,9 @@
this.showunselected,
this.options.datasrc.getChildren().length - this.numselected);
ul.grab(this.unselectedbtn);
+
+ var br = new Element('br');
+ ul.grab(br);
this.selectFilteredBtn = this.makebtn(this.options.labels.selectedfiltered,
this.selectFiltered,
12 years, 8 months
r4957 - in trunk: cumin/bin cumin/etc cumin/python/cumin sage/python/sage sage/python/sage/aviary
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-09-09 17:17:26 +0000 (Fri, 09 Sep 2011)
New Revision: 4957
Added:
trunk/sage/python/sage/https.py
trunk/sage/python/sage/verifiedhttps.py
Removed:
trunk/sage/python/sage/aviary/https.py
Modified:
trunk/cumin/bin/cumin-web
trunk/cumin/etc/cumin.conf
trunk/cumin/python/cumin/config.py
trunk/cumin/python/cumin/main.py
trunk/sage/python/sage/aviary/aviaryoperations.py
Log:
Add server certificate validation if the ssl module is installed,
otherwise fall back on client only validation.
BZ733447
Modified: trunk/cumin/bin/cumin-web
===================================================================
--- trunk/cumin/bin/cumin-web 2011-09-08 18:44:35 UTC (rev 4956)
+++ trunk/cumin/bin/cumin-web 2011-09-09 17:17:26 UTC (rev 4957)
@@ -20,6 +20,8 @@
cumin.aviary_query_servers = values.aviary_query_servers
cumin.aviary_key = values.aviary_key
cumin.aviary_cert = values.aviary_cert
+ cumin.aviary_root_cert = values.aviary_root_cert
+ cumin.aviary_domain_verify = values.aviary_domain_verify
def set_wallaby_configs(cumin, values, brokers):
if values.wallaby_broker == "":
Modified: trunk/cumin/etc/cumin.conf
===================================================================
--- trunk/cumin/etc/cumin.conf 2011-09-08 18:44:35 UTC (rev 4956)
+++ trunk/cumin/etc/cumin.conf 2011-09-09 17:17:26 UTC (rev 4957)
@@ -16,7 +16,7 @@
# sasl-mech-list: [default, allow all available mechanisms]
# wallaby-broker: [default, first item in 'brokers' list]
# wallaby-refresh: 60
-# use-aviary: True
+# use-aviary: False
# aviary-job-servers: http://localhost:9090
# aviary-query-servers: http://localhost:9091
# log-level: info
Modified: trunk/cumin/python/cumin/config.py
===================================================================
--- trunk/cumin/python/cumin/config.py 2011-09-08 18:44:35 UTC (rev 4956)
+++ trunk/cumin/python/cumin/config.py 2011-09-09 17:17:26 UTC (rev 4957)
@@ -156,7 +156,7 @@
param.default = 60
param = ConfigParameter(self, "use-aviary", bool)
- param.default = True
+ param.default = False
param = ConfigParameter(self, "aviary-job-servers", str)
param.default = "http://localhost:9090"
@@ -170,6 +170,12 @@
param = ConfigParameter(self, "aviary-cert", str)
param.default = ""
+ param = ConfigParameter(self, "aviary-root-cert", str)
+ param.default = ""
+
+ param = ConfigParameter(self, "aviary-domain-verify", bool)
+ param.default = True
+
self.log_file = ConfigParameter(self, "log-file", str)
param = ConfigParameter(self, "log-level", str)
Modified: trunk/cumin/python/cumin/main.py
===================================================================
--- trunk/cumin/python/cumin/main.py 2011-09-08 18:44:35 UTC (rev 4956)
+++ trunk/cumin/python/cumin/main.py 2011-09-09 17:17:26 UTC (rev 4957)
@@ -89,6 +89,8 @@
self.aviary_query_servers = ""
self.aviary_key = ""
self.aviary_cert = ""
+ self.aviary_root_cert = ""
+ self.aviary_domain_verify=True
self.wallaby = None
self.wallaby_broker = None
@@ -168,6 +170,9 @@
# given op...
self.remote = Catalog()
ops = [QmfOperations("qmf", self.session)]
+
+ log.info("%s Aviary interface for job submission and control." % \
+ (self.use_aviary and "Enabling" or "Disabling"))
if self.use_aviary:
from sage.aviary.aviaryoperations import AviaryOperations
aviary_dir = os.path.join(self.home, "rpc-defs/aviary")
@@ -175,7 +180,9 @@
ops.insert(0, AviaryOperations("aviary", aviary_dir,
self.aviary_job_servers,
self.aviary_query_servers,
- self.aviary_key, self.aviary_cert))
+ self.aviary_key, self.aviary_cert,
+ self.aviary_root_cert,
+ self.aviary_domain_verify))
self.remote.add_mechanisms(ops)
# Create RPC interface for Wallaby
Modified: trunk/sage/python/sage/aviary/aviaryoperations.py
===================================================================
--- trunk/sage/python/sage/aviary/aviaryoperations.py 2011-09-08 18:44:35 UTC (rev 4956)
+++ trunk/sage/python/sage/aviary/aviaryoperations.py 2011-09-09 17:17:26 UTC (rev 4957)
@@ -6,14 +6,16 @@
import socket
import string
import time
+import sage
from suds import *
from suds.client import Client
from suds.transport.https import HttpAuthenticated
from sage.util import CallSync, CallThread, ObjectPool, host_list
+from sage.https import *
from datetime import datetime
-from sage.aviary.https import *
+
log = logging.getLogger("sage.aviary")
#f = open("./suds.client.log", 'a+')
@@ -59,7 +61,7 @@
class AviaryOperations(object):
def __init__(self, name, datadir, job_servers, query_servers,
- key="", cert=""):
+ key="", cert="", root_cert="", domain_verify=True):
self.name = name
self.datadir = datadir
@@ -92,7 +94,25 @@
self.key = key
self.cert = cert
+ self.root_cert = root_cert
+ self.domain_verify = domain_verify
+ self.server_validation_possible = hasattr(sage.https,
+ "HTTPSFullCertTransport")
+ if self.root_cert == "":
+ log.info("AviaryOperations: no root certificate file specified, "\
+ "using client validation only for ssl connections.")
+ elif not self.server_validation_possible:
+ log.info("AviaryOperations: server certificate validation not "\
+ "supported (no ssl module?), using client validation "\
+ "only for ssl connections.")
+ else:
+ log.info("AviaryOperations: using client and server "\
+ "certificate validation for ssl connections.")
+
+ log.info("AviaryOperations: verify server domain against "\
+ "certificate during validation (%s)" % self.domain_verify)
+
# job server operations
def set_job_attribute(self, scheduler, job_id, name, value, callback, submission):
@@ -508,10 +528,21 @@
# we have to always reset the transport here.
if scheme == "https":
if not os.path.isfile(self.key):
- raise Exception("File not found for aviary-key, check cumin.conf settings")
+ raise Exception("Private key file"\
+ "for ssl communication with Aviary not found")
if not os.path.isfile(self.cert):
- raise Exception("File not found for aviary-cert, check cumin.conf settings")
- the_transport = HTTPSClientCertTransport(self.key, self.cert)
+ raise Exception("Client certificate file"\
+ "for ssl communication with Aviary not found")
+ if self.root_cert != "" and self.server_validation_possible:
+ if not os.path.isfile(self.root_cert):
+ raise Exception("Root certificate file"\
+ "for Aviary server validation not found")
+ the_transport = HTTPSFullCertTransport(self.key,
+ self.cert,
+ self.root_cert,
+ self.domain_verify)
+ else:
+ the_transport = HTTPSClientCertTransport(self.key, self.cert)
else:
# this is the default transport when none is specified
the_transport = HttpAuthenticated()
Deleted: trunk/sage/python/sage/aviary/https.py
===================================================================
--- trunk/sage/python/sage/aviary/https.py 2011-09-08 18:44:35 UTC (rev 4956)
+++ trunk/sage/python/sage/aviary/https.py 2011-09-09 17:17:26 UTC (rev 4957)
@@ -1,60 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright 2009-2011 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# uses Suds - https://fedorahosted.org/suds/
-import urllib2 as u2
-from suds.transport.http import HttpTransport, Reply, TransportError
-import httplib
-import socket
-
-class HTTPSClientAuthHandler(u2.HTTPSHandler):
- def __init__(self, key, cert):
- u2.HTTPSHandler.__init__(self)
- self.key = key
- self.cert = cert
-
- def https_open(self, req):
- #Rather than pass in a reference to a connection class, we pass in
- # a reference to a function which, for all intents and purposes,
- # will behave as a constructor
- return self.do_open(self.getConnection, req)
-
- def getConnection(self, host, timeout=300):
- return httplib.HTTPSConnection(host, key_file=self.key, cert_file=self.cert)
-
-class HTTPSClientCertTransport(HttpTransport):
- def __init__(self, key, cert, *args, **kwargs):
- HttpTransport.__init__(self, *args, **kwargs)
- self.key = key
- self.cert = cert
-
- def u2open(self, u2request):
- """
- Open a connection.
- @param u2request: A urllib2 request.
- @type u2request: urllib2.Request.
- @return: The opened file-like urllib2 object.
- @rtype: fp
- """
- tm = self.options.timeout
- url = u2.build_opener(HTTPSClientAuthHandler(self.key, self.cert))
- if self.u2ver() < 2.6:
- socket.setdefaulttimeout(tm)
- return url.open(u2request)
- else:
- return url.open(u2request, timeout=tm)
Copied: trunk/sage/python/sage/https.py (from rev 4953, trunk/sage/python/sage/aviary/https.py)
===================================================================
--- trunk/sage/python/sage/https.py (rev 0)
+++ trunk/sage/python/sage/https.py 2011-09-09 17:17:26 UTC (rev 4957)
@@ -0,0 +1,152 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009-2011 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# uses Suds - https://fedorahosted.org/suds/
+import urllib2 as u2
+from suds.transport.http import HttpTransport, Reply, TransportError
+import httplib
+import socket
+
+class HTTPSClientAuthHandler(u2.HTTPSHandler):
+ def __init__(self, key, cert):
+ """
+ @param key: full path for the client's private key file
+ @param cert: full path for the client's PEM certificate file
+ """
+ u2.HTTPSHandler.__init__(self)
+ self.key = key
+ self.cert = cert
+
+ def https_open(self, req):
+ """
+ Override https_open() in the HTTPSHandler class.
+
+ The inherited method does not set private key and certificate values on
+ the HTTPSConnection object.
+ """
+ # Rather than pass in a reference to a connection class, we pass in
+ # a reference to a function which, for all intents and purposes,
+ # will behave as a constructor
+ return self.do_open(self._get_connection, req)
+
+ def _get_connection(self, host, timeout=300):
+ """
+ @return: an HTTPSConnection object constructed with private key and
+ certificate values supplied.
+ @rtype: HTTPSConnection
+ """
+ return httplib.HTTPSConnection(host, key_file=self.key, cert_file=self.cert)
+
+class HTTPSClientCertTransport(HttpTransport):
+ def __init__(self, key, cert, *args, **kwargs):
+ """
+ @param key: full path for the client's private key file
+ @param cert: full path for the client's PEM certificate file
+ """
+ HttpTransport.__init__(self, *args, **kwargs)
+ self.key = key
+ self.cert = cert
+
+ def u2open(self, u2request):
+ """
+ Open an ssl connection with client certificate validation.
+
+ @param u2request: A urllib2 request.
+ @type u2request: urllib2.Request.
+ @return: The opened file-like urllib2 object.
+ @rtype: fp
+ """
+ tm = self.options.timeout
+ url = u2.build_opener(self._get_auth_handler())
+ if self.u2ver() < 2.6:
+ socket.setdefaulttimeout(tm)
+ return url.open(u2request)
+ else:
+ return url.open(u2request, timeout=tm)
+
+ def _get_auth_handler(self):
+ return HTTPSClientAuthHandler(self.key, self.cert)
+
+try:
+ # If verifiedhttps dependencies can be satisfied,
+ # this import will succeed and the following two classes will
+ # be available to provide server certificate validation.
+ # A module can check for the presence of these two classes
+ # after import thusly (with "whatever" enclosing module
+ # if necessary):
+ # import https
+ # if hasattr(<whatever.>https, "HTTPSFullAuthHandler"):
+ # ...
+ # if hasattr(<whatever.>https, "HTTPSFullCertTransport"):
+ # ...
+ from sage.verifiedhttps import VerifiedHTTPSConnection
+
+ class HTTPSFullAuthHandler(HTTPSClientAuthHandler):
+ """
+ Add server certificate validation to HTTPSClientAuthHandler
+ via a different connection type (VerifiedHTTPSConnection).
+ """
+ def __init__(self, my_key, my_cert, root_cert, domain_verify):
+ """
+ @param my_key: full path for the client's private key file
+ @param my_cert: full path for the client's PEM certificate file
+ @param root_cert: full path for root certificates file used to
+ verify server certificates on connection
+ @param domain_verify: check server host against the 'commonName'
+ field in the server certificate
+ """
+ HTTPSClientAuthHandler.__init__(self, my_key, my_cert)
+ self.root_cert = root_cert
+ self.domain_verify = domain_verify
+
+ def _get_connection(self, host, timeout=300):
+ """
+ @return: A connection object derived from httplib types with
+ with client and server certificate validation support
+ @rtype: VerifiedHTTPSConnection
+ """
+ return VerifiedHTTPSConnection(host,
+ key_file=self.key,
+ cert_file=self.cert,
+ root_cert=self.root_cert,
+ domain_verify=self.domain_verify)
+
+ class HTTPSFullCertTransport(HTTPSClientCertTransport):
+ """
+ Add server certificate validation to HTTPSClientCertTransport
+ via a different handler type (HTTPSFullAuthHandler)
+ """
+ def __init__(self, key, cert, root_cert, domain_verify=True,
+ *args, **kwargs):
+ """
+ @param key: full path for the client's private key file
+ @param cert: full path for the client's PEM certificate file
+ @param root_cert: full path for root certificates file used to
+ verify server certificates on connection
+ @param domain_verify: check server host against the 'commonName'
+ field in the server certificate
+ """
+ HTTPSClientCertTransport.__init__(self, key, cert, *args, **kwargs)
+ self.root_cert = root_cert
+ self.domain_verify = domain_verify
+
+ def _get_auth_handler(self):
+ return HTTPSFullAuthHandler(self.key, self.cert, self.root_cert,
+ self.domain_verify)
+except:
+ pass
Added: trunk/sage/python/sage/verifiedhttps.py
===================================================================
--- trunk/sage/python/sage/verifiedhttps.py (rev 0)
+++ trunk/sage/python/sage/verifiedhttps.py 2011-09-09 17:17:26 UTC (rev 4957)
@@ -0,0 +1,70 @@
+import httplib
+import socket
+import ssl
+
+# Note: much thanks to Joseph Turner for showing the world
+# how to extend httplib using the ssl module to implement
+# server certificate validation.
+# https://github.com/josephturnerjr/urllib2.VerifiedHTTPS
+
+class SSLVerificationError(Exception):
+ pass
+
+# subclass of HTTPSConnection to do cert verification and domain verification
+class VerifiedHTTPSConnection(httplib.HTTPSConnection):
+
+ def __init__(self, host, port=None, key_file=None, cert_file=None,
+ root_cert=None, strict=None,
+ timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
+ server_verify=True, domain_verify=True):
+ """
+ All params except those noted below are passed through to
+ the httplib.HTTPSConnection constructor. Check docs on that
+ class information.
+
+ @param root_cert: full path to root certificates file
+ @param server_verify: does server certificate verification if True
+ @param domain_verify: checks server certificate 'commonName' against host if True
+ """
+ httplib.HTTPSConnection.__init__(self, host, port, key_file, cert_file,
+ strict, timeout)
+ self.root_cert = root_cert
+ self.server_verify = server_verify
+ self.domain_verify = domain_verify
+
+ def connect(self):
+ # overrides the version in httplib so that we do certificate verification
+ try:
+ # this is a convenience function added in python 2.6
+ sock = socket.create_connection((self.host, self.port), self.timeout)
+ except AttributeError:
+ # There is no timeout attribute in earlier versions of this object.
+ # The only option available is to set a global default timeout for
+ # all socket objects.
+ socket.setdefaulttimeout(10)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect((self.host, self.port))
+
+ # This code is not available for older versions of python and seems to
+ # have no effect on establishing a verified https connection.
+ #if self._tunnel_host:
+ # self.sock = sock
+ # self._tunnel()
+
+ if self.server_verify:
+ self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
+ cert_reqs=ssl.CERT_REQUIRED,
+ ca_certs=self.root_cert)
+ if self.domain_verify:
+ cert_subject = self.sock.getpeercert()['subject']
+ cert_dict = {}
+ for c in cert_subject:
+ cert_dict.update(c)
+ cert_host = cert_dict['commonName']
+ if self.host != cert_host:
+ raise SSLVerificationError("Server certificate doesn't match domain;"\
+ " untrusted connection")
+ else:
+ self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
+
+
12 years, 8 months
r4956 - trunk/cumin/model
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-08 18:44:35 +0000 (Thu, 08 Sep 2011)
New Revision: 4956
Modified:
trunk/cumin/model/cumin.xml
Log:
Adding "NumHosts" to the Node class.
Modified: trunk/cumin/model/cumin.xml
===================================================================
--- trunk/cumin/model/cumin.xml 2011-09-08 18:29:39 UTC (rev 4955)
+++ trunk/cumin/model/cumin.xml 2011-09-08 18:44:35 UTC (rev 4956)
@@ -59,8 +59,8 @@
<property name="Tags" type="sstr"/>
<property name="Checkin" type="sstr"/>
<property name="Features" type="sstr"/>
- </class>
+ <property name="NumHosts" type="sstr"/>
+ </class>
-
</package>
</model>
12 years, 8 months
r4955 - trunk/cumin/python/cumin/grid
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-08 18:29:39 +0000 (Thu, 08 Sep 2011)
New Revision: 4955
Modified:
trunk/cumin/python/cumin/grid/tags.py
Log:
Adding a "Number of hosts" column to the tag inventory display.
Modified: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py 2011-09-08 15:45:52 UTC (rev 4954)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-09-08 18:29:39 UTC (rev 4955)
@@ -88,7 +88,7 @@
wallaby_features = self.app.wallaby.get_data(WBTypes.FEATURES)
for i, tag in enumerate(wallaby_tags):
- data.append({'Tag':str(tag.name), 'Nodes':self.app.wallaby.get_node_names(tag), 'Features':tag.features})
+ data.append({'Tag':str(tag.name), 'Features':tag.features, 'NumHosts':len(self.app.wallaby.get_node_names(tag)), 'Host':self.app.wallaby.get_node_names(tag)} )
except Exception, e:
log.exception(e)
@@ -102,7 +102,7 @@
if results:
for result in results:
#Tag goes in the 0 and 1 column, 0 for checkbox value, 1 for the column value
- records.append([result['Tag'], result['Tag'], result['Nodes'], result['Features']])
+ records.append([result['Tag'], result['Tag'], result['Features'], result['NumHosts'], result['Host']])
return records
@@ -170,8 +170,12 @@
self.add_search_filter(col)
col = self.FeatureColumn(app, "featcol", cls.Features)
- col.width = "80%"
+ col.width = "60%"
self.add_column(col)
+
+ col = self.HostCountColumn(app, "hostcount", cls.NumHosts)
+ col.width = "20%"
+ self.add_column(col)
RemoveNodeTags(app, self, "removetags")
@@ -181,7 +185,7 @@
class TagColumn(ObjectTableColumn):
def render_cell_content(self, session, data):
tags = super(TagInventory.TagColumn, self).render_cell_content(session, data)
- node_list = data[2]
+ node_list = data[4]
nodes = ""
if node_list:
for node in node_list:
@@ -200,7 +204,7 @@
'''
def render_cell_content(self, session, data):
tags = data[1]
- feature_list = data[3]
+ feature_list = data[2]
features = ""
if feature_list:
for feature in feature_list:
@@ -215,6 +219,17 @@
features = truncate_text(features, 50, True)
return fmt_link(href, features)
+ class HostCountColumn(ObjectTableColumn):
+ def render_cell_content(self, session, data):
+ tags = super(TagInventory.HostCountColumn, self).render_cell_content(session, data)
+ return tags
+
+ def render_header_content(self, session):
+ return "Number of hosts"
+
+ def render_text_align(self, session):
+ return "right"
+
class NodeInventory(ObjectSelectorNoCheckboxes):
'''
12 years, 8 months
r4954 - in trunk/cumin/python/cumin: . grid
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-08 15:45:52 +0000 (Thu, 08 Sep 2011)
New Revision: 4954
Modified:
trunk/cumin/python/cumin/grid/tags.py
trunk/cumin/python/cumin/widgets.py
Log:
Making the PageableFilteredSelect widget capable of scaling down for lists that never reach max_per_page length.
Modified: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py 2011-09-07 17:37:53 UTC (rev 4953)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-09-08 15:45:52 UTC (rev 4954)
@@ -594,6 +594,18 @@
items.append(item)
return items
+
+ def get_items_count(self, session):
+ return len(fetchTags(self, session))
+
+ def render_container_height(self, session):
+ ''' scales-down the container height for lists that will never fill the max size '''
+ height = self.container_height
+ row_height = 18
+ num_items = self.get_items_count(session)
+ if num_items < self.items_per_page:
+ height = height - ((self.items_per_page - num_items) * row_height)
+ return "%dpx" % height
def render_title(self, session):
return "Update tags"
@@ -705,6 +717,19 @@
items.append(item)
return items
+
+ def get_items_count(self, session):
+ return len(fetchNodes(self, session))
+
+ def render_container_height(self, session):
+ ''' scales-down the container height for lists that will never fill the max size '''
+ height = self.container_height
+ row_height = 18
+ num_items = self.get_items_count(session)
+ if num_items < self.items_per_page:
+ height = height - ((self.items_per_page - num_items) * row_height)
+ return "%dpx" % height
+
def render_title(self, session):
return "Update hosts"
@@ -816,6 +841,19 @@
items.append(item)
return items
+
+ def get_items_count(self, session):
+ return len(fetchFeatures(self, session))
+
+ def render_container_height(self, session):
+ ''' scales-down the container height for lists that will never fill the max size '''
+ height = self.container_height
+ row_height = 18
+ num_items = self.get_items_count(session)
+ if num_items < self.items_per_page:
+ height = height - ((self.items_per_page - num_items) * row_height)
+ return "%dpx" % height
+
def render_title(self, session):
return "Update features"
@@ -1008,10 +1046,17 @@
def do_invoke(self, session, object, invoc, tag):
tags = [x.strip() for x in tag.split(',')]
+ max_retries = 5
try:
for new_tag in tags:
- call_async(invoc.make_callback(), self.app.wallaby.create_tag, new_tag)
+ retries = 0
+ while not self.app.wallaby.create_tag(new_tag) and retries < max_retries:
+ retries += 1
+ if retries < max_retries:
+ log.info("Create tag: %s failed, retrying." % new_tag)
+ else:
+ log.error("Create tag: %s failed." % new_tag)
except Exception, e:
invoc.status = invoc.FAILED
log.exception(e)
Modified: trunk/cumin/python/cumin/widgets.py
===================================================================
--- trunk/cumin/python/cumin/widgets.py 2011-09-07 17:37:53 UTC (rev 4953)
+++ trunk/cumin/python/cumin/widgets.py 2011-09-08 15:45:52 UTC (rev 4954)
@@ -1578,7 +1578,11 @@
class PageableFilteredSelect(CheckboxItemSetField):
def __init__(self, app, name):
item_parameter = SymbolParameter(app, "iparam")
- super(PageableFilteredSelect, self).__init__(app, name, item_parameter)
+ super(PageableFilteredSelect, self).__init__(app, name, item_parameter)
+ self.container_height = 375
+ self.container_width = 250
+ self.width = 400
+ self.items_per_page = 15
def render_items(self, session):
items = self.do_get_items(session)
@@ -1599,18 +1603,18 @@
return "selected"
def render_items_per_page(self, session):
- return 15
+ return self.items_per_page
def render_title(self, session):
return "Make your selections"
def render_width(self, session):
- return "400px"
+ return "%dpx" % self.width
def render_container_width(self, session):
- return "250px"
+ return "%dpx" % self.container_width
def render_container_height(self, session):
- return "375px"
+ return "%dpx" % self.container_height
\ No newline at end of file
12 years, 8 months
r4953 - trunk/cumin/python/cumin/grid
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-07 17:37:53 +0000 (Wed, 07 Sep 2011)
New Revision: 4953
Modified:
trunk/cumin/python/cumin/grid/tags.py
Log:
Changing the delete tag functionality away from using the hybrid selector utility in favor of using the more cumin-like checkboxes on the ObjectSelector/activate with a button in the "Act on selection" area.
Modified: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py 2011-09-07 17:22:59 UTC (rev 4952)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-09-07 17:37:53 UTC (rev 4953)
@@ -71,7 +71,7 @@
filter_text = values[filter_field].replace("%","")
for row in rows:
- if filter_text.lower() in row[self.fields_by_name[filter_field].index].lower():
+ if filter_text.lower() in row[self.fields_by_name[filter_field].index].lower():
filtered.append(row)
return filtered
@@ -101,10 +101,12 @@
records = list()
if results:
for result in results:
- records.append([result['Tag'], result['Nodes'], result['Features']])
+ #Tag goes in the 0 and 1 column, 0 for checkbox value, 1 for the column value
+ records.append([result['Tag'], result['Tag'], result['Nodes'], result['Features']])
- return records
+ return records
+
class NodesAdapter(ResponseAdapter):
'''
This class is meant to adapt the wallaby response for get_data(NODES) into a format appropriate
@@ -145,7 +147,7 @@
self.set_nodes = TagsTagEditTask(app, self)
self.set_features = TagsFeatureEditTask(app, self)
-class TagInventory(ObjectSelectorNoCheckboxes):
+class TagInventory(ObjectSelector):
'''
Table that will display the list of all tags across the system.
'''
@@ -158,10 +160,6 @@
link = TaskLink(app, "tag_add", tag_add_task)
self.links.add_child(link)
- remove_node_tags_task = RemoveNodeTags(app)
- link = TaskLink(app, "tag_remove", remove_node_tags_task)
- self.links.add_child(link)
-
self.activate_config_task = ActivateConfigTask(app)
link = TaskLink(app, "actlink", self.activate_config_task)
self.links.add_child(link)
@@ -174,9 +172,8 @@
col = self.FeatureColumn(app, "featcol", cls.Features)
col.width = "80%"
self.add_column(col)
- #self.add_search_filter(col)
-
+ RemoveNodeTags(app, self, "removetags")
def render_title(self, session):
return "Tags"
@@ -184,7 +181,7 @@
class TagColumn(ObjectTableColumn):
def render_cell_content(self, session, data):
tags = super(TagInventory.TagColumn, self).render_cell_content(session, data)
- node_list = data[1]
+ node_list = data[2]
nodes = ""
if node_list:
for node in node_list:
@@ -202,8 +199,8 @@
If a tag has no features on it, <add features to this tag> will be displayed instead.
'''
def render_cell_content(self, session, data):
- tags = data[0]
- feature_list = data[2]
+ tags = data[1]
+ feature_list = data[3]
features = ""
if feature_list:
for feature in feature_list:
@@ -397,24 +394,30 @@
def render_title(self, session):
return("Tag names: ")
-class RemoveTags(ObjectTaskForm):
+class RemoveTags(ObjectSelectorTaskForm):
'''
This form is used to allow the user to pick a set of tags to be removed.
+ We use ObjectSelectorTakForm because it acts on selections from the ObjectSelector it
+ originates from.
'''
def __init__(self, app, name, task, cls):
- super(RemoveTags, self).__init__(app, name, task, cls)
+ super(RemoveTags, self).__init__(app, name, task)
self.task = task
- self.tag = self.TagsList(app, "tag")
- self.add_field(self.tag)
+ def get_selection(self, session):
+ ''' return the set of checkboxes from the ObjectSelector that were set in self.ids '''
+ ids = self.ids.get(session)
+ selection = list(ids)
+ self.selection.set(session, selection)
+ return selection
+
def process_submit(self, session):
- tag_to_kill = self.tag.get(session)
+ tags_to_kill = self.selection.get(session)
- self.tag.validate(session)
if not self.errors.get(session):
- self.task.invoke(session, None, tag_to_kill)
+ self.task.invoke(session, tags_to_kill)
url = self.return_url.get(session)
self.page.redirect.set(session, url)
@@ -423,49 +426,9 @@
return self.tag.input.path
def render_title(self, session):
- return "Remove tags"
+ return "Delete tags"
- class TagNamesField(StringField):
- def render_title(self, session):
- return("Tag names: ")
-
- class TagsList(PageableFilteredSelect):
- '''
- This class takes care of displaying the tags for the given node in a filterable select box
- that is pageable.
- '''
- def __init__(self, app, name):
- super(RemoveTags.TagsList, self).__init__(app, name)
-
- def render_items(self, session):
- items = self.do_get_items(session)
- tags_string = ""
-
- for i, tag in enumerate(items):
- tags_string = tags_string + "<option id='" + str(i) + "' name=\"" + tag.title + "\" value=\"" + tag.title + "\"" + ">" + tag.title + "</option>"
-
- return tags_string
-
- def do_get_items(self, session):
- '''
- Returns the tags returned from the wallaby call formatted as checkboxitems
- (which are used as select options in this case)
- '''
- tags = fetchTags(self, session)
- items = list()
-
- if tags:
- for tag in tags:
- item = CheckboxItem(tag)
- item.title = tag
- items.append(item)
-
- return items
-
- def render_title(self, session):
- return "Select tags to delete"
-
-
+
class TagToNodes(ObjectTaskForm):
'''
This form is designed to allow many nodes to be assigned to many tags at the same time.
@@ -1081,8 +1044,6 @@
if tag not in updated_tags:
updated_tags.append(tag)
call_async(invoc.make_callback(), self.app.wallaby.edit_tags, node, *updated_tags)
-
- #self.app.wallaby.edit_tags(node, *updated_tags)
except Exception, e:
invoc.status = invoc.FAILED
@@ -1090,21 +1051,31 @@
invoc.end()
-class RemoveNodeTags(Task):
+class RemoveNodeTags(ObjectSelectorTask):
'''
This is the task that is invoked to trigger the deletion of a given tag.
It will call WallabyOperations.remove_tag, which in turn will remove the tag from
all nodes it is currently associated with and then will remove the tag from wallaby entirely.
'''
- def __init__(self, app):
- super(RemoveNodeTags, self).__init__(app)
+ def __init__(self, app, selector, name):
+ super(RemoveNodeTags, self).__init__(app, selector)
cls = app.model.com_redhat_cumin_grid.Node
self.form = RemoveTags(app, self.name, self, cls)
- def get_title(self, session, object):
+ def get_title(self, session):
return "Delete tags"
- def do_invoke(self, session, object, invoc, tags):
+ def invoke(self, session, selection):
+ invoc = self.start(session, selection)
+
+ try:
+ self.do_invoke(invoc, selection)
+ except Exception, e:
+ invoc.exception = e
+ invoc.status = invoc.FAILED
+ invoc.end()
+
+ def do_invoke(self, invoc, tags):
try:
for tag_to_kill in tags:
call_async(invoc.make_callback(), self.app.wallaby.remove_tag, tag_to_kill)
@@ -1113,6 +1084,9 @@
log.exception(e)
invoc.end()
+
+ def get_item_content(self, session, item):
+ return item
class TagsEditor(RadioModeSet):
12 years, 8 months