r4972 - trunk/cumin/python/cumin/grid
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-15 20:33:19 +0000 (Thu, 15 Sep 2011)
New Revision: 4972
Modified:
trunk/cumin/python/cumin/grid/tags.strings
Log:
swapping the hosts/features properties. Also added a bit of formatting for consistency with the rest of the interface.
Modified: trunk/cumin/python/cumin/grid/tags.strings
===================================================================
--- trunk/cumin/python/cumin/grid/tags.strings 2011-09-15 18:15:41 UTC (rev 4971)
+++ trunk/cumin/python/cumin/grid/tags.strings 2011-09-15 20:33:19 UTC (rev 4972)
@@ -23,7 +23,11 @@
border-top: 1px dotted #ccc;
}
-table.PropertySet thead a {
+table.PropertySet tbody th {
+ width:5%;
+}
+
+table.PropertySet thead td {
color: #666;
font-weight: normal;
font-style: italic;
@@ -31,14 +35,20 @@
[TagGeneral.html]
<table id="{id}" class="PropertySet">
+ <thead>
+ <tr>
+ <td>Property</td>
+ <td>Value</td>
+ </tr>
+ </thead>
<tbody>
<tr class="item" title="">
+ <th>{features_title}</th>
+ <td class="{class}">{features}</td>
+ </tr>
+ <tr class="item" title="">
<th>{hosts_title}</th>
<td class="{class}">{hosts}</td>
</tr>
- <tr class="item" title="">
- <th>{features_title}</th>
- <td class="{class}">{features}</td>
- </tr>
</tbody>
</table>
12 years, 8 months
r4971 - in trunk/cumin: bin python/cumin python/cumin/account
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-09-15 18:15:41 +0000 (Thu, 15 Sep 2011)
New Revision: 4971
Added:
trunk/cumin/python/cumin/authenticator.py
Modified:
trunk/cumin/bin/cumin-admin
trunk/cumin/bin/cumin-web
trunk/cumin/python/cumin/account/widgets.py
trunk/cumin/python/cumin/admin.py
trunk/cumin/python/cumin/config.py
trunk/cumin/python/cumin/main.py
Log:
Add support for authentication of users through external mechanisms. The solution is extensible, currently support is available for authentication through LDAP servers and external scripts.
Much thanks to Vladimir Motoska and the folks at SORS for submitting the original patch.
BZ737979
Modified: trunk/cumin/bin/cumin-admin
===================================================================
--- trunk/cumin/bin/cumin-admin 2011-09-15 14:12:37 UTC (rev 4970)
+++ trunk/cumin/bin/cumin-admin 2011-09-15 18:15:41 UTC (rev 4971)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
import os
import sys
@@ -63,8 +63,9 @@
sys.exit(1)
broker_uris = [x.strip() for x in opts.brokers.split(",")]
+ authmech = [x.strip() for x in values.common.auth.split(";")]
- app = Cumin(config.get_home(), broker_uris, opts.database)
+ app = Cumin(config.get_home(), broker_uris, opts.database, authmech=authmech)
app.check()
app.init()
@@ -95,6 +96,8 @@
lines.append("")
lines.append(" add-user USER [PASSWORD] Add USER")
lines.append(" external-user USER Add USER with external authentication")
+ lines.append(" external-sync AUTH [VERBOSE] Batch import users with external authentication")
+ lines.append(" AUTH is the name of a mechanism like 'ldap' or 'script'")
lines.append(" remove-user USER Remove USER")
lines.append(" add-assignment USER ROLE Add USER to ROLE")
lines.append(" remove-assignment USER ROLE Remove USER from ROLE")
@@ -103,6 +106,8 @@
lines.append(" list-roles List roles")
lines.append(" export-users FILE Export user list to file")
lines.append(" import-users FILE Import user list from file")
+ lines.append(" expunge-users Remove all users. Use with caution")
+ lines.append(" You may want to run export-users first")
lines.append("")
lines.append("Schema commands:")
lines.append("")
@@ -113,6 +118,22 @@
return "\n".join(lines)
+def confirm(prompt, resp=False):
+ if resp:
+ prompt = '%s [%s]|%s: ' % (prompt, 'yes', 'no')
+ else:
+ prompt = '%s [%s]|%s: ' % (prompt, 'no', 'yes')
+
+ while True:
+ ans = raw_input(prompt)
+ if not ans:
+ return resp
+ ans = ans.lower()
+ if ans not in ['y', 'n', 'yes', 'no']:
+ print 'Please enter yes or no.'
+ continue
+ return ans in ('y', 'yes')
+
def handle_print_schema(app, cursor, opts, args):
print app.admin.get_schema(),
@@ -196,26 +217,67 @@
user = app.admin.add_user(cursor, name, "")
except IntegrityError:
error("A user called '%s' already exists" % name)
+
app.admin.add_assignment(cursor, user, role)
print "External user '%s' is added" % name
+def handle_external_sync(app, cursor, opts, args):
+ try:
+ authenticatorname = args[0]
+ except IndexError:
+ error("AUTHENTICATOR name is required")
+
+ output_on = False
+ if len(args) > 1:
+ output_on = args[1].lower() in ("t", "true", "1")
+
+ users, conflicts, errors = \
+ app.authenticator.batch_import(authenticatorname, output_on)
+
+ print("Successfully imported %s users, %s conflicts" % (users, conflicts))
+ if errors:
+ print("Errors reported, check logs for details.")
+
+def handle_expunge_users(app, cursor, opts, args):
+
+ print("This will remove all users from Cumin's database.")
+ print("Hint: you may want to backup Cumin's user database "\
+ "with export-users first.\n")
+ try:
+ go = confirm( "Are you sure you want to do this?", resp=False)
+ except KeyboardInterrupt:
+ go = False
+
+ if go:
+ cls = app.model.com_redhat_cumin.User
+ cls.delete_selection(cursor)
+ print("\nAll users removed.")
+ else:
+ print("\nNo users removed.")
+
+
def handle_add_user(app, cursor, opts, args):
try:
name = args[0]
except IndexError:
error("USER is required")
+ # Don't make the user go through the pain of
+ # providing a password at the prompt....
+ if app.admin.get_user(cursor, name):
+ error("A user called '%s' already exists" % name)
+
try:
password = args[1]
except IndexError:
password = prompt_password()
crypted = crypt_password(password)
-
role = app.admin.get_role(cursor, "user")
try:
user = app.admin.add_user(cursor, name, crypted)
except IntegrityError:
error("A user called '%s' already exists" % name)
+
app.admin.add_assignment(cursor, user, role)
print "User '%s' is added" % name
@@ -371,10 +433,11 @@
if not user:
error("User '%s' is not found" % user_name)
-
+ if user.password == "":
+ error("User '%s' is an external user, password cannot be changed "\
+ "through this mechanism" % user_name)
user.password = crypt_password(prompt_password())
user.save(cursor)
-
print "Password of user '%s' is changed" % user.name
def handle_load_demo_data(app, cursor, opts, args):
Modified: trunk/cumin/bin/cumin-web
===================================================================
--- trunk/cumin/bin/cumin-web 2011-09-15 14:12:37 UTC (rev 4970)
+++ trunk/cumin/bin/cumin-web 2011-09-15 18:15:41 UTC (rev 4971)
@@ -103,8 +103,9 @@
values.log_max_archives)
broker_uris = [x.strip() for x in opts.brokers.split(",")]
+ authmech = [x.strip() for x in values.auth.split(";")]
cumin = Cumin(config.get_home(), broker_uris, opts.database,
- opts.host, opts.port, values.persona)
+ opts.host, opts.port, values.persona, authmech)
if type(values.sasl_mech_list) == str:
cumin.sasl_mech_list = values.sasl_mech_list.upper()
Modified: trunk/cumin/python/cumin/account/widgets.py
===================================================================
--- trunk/cumin/python/cumin/account/widgets.py 2011-09-15 14:12:37 UTC (rev 4970)
+++ trunk/cumin/python/cumin/account/widgets.py 2011-09-15 18:15:41 UTC (rev 4971)
@@ -61,7 +61,6 @@
def render_content(self, sessino):
return "Change password"
-
class LoginPage(HtmlPage):
def __init__(self, app, name):
super(LoginPage, self).__init__(app, name)
@@ -128,14 +127,11 @@
self.login_invalid.set(session, True)
return
- crypted = user.password
-
- if crypted and crypt(password, crypted) == crypted:
+ authenticated = self.app.authenticator.authenticate(name,password)
+ if authenticated:
# You're in!
-
login = LoginSession(self.app, user)
session.client_session.attributes["login_session"] = login
-
url = self.page.origin.get(session)
self.page.redirect.set(session, url)
@@ -178,53 +174,51 @@
self.add_field(self.new1)
def validate(self, session):
- super(ChangePasswordForm, self).validate(session)
-
- current = self.current.get(session)
- new0 = self.new0.get(session)
- new1 = self.new1.get(session)
-
user = session.client_session.attributes["login_session"].user
-
# In case a different login session for this user has made
# changes, refresh the user object
-
user.load(session.cursor)
+
+ new0 = self.new0.get(session)
+ new1 = self.new1.get(session)
- crypted = user.password
-
- if crypt_password(current, crypted) != crypted:
- error = FormError("The password is incorrect")
- self.errors.add(session, error)
-
if new0 != new1:
error = FormError("The new passwords do not match")
self.errors.add(session, error)
+ return
+ super(ChangePasswordForm, self).validate(session)
+
+ # authenticator.update_password does authentication
+ # before allowing the password change, no need to authenticate here
+
def process_submit(self, session):
self.validate(session)
if not self.errors.get(session):
- password = self.new0.get(session)
-
- conn = self.app.database.get_connection()
-
+ newpassword = self.new0.get(session)
+ oldpassword = self.current.get(session)
+ user = session.client_session.attributes["login_session"].user
try:
- cursor = conn.cursor()
+ status, message = self.app.authenticator.update_password(user.name,
+ oldpassword,
+ newpassword)
+ if not status:
+ if not message:
+ message = "Update failed."
+ error = FormError(message)
+ self.errors.add(session, error)
- user = session.client_session.attributes["login_session"].user
- user.password = crypt_password(password)
- user.save(cursor)
+ except Exception,e:
+ log.debug("Error in external authenticator: %s" , e )
+ error = FormError("Password change error. Please contact your site administrator.")
+ self.errors.add(session,error)
- conn.commit()
- finally:
- conn.close()
-
+ if not self.errors.get(session):
task = ObjectTask(self.app)
invoc = task.start(session, None)
invoc.description = "Password changed"
invoc.end()
-
url = self.return_url.get(session)
self.page.redirect.set(session, url)
Modified: trunk/cumin/python/cumin/admin.py
===================================================================
--- trunk/cumin/python/cumin/admin.py 2011-09-15 14:12:37 UTC (rev 4970)
+++ trunk/cumin/python/cumin/admin.py 2011-09-15 18:15:41 UTC (rev 4971)
@@ -1,6 +1,7 @@
from StringIO import StringIO
from util import *
+from psycopg2 import IntegrityError
log = logging.getLogger("cumin.admin")
@@ -67,6 +68,12 @@
def add_user(self, cursor, name, crypted_password):
cls = self.app.model.com_redhat_cumin.User
+ # Check for existence of the user first.
+ # This will save exception traces and sql
+ # dumps in the log file.
+ if cls.get_object(cursor, name=name):
+ raise IntegrityError("Duplicate cumin user")
+
user = cls.create_object(cursor)
user.name = name
user.password = crypted_password
Added: trunk/cumin/python/cumin/authenticator.py
===================================================================
--- trunk/cumin/python/cumin/authenticator.py (rev 0)
+++ trunk/cumin/python/cumin/authenticator.py 2011-09-15 18:15:41 UTC (rev 4971)
@@ -0,0 +1,335 @@
+from util import *
+import ldap
+import ldapurl
+import subprocess
+from subprocess import PIPE
+from psycopg2 import IntegrityError
+import syslog
+
+# Send sensitive authentication logs to syslog
+# but use this for other stuff. We want to keep
+# all user and password information out of the
+# publically readable log file.
+log = logging.getLogger("cumin.authenticator")
+
+
+class _syslog(object):
+ log_auth = False
+ @classmethod
+ def log(cls, msg):
+ if _syslog.log_auth:
+ syslog.syslog(msg)
+
+class CuminAuthenticatorLDAP(object):
+ def __init__(self, url):
+ log.info("Initializing %s", self)
+ myurl = url.split("=",1)[1]
+ try:
+ self.url = ldapurl.LDAPUrl(myurl)
+ log.info("Authenticator: parsed LDAP url successfully.")
+ except Exception,e:
+ log.error("Authenticator: cannot parse LDAP url %s", e)
+
+ def __prep_con(self):
+ try:
+ conn = ldap.initialize(self.url.urlscheme + "://" +self.url.hostport)
+ except Exception,e:
+ log.error("Authenticator: cannot contact ldap server %s",e)
+ return False
+
+ if self.url.who and self.url.cred:
+ try:
+ conn.simple_bind_s(self.url.who,self.url.cred)
+ except:
+ log.error("Authenticator: cannot authenticate as service %s",
+ self.url.who)
+ return False
+ return conn
+
+ def change_pw(self,user,oldpass,newpass):
+ conn = self.__prep_con()
+ if not conn:
+ return False
+ filter = self.url.filterstr % user
+ try:
+ res = conn.search_s(self.url.dn, self.url.scope, filter)
+ except Exception, e:
+ log.error("Authenticator: update password, "\
+ "query returned exception %s", e)
+ res = []
+ if not res:
+ log.info("Authenticator: update password, "\
+ "query returned no results in %s", self.__class__.__name__)
+ else:
+ dn,ent = res[0]
+ conn.simple_bind_s(dn,oldpass)
+ conn.passwd_s(dn,oldpass,newpass)
+ log.info("Authenticator: update password succeeded in %s",
+ self.__class__.__name__)
+ _syslog.log("cumin: updated password via LDAP for user %s" % dn)
+ return True
+ return False
+
+ def batch_import(self):
+ log.debug("Authenticator: batch import in %s", self.__class__.__name__)
+ userlist = res = []
+ conn = self.__prep_con()
+ error = not conn
+ if not error:
+ filter = self.url.filterstr % '*'
+ try:
+ res = conn.search_s(self.url.dn, self.url.scope, filter)
+ except Exception, e:
+ log.error("Authenticator:, batch import, "\
+ "query returned exception %s", e)
+ error = True
+ if not res:
+ log.info("Authenticator: batch import failed, "\
+ "query returned no results in %s", self.__class__.__name__)
+ else:
+ for dn,ent in res:
+ dn_arr = ldap.dn.explode_rdn(dn)
+ naming_attr = dn_arr[0].split('=')[0]
+ userlist.append(ent[naming_attr][0])
+ return userlist, error
+
+ def authenticate(self, username, password):
+ log.debug("Authenticating against %s", self.__class__.__name__)
+ conn = self.__prep_con()
+ if not conn:
+ return False
+ filter = self.url.filterstr % username
+ try:
+ res = conn.search_s(self.url.dn, self.url.scope, filter)
+ except Exception, e:
+ log.error("Authenticator: authenticate, "\
+ "query returned exception %s", e)
+ res = []
+ if not res:
+ log.info("Authenticator: authentication failed, "\
+ "query returned no results in %s", self.__class__.__name__)
+ else:
+ for dn,ent in res:
+ try:
+ conn.simple_bind_s(dn,password)
+ _syslog.log("cumin: authenticated user %s via LDAP" % dn)
+ return True
+ except Exception, e:
+ log.info("Authenticator: LDAP authentication "\
+ "returned exception %s" % e)
+ _syslog.log("cumin: authentication via LDAP failed "\
+ "for user" % dn)
+ return False
+
+class CuminAuthenticatorScript(object):
+ def __init__(self, commandline):
+ self.params = {}
+ self.cap = []
+ me = self.__class__.__name__
+ for a in commandline.split(','):
+ param,val = a.split('=')
+ self.params[param] = val
+
+ if self.params.has_key('script') and \
+ os.access(self.params['script'], os.X_OK):
+ if self.params.has_key('auth'):
+ log.debug("Authenticator: adding auth capability to %s", me)
+ self.cap.append('a')
+ if self.params.has_key('change'):
+ log.debug("Authenticator: adding change capability to %s", me)
+ self.cap.append('c')
+ if self.params.has_key('list'):
+ log.debug("Authenticator: adding list capability to %s", me)
+ self.cap.append('l')
+ else:
+ log.error('Authenticator: script parameter missing or target file '\
+ 'not executable in %s', me)
+
+ def authenticate(self, username, password):
+ me = self.__class__.__name__
+ if 'a' in self.cap:
+ log.debug("Authenticator: executing %s in %s",
+ self.params['script'], me)
+ # Use nameless pipes to pass parameters.
+ # If they are passed as arguments they show up in the output
+ # of ps, etc. This way is secure.
+ # Note, the call to communicate causes an EOF on stdin
+ cmd = [self.params['script']]
+ res = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ out, err = res.communicate(input="%s %s %s" % \
+ (self.params['auth'], username, password))
+ log.debug("Authenticator: authenticate, script returned "\
+ "%s, %s, exitcode %s", out, err, res.returncode)
+ if res.returncode == 0:
+ _syslog.log("cumin: authenticated user %s via script" % username)
+ return True
+ _syslog.log("cumin: authentication via script failed for user %s" % username)
+ return False
+
+ def batch_import(self):
+ if 'l' in self.cap:
+ log.debug("Authenticator: batch import in %s",
+ self.__class__.__name__)
+ # Use nameless pipes to pass parameters.
+ # If they are passed as arguments they show up in the output
+ # of ps, etc. This way is secure.
+ # Note, the call to communicate causes an EOF on stdin
+ cmd = [self.params['script']]
+ res = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ out, err = res.communicate(input=self.params['list'])
+ log.debug("Authenticator: batch import, script returned "\
+ "%s, %s, exitcode %s", out, err, res.returncode)
+ return out.split(','), False
+ return [], False
+
+ def change_pw(self, user, newpass, oldpass):
+ if 'c' in self.cap:
+ me = self.__class__.__name__
+ log.debug("Authenticator: change password in %s", me)
+
+ # Use nameless pipes to pass parameters.
+ # If they are passed as arguments they show up in the output
+ # of ps, etc. This way is secure.
+ # Note, the call to communicate causes an EOF on stdin
+ cmd = [self.params['script']]
+ try:
+ res = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ out, err = res.communicate(input="%s %s %s %s" % \
+ (self.params['change'], user, newpass, oldpass))
+ log.debug("Authenticator: update password, script returned "\
+ "%s, %s, exitcode %s", out, err, res.returncode)
+ except Exception, e:
+ import traceback
+ traceback.print_exc()
+ res.returncode = -1
+
+ if res.returncode == 0:
+ _syslog.log("cumin: updated password via script for user %s" % user)
+ return True
+ return False
+
+authen_map = { 'ldap': CuminAuthenticatorLDAP,
+ 'script': CuminAuthenticatorScript }
+
+class CuminAuthenticator(object):
+ def __init__(self, app, log_authentication=True):
+ self.authenticators=[]
+ self.app = app
+ _syslog.log_auth = log_authentication
+ log.info("Initializing %s", self)
+ for authmech in app.authmech:
+ try:
+ mechname = authmech.split('=')[0]
+ log.info("Authenticator: adding auth mechanism %s", mechname)
+ classname = authen_map[mechname]
+ except KeyError:
+ log.error("Authenticator: unsupported authenticator type %s",
+ mechname)
+ continue
+ self.authenticators.append(classname(authmech))
+
+ def batch_import(self, backend, output_on=False):
+ numusers = 0
+ numconflicts = 0
+ errors = False
+ import_list = []
+ log.debug("Authenticator: calling batch import")
+ try:
+ classname = authen_map[backend]
+ except KeyError:
+ log.error("Authenticator: batch import, unsupported authenticator "\
+ "type '%s'", backend)
+ errors = True
+
+ if not errors:
+ try:
+ idx = [x.__class__.__name__ for x in self.authenticators]
+ idx = idx.index(classname.__name__)
+ except ValueError:
+ log.error("Authenticator: batch import, authenticator "\
+ "not configured '%s'", backend)
+ errors = True
+
+ if not errors:
+ users, errors = self.authenticators[idx].batch_import()
+ for imp in users:
+ import_list.append(imp)
+
+ if len(import_list) > 0:
+ try:
+ conn = self.app.database.get_connection()
+ cursor = conn.cursor()
+ role = self.app.admin.get_role(cursor, "user")
+ for importuser in import_list:
+ try:
+ user = self.app.admin.add_user(cursor, importuser, "")
+ if output_on:
+ print("Importing user %s" % importuser)
+ self.app.admin.add_assignment(cursor, user, role)
+ conn.commit()
+ numusers += 1
+ except IntegrityError:
+ if output_on:
+ print("Import failed, a user called '%s' "\
+ "already exists" % importuser)
+ numconflicts += 1
+ conn.rollback()
+ finally:
+ conn.close()
+ return numusers, numconflicts, errors
+
+ def authenticate(self, username, password):
+ log.debug("Authenticator: calling authenticate")
+ cursor = self.app.database.get_read_cursor()
+ cls = self.app.model.com_redhat_cumin.User
+ user = cls.get_object(cursor, name=username)
+ external = len(user.password) == 0
+ log.info("Authenticator: authenticating external user %s" % external)
+ if not external:
+ res = crypt(password, user.password) == user.password
+ if res:
+ msg = "cumin: authenticated user %s" % username
+ else:
+ msg = "cumin: authentication failed for user %s" % username
+ _syslog.log(msg)
+ return res
+ else:
+ for authenticator in self.authenticators:
+ log.debug("Authenticator: authenticate, try %s", authenticator)
+ if authenticator.authenticate(username, password):
+ return True
+ return False
+
+ def update_password(self, username, oldpassword, newpassword):
+ status = False
+ message = ""
+ log.debug("Authenticator: calling update_password")
+ conn = self.app.database.get_connection()
+ try:
+ cursor = conn.cursor()
+ cls = self.app.model.com_redhat_cumin.User
+ user = cls.get_object(cursor, name=username)
+ if user.password == "":
+ for authenticator in self.authenticators:
+ status = authenticator.authenticate(username, oldpassword)
+ if status:
+ status = authenticator.change_pw(username,
+ oldpassword, newpassword)
+ if not status:
+ message = "Could not change password"
+ break
+
+ if not status and message == "":
+ message = "The password is incorrect"
+ else:
+ status = crypt(oldpassword, user.password) == user.password
+ if status:
+ user.password = crypt_password(newpassword)
+ user.save(cursor)
+ conn.commit()
+ _syslog.log("cumin: updated password for user %s" % username)
+ else:
+ message = "The password is incorrect"
+ finally:
+ conn.close()
+ return status, message
Modified: trunk/cumin/python/cumin/config.py
===================================================================
--- trunk/cumin/python/cumin/config.py 2011-09-15 14:12:37 UTC (rev 4970)
+++ trunk/cumin/python/cumin/config.py 2011-09-15 18:15:41 UTC (rev 4971)
@@ -145,6 +145,9 @@
param = ConfigParameter(self, "brokers", str)
param.default = "amqp://localhost"
+ param = ConfigParameter(self, "auth", str)
+ param.default = 'internal'
+
# Leave default set to None, which is equivalent to
# previous behavior
param = ConfigParameter(self, "sasl-mech-list", str)
Modified: trunk/cumin/python/cumin/main.py
===================================================================
--- trunk/cumin/python/cumin/main.py 2011-09-15 14:12:37 UTC (rev 4970)
+++ trunk/cumin/python/cumin/main.py 2011-09-15 18:15:41 UTC (rev 4971)
@@ -21,6 +21,7 @@
from user import *
from util import *
from widgets import *
+from authenticator import *
from sage.catalog import Catalog
from sage.qmf.qmfoperations import QmfOperations
from sage.wallaby.wallabyoperations import WallabyOperations
@@ -33,10 +34,12 @@
class Cumin(Application):
def __init__(self, home, broker_uris, database_dsn,
- host="localhost", port=45672, persona="default"):
+ host="localhost", port=45672, persona="default",
+ authmech=["internal"]):
super(Cumin, self).__init__()
self.home = home
+ self.authmech = authmech
model_dir = os.path.join(self.home, "model")
@@ -46,6 +49,7 @@
self.database = CuminDatabase(self, database_dsn)
self.server = CuminServer(self, host, port)
self.admin = CuminAdmin(self)
+ self.authenticator = CuminAuthenticator(self)
self.add_resource_dir(os.path.join(self.home, "resources-wooly"))
self.add_resource_dir(os.path.join(self.home, "resources"))
@@ -657,6 +661,7 @@
def render_cell_content(self, session, record):
created = self.field.get_content(session, record)
+ import time
return fmt_duration(time.time() - secs(created))
def render_text_align(self, session):
12 years, 8 months
r4970 - trunk/cumin/python/cumin/inventory
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-15 14:12:37 +0000 (Thu, 15 Sep 2011)
New Revision: 4970
Modified:
trunk/cumin/python/cumin/inventory/system.py
Log:
shrinking the tags column on the inventory table.
Modified: trunk/cumin/python/cumin/inventory/system.py
===================================================================
--- trunk/cumin/python/cumin/inventory/system.py 2011-09-15 14:01:46 UTC (rev 4969)
+++ trunk/cumin/python/cumin/inventory/system.py 2011-09-15 14:12:37 UTC (rev 4970)
@@ -254,7 +254,7 @@
try:
value = record[self.index]
value = ",".join(value)
- value = truncate_text(value, 50, True)
+ value = truncate_text(value, 40, True)
except Exception, e:
pass
return value
12 years, 8 months
r4969 - trunk/cumin/python/cumin/inventory
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-15 14:01:46 +0000 (Thu, 15 Sep 2011)
New Revision: 4969
Modified:
trunk/cumin/python/cumin/inventory/system.py
trunk/cumin/python/cumin/inventory/system.strings
Log:
small tweak to the inventory::configuration view
Modified: trunk/cumin/python/cumin/inventory/system.py
===================================================================
--- trunk/cumin/python/cumin/inventory/system.py 2011-09-14 21:05:01 UTC (rev 4968)
+++ trunk/cumin/python/cumin/inventory/system.py 2011-09-15 14:01:46 UTC (rev 4969)
@@ -132,9 +132,7 @@
node_name = system.nodeName
tags = self.app.wallaby.get_tag_names(node_name)
- tags_string = ""
- for tag in tags:
- tags_string = tags_string + "<li>%s</li>" % tag
+ tags_string = ", ".join(tags)
return tags_string
Modified: trunk/cumin/python/cumin/inventory/system.strings
===================================================================
--- trunk/cumin/python/cumin/inventory/system.strings 2011-09-14 21:05:01 UTC (rev 4968)
+++ trunk/cumin/python/cumin/inventory/system.strings 2011-09-15 14:01:46 UTC (rev 4969)
@@ -100,17 +100,20 @@
//]]>
</script>
+[SystemTagSet.css]
+table.topalign td {
+ vertical-align: top;
+}
+
[SystemTagSet.html]
-<table class="{class}">
+<table class="{class} topalign">
<tbody>
<tr>
- <td>
- <h2>Tags</h2>
- </td>
- <td>
- <ul>
+ <th>
+ Tags:
+ </th>
+ <td align="left">
{tags}
- </ul>
</td>
</tr>
</tbody>
12 years, 8 months
r4968 - trunk/cumin/python/cumin/inventory
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-14 21:05:01 +0000 (Wed, 14 Sep 2011)
New Revision: 4968
Modified:
trunk/cumin/python/cumin/inventory/system.py
trunk/cumin/python/cumin/inventory/system.strings
Log:
Adding a "configuration" tab to the system view page. It will house the tagging information along with whatever other configuration-related information we may come up with.
Modified: trunk/cumin/python/cumin/inventory/system.py
===================================================================
--- trunk/cumin/python/cumin/inventory/system.py 2011-09-14 20:11:10 UTC (rev 4967)
+++ trunk/cumin/python/cumin/inventory/system.py 2011-09-14 21:05:01 UTC (rev 4968)
@@ -60,6 +60,9 @@
overview = SystemStats(app, "overview", self.object)
self.view.add_tab(overview)
+
+ configuration = SystemTags(app, "configuration", self.object)
+ self.view.add_tab(configuration)
class SystemGeneralStatSet(StatSet):
fmt_as_bytes = ("memFree", "swapFree")
@@ -116,6 +119,35 @@
return params
+class SystemTagSet(Widget):
+ def __init__(self, app, name, system):
+ super(SystemTagSet, self).__init__(app, name)
+ self.system = system
+
+ def render_title(self, session):
+ return "Tags"
+
+ def render_tags(self, session):
+ system = self.system.get(session)
+ node_name = system.nodeName
+ tags = self.app.wallaby.get_tag_names(node_name)
+
+ tags_string = ""
+ for tag in tags:
+ tags_string = tags_string + "<li>%s</li>" % tag
+
+ return tags_string
+
+class SystemTags(Widget):
+ def __init__(self, app, name, system):
+ super(SystemTags, self).__init__(app, name)
+
+ self.tag_set = SystemTagSet(app, name, system)
+ self.add_child(self.tag_set)
+
+ def render_title(self, session):
+ return "Configuration"
+
class SystemSlotMap(SlotMap):
def __init__(self, app, name, system):
super(SystemSlotMap, self).__init__(app, name)
Modified: trunk/cumin/python/cumin/inventory/system.strings
===================================================================
--- trunk/cumin/python/cumin/inventory/system.strings 2011-09-14 20:11:10 UTC (rev 4967)
+++ trunk/cumin/python/cumin/inventory/system.strings 2011-09-14 21:05:01 UTC (rev 4968)
@@ -99,3 +99,19 @@
}
//]]>
</script>
+
+[SystemTagSet.html]
+<table class="{class}">
+ <tbody>
+ <tr>
+ <td>
+ <h2>Tags</h2>
+ </td>
+ <td>
+ <ul>
+ {tags}
+ </ul>
+ </td>
+ </tr>
+ </tbody>
+</table>
\ No newline at end of file
12 years, 8 months
r4967 - trunk/cumin/python/cumin/grid
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-14 20:11:10 +0000 (Wed, 14 Sep 2011)
New Revision: 4967
Modified:
trunk/cumin/python/cumin/grid/tags.py
Log:
Fixing-up the initial values for the tag nodes/features tasks.
Modified: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py 2011-09-14 19:07:56 UTC (rev 4966)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-09-14 20:11:10 UTC (rev 4967)
@@ -940,8 +940,7 @@
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
-
+ return "%dpx" % height
def render_title(self, session):
return "Update features"
@@ -1030,8 +1029,14 @@
return "Edit hosts"
def do_enter(self, session, osession):
- self.form.tags.set(session, osession.values_by_path["main.grid.tag.id"])
- self.form.node_name.set(session, self.form.node_name.get(osession))
+ tag_id = osession.values_by_path["main.grid.tag.id"]
+ self.form.tags.set(session, tag_id)
+
+ nodes = "No nodes currently assigned"
+ node_list = self.app.wallaby.get_node_names(tag_id)
+ if len(node_list) > 0:
+ nodes = ", ".join(node_list)
+ self.form.node_name.set(session, nodes)
def do_invoke(self, invoc, negotiator, tag, chosen_nodes):
'''
@@ -1056,7 +1061,7 @@
except Exception, e:
invoc.status = invoc.FAILED
log.exception(e)
-
+
invoc.end()
class TagsFeatureEditTask(ObjectFrameTask):
@@ -1073,8 +1078,13 @@
return "Edit features"
def do_enter(self, session, osession):
- self.form.tags.set(session, osession.values_by_path["main.grid.tag.id"])
- self.form.feature_name.set(session, self.form.feature_name.get(osession))
+ tag_id = osession.values_by_path["main.grid.tag.id"]
+ self.form.tags.set(session, tag_id)
+ features = "No features currently assigned"
+ feature_list = self.app.wallaby.get_tag_by_name(tag_id).features
+ if len(feature_list) > 0:
+ features = ", ".join(feature_list)
+ self.form.feature_name.set(session, features)
def do_invoke(self, invoc, negotiator, tag, chosen_features):
'''
12 years, 8 months
r4966 - trunk/cumin/python/cumin/grid
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-14 19:07:56 +0000 (Wed, 14 Sep 2011)
New Revision: 4966
Modified:
trunk/cumin/python/cumin/grid/tags.py
trunk/cumin/python/cumin/grid/tags.strings
Log:
Removing unused TagToNodes class and strings content.
Modified: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py 2011-09-14 19:04:02 UTC (rev 4965)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-09-14 19:07:56 UTC (rev 4966)
@@ -543,53 +543,7 @@
def render_title(self, session):
return "Delete tags"
-
-
-class TagToNodes(ObjectTaskForm):
- '''
- This form is designed to allow many nodes to be assigned to many tags at the same time.
- '''
- def __init__(self, app, name, task, cls):
- super(TagToNodes, self).__init__(app, name, task, cls)
-
- self.tagbox = StringParameter(app, "tagbox")
- self.add_parameter(self.tagbox)
-
- self.nodebox = StringParameter(app, "nodebox")
- self.add_parameter(self.nodebox)
-
- def process_submit(self, session):
- nodes = self.nodebox.get(session)
- tags = self.tagbox.get(session)
-
- self.task.invoke(session, None, nodes, tags)
-
- url = self.return_url.get(session)
- self.page.redirect.set(session, url)
-
- def render_tag_list(self, session):
- items = fetchItems(self, session, WBTypes.TAGS)
- tags_string = ""
- tags = list()
-
- for i, tag in enumerate(items):
- tags.append(str(tag))
-
- return tags
-
- def render_node_list(self, session):
- items = fetchItems(self, session, WBTypes.NODES)
- nodes_string = ""
- nodes = list()
- for i, node in enumerate(items):
- nodes.append(str(node))
-
- return nodes
-
- def render_title(self, session):
- return "Apply tags to nodes"
-
class TagSelectField(FormField):
'''
This specialized FormField can be used to give the user a text box
Modified: trunk/cumin/python/cumin/grid/tags.strings
===================================================================
--- trunk/cumin/python/cumin/grid/tags.strings 2011-09-14 19:04:02 UTC (rev 4965)
+++ trunk/cumin/python/cumin/grid/tags.strings 2011-09-14 19:07:56 UTC (rev 4966)
@@ -10,81 +10,7 @@
{value}
<input type="hidden" name="{name}" id="{name}" value="{value}" tabindex="{tab_index}" size="{size}"/>
-[TagToNodes.html]
-<form id="{id}" class="ButtonForm" method="post" action="?">
- <div class="title">{title}</div>
- <div class="content">
- <div class='lfloat'>
- <div>
- Nodes
- </div>
- <div>
- <textarea id='{id}.nodebox' name='{id}.nodebox' cols='30' rows='5' size='30' onfocus="javascript:if(this.value=='') {if(node_completer)node_completer.observer.onFired();}" />
- </div>
- </div>
- <div class='rfloat'>
- <div>
- Tags
- </div>
- <div>
- <textarea id='{id}.tagbox' name='{id}.tagbox' cols='30' rows='5' size='30' onfocus="javascript:if(this.value=='') {if(tag_completer)tag_completer.observer.onFired();}" />
- </div>
- </div>
- <div style="clear:both;"> </div>
- </div>
- <div class="buttons">
- {submit}
- {cancel}
- </div>
- <div>{hidden_inputs}</div>
-</form>
-<script type="text/javascript">
-//<![CDATA[
-
- updateResults = function() {
- $('{id}.results').innerHTML = "";
- for(var i=0; i < $('{id}.tagbox').options.length; i++) {
- if ($('{id}.tagbox').options[i].selected == true) {
- $('{id}.results').innerHTML = $('{id}.results').innerHTML + $('{id}.tagbox').options[i].text + ": ";
- for(var j=0; j < $('{id}.nodebox').options.length; j++) {
- if ($('{id}.nodebox').options[j].selected == true) {
- $('{id}.results').innerHTML = $('{id}.results').innerHTML + $('{id}.nodebox').options[j].text + " ";
- }
- }
- $('{id}.results').innerHTML = $('{id}.results').innerHTML + "<br/>";
- }
- }
- }
-var node_completer = null;
-var tag_completer = null;
-document.addEvent('domready', function() {
- var tokens = {node_list};
- var tags = {tag_list};
-
- node_completer = new Autocompleter.Local('{id}.nodebox', tokens, {
- 'minLength': 1, // We need at least 1 character
- 'selectMode': 'type-ahead', // Instant completion
- 'multiple': true, // Tag support, by default comma separated
- 'overflow': true,
- 'filterSubset': true,
- 'zindex': 50000
- });
-
- tag_completer = new Autocompleter.Local('{id}.tagbox', tags, {
- 'minLength': 1, // We need at least 1 character
- 'selectMode': 'type-ahead', // Instant completion
- 'multiple': true, // Tag support, by default comma separated
- 'overflow': true,
- 'filterSubset': true,
- 'zindex': 50000
- });
-});
-
-
-//]]>
-</script>
-
[TagGeneral.css]
table.PropertySet {
width: 100%;
12 years, 8 months
r4965 - trunk/cumin/python/cumin/grid
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-14 19:04:02 +0000 (Wed, 14 Sep 2011)
New Revision: 4965
Modified:
trunk/cumin/python/cumin/grid/pool.py
trunk/cumin/python/cumin/grid/tags.py
trunk/cumin/python/cumin/grid/tags.strings
Log:
Refactoring the wallaby tag functionality as follows:
1) The Inventory main tab will now show wallaby node data, the tag list and last checkin time will be in columns on that selector. This eliminates the need for the Tags::Node path, so it has been removed
2) The Grid::Tags tab is now known as Grid::Configuration and there is currently only a Tags selector that shows the tags information. You can click on a tag, to get a view of just that tag. From there, you can edit nodes/features via the task links.
Modified: trunk/cumin/python/cumin/grid/pool.py
===================================================================
--- trunk/cumin/python/cumin/grid/pool.py 2011-09-13 13:50:04 UTC (rev 4964)
+++ trunk/cumin/python/cumin/grid/pool.py 2011-09-14 19:04:02 UTC (rev 4965)
@@ -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, TagInventory
+from cumin.grid.tags import TagsNodeEditTask, TagsFrame, TagInventory
from submission import SubmissionFrame, PoolSubmissionJoinSelector
from slot import SlotFrame
Modified: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py 2011-09-13 13:50:04 UTC (rev 4964)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-09-14 19:04:02 UTC (rev 4965)
@@ -11,15 +11,15 @@
from cumin.formats import fmt_link, fmt_datetime
from cumin.objectselector import *
-from cumin.objectframe import ObjectFrameTaskForm, ObjectFrameTaskFeedbackForm, ObjectFrameTask
+from cumin.objectframe import *
from cumin.parameters import RosemaryObjectParameter
from cumin.task import TaskLink, Task, ObjectTaskForm
from cumin.widgets import *
+from cumin.stat import *
from sage.wallaby.wallabyoperations import WallabyOperations, WBTypes
from sage.util import *
-
strings = StringCatalog(__file__)
log = logging.getLogger("cumin.tags")
@@ -138,15 +138,133 @@
def create_table(self, app, name, cls):
''' override the default to give us a plain ObjectTable rather than and ObjectSelectorTable '''
return ObjectTable(app, name, cls)
+
+class TagObjectView(ObjectView):
+ ''' necessary so that we can override the render_title method
+ to get the name from our tag object
+ '''
+ def render_title(self, session):
+ obj = self.object.get(session)
+ return obj.name
-class TagsFrame(ObjectFrame):
+ def add_details_tab(self):
+ pass
+
+class TagObjectFrame(Frame, ModeSet):
+ '''
+ This frame houses the view page for a tag, which is a non-traditional object
+ in the sense that it does not live in the cumin database. Given that, we
+ need to make a few changes to the typical functionality.
+ '''
+ def __init__(self, app, name, cls):
+ super(TagObjectFrame, self).__init__(app, name)
+
+ self.cls = cls
+
+ # This will hold the id of the object that should be assigned
+ # to self.object (it is the _id field from a sql table).
+ # Both the id and the object are typically determined during
+ # the "process" pass before a page is rendered.
+ self.id = StringParameter(app, "id")
+ self.add_parameter(self.id)
+
+ # This will be given a value during the "process" pass after
+ # self.id is determined (lookup by id)
+ self.object = Attribute(app, "object")
+ self.add_attribute(self.object)
+ self.view = TagObjectView(app, "view", self.object)
+ self.add_child(self.view)
+ self.icon_href = "resource?name=action-36.png"
+ self.tasks = list()
+
+ def init(self):
+ super(TagObjectFrame, self).init()
+ assert self.cls, self
+ for task in self.tasks:
+ task.init()
+
+ def get_href(self, session, id):
+ branch = session.branch()
+ self.id.set(branch, id)
+ self.view.show(branch)
+ return branch.marshal()
+
+ def do_process(self, session):
+ id = self.id.get(session)
+ assert id
+ obj = self.get_object(session, id)
+ self.object.set(session, obj)
+ super(TagObjectFrame, self).do_process(session)
+
+ def get_object(self, session, id):
+ return self.app.wallaby.get_tag_by_name(id)
+
+class TagOverview(Widget):
+ '''
+ This is the contents of the "overview" tab that should be in the tag frame
+ '''
+ def __init__(self, app, name, tag):
+ super(TagOverview, self).__init__(app, name)
+
+ self.add_child(TagGeneral(app, "taggen", tag))
+
+ def render_title(self, session):
+ return "Overview"
+
+class TagGeneral(Widget):
+ '''
+ This will display the general details for a tag (namely, the hosts
+ and features)
+ '''
+ def __init__(self, app, name, tag):
+ super(TagGeneral, self).__init__(app, name)
+ self.update_enabled = True
+ self.tag = tag
+
+ def render_hosts(self, session):
+ retval = ""
+ try:
+ obj = self.tag.get(session)
+ retval = ", ".join(self.app.wallaby.get_node_names(obj))
+ except Exception, e:
+ log.debug("Exception in rendering tag hosts, tags probably not loaded yet: %s", e.message)
+ return retval
+
+ def render_features(self, session):
+ retval = ""
+ try:
+ obj = self.tag.get(session)
+ retval = ", ".join(obj.features)
+ except Exception, e:
+ log.debug("Exception in rendering tag features, tags probably not loaded yet: %s", e.message)
+ return retval
+
+ def render_hosts_title(self, session):
+ return "Hosts:"
+
+ def render_features_title(self, session):
+ return "Features:"
+
+class TagsFrame(TagObjectFrame):
def __init__(self, app, name):
cls = app.model.com_redhat_cumin_grid.Node
super(TagsFrame, self).__init__(app, name, cls)
+
self.set_tags = TagsNodeEditTask(app, self)
self.set_nodes = TagsTagEditTask(app, self)
self.set_features = TagsFeatureEditTask(app, self)
+
+ self.overview = TagOverview(app, "tagoverview", self.object)
+ self.view.add_tab(self.overview)
+ def get_title(self, session):
+ retval = ""
+ try:
+ retval = self.object.get(session).name
+ except Exception, e:
+ pass
+ return retval
+
class TagInventory(ObjectSelector):
'''
Table that will display the list of all tags across the system.
@@ -164,7 +282,7 @@
link = TaskLink(app, "actlink", self.activate_config_task)
self.links.add_child(link)
- col = self.TagColumn(app, "tagcol", cls.Tags)
+ col = self.TagColumn(app, "tagcol", cls.Tags, cls.Tags, "main.grid.tag")
col.width = "20%"
self.add_column(col)
self.add_search_filter(col)
@@ -182,20 +300,15 @@
def render_title(self, session):
return "Configuration"
- class TagColumn(ObjectTableColumn):
- def render_cell_content(self, session, data):
- tags = super(TagInventory.TagColumn, self).render_cell_content(session, data)
- node_list = data[4]
- nodes = ""
- if node_list:
- for node in node_list:
- nodes = nodes + "," + str(node)
- nodes = nodes[1:]
- #here we set some info that will be used in the display of the target form
- self.frame.tag.set_nodes.form.node_name.set(session, nodes)
- self.frame.tag.set_nodes.form.tags.set(session, tags)
- href = self.frame.tag.set_nodes.get_href(session)
- return fmt_link(href, tags)
+ class TagColumn(ObjectLinkColumn):
+ def render_cell_href(self, session, record):
+ id = record[self.id_field.index]
+ frame = self.page.page_widgets_by_path[self.frame_path]
+
+ if isinstance(frame, TagObjectFrame):
+ return frame.get_href(session, id)
+ else:
+ return id #not able to get a link
class FeatureColumn(ObjectTableColumn):
'''
@@ -203,21 +316,14 @@
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[1]
feature_list = data[2]
features = ""
if feature_list:
for feature in feature_list:
features = features + "," + str(feature)
features = features[1:]
- #here we set some info that will be used in the display of the target form
- self.frame.tag.set_features.form.feature_name.set(session, features)
- self.frame.tag.set_features.form.tags.set(session, tags)
- href = self.frame.tag.set_features.get_href(session)
- if not features or features == "":
- features = escape_entity("<add features to this tag>")
features = truncate_text(features, 50, True)
- return fmt_link(href, features)
+ return features
class HostCountColumn(ObjectTableColumn):
def render_cell_content(self, session, data):
@@ -241,11 +347,6 @@
super(NodeInventory, self).__init__(app, name, cls)
self.table.adapter = NodesAdapter(app, cls)
-# NOT SURE IF THIS FUNCTIONALITY IS NECESSARY, turned off for now
-# edit_node_tags_task = ChangeNodeTags(app)
-# link = TaskLink(app, "node_tag_edit", edit_node_tags_task)
-# self.links.add_child(link)
-
col = self.NodeColumn(app, "nodecol", cls.Host)
self.add_column(col)
self.add_search_filter(col)
@@ -467,7 +568,7 @@
self.page.redirect.set(session, url)
def render_tag_list(self, session):
- items = fetchTags(self, session)
+ items = fetchItems(self, session, WBTypes.TAGS)
tags_string = ""
tags = list()
@@ -477,7 +578,7 @@
return tags
def render_node_list(self, session):
- items = fetchNodes(self, session)
+ items = fetchItems(self, session, WBTypes.NODES)
nodes_string = ""
nodes = list()
@@ -522,7 +623,7 @@
class TagSearchInputSet(IncrementalSearchInput):
def do_get_items(self, session):
- items = fetchTags(self, session)
+ items = fetchItems(self, session, WBTypes.TAGS)
tags_string = ""
tags = list()
@@ -538,7 +639,7 @@
return tag
-class EditNodeTagsForm(ObjectFrameTaskFeedbackForm):
+class EditNodeTagsForm(ObjectFrameTaskForm):
'''
This form is designed to allow the editing of tags for any single node
'''
@@ -598,7 +699,7 @@
return tags_string
def do_get_items(self, session):
- tags = fetchTags(self, session)
+ tags = fetchItems(self, session, WBTypes.TAGS)
items = list()
@@ -611,7 +712,7 @@
return items
def get_items_count(self, session):
- return len(fetchTags(self, session))
+ return len(fetchItems(self, session, WBTypes.TAGS))
def render_container_height(self, session):
''' scales-down the container height for lists that will never fill the max size '''
@@ -678,7 +779,7 @@
class DisabledInput(StringInput):
pass
-class EditTagNodesForm(ObjectFrameTaskFeedbackForm):
+class EditTagNodesForm(ObjectFrameTaskForm):
'''
This form will allow the editing of nodes for a single given tag
'''
@@ -731,7 +832,7 @@
return nodes_string
def do_get_items(self, session):
- nodes = fetchNodes(self, session)
+ nodes = fetchItems(self, session, WBTypes.NODES)
items = list()
if nodes:
@@ -743,7 +844,7 @@
return items
def get_items_count(self, session):
- return len(fetchNodes(self, session))
+ return len(fetchItems(self, session, WBTypes.NODES))
def render_container_height(self, session):
''' scales-down the container height for lists that will never fill the max size '''
@@ -810,7 +911,7 @@
class DisabledInput(StringInput):
pass
-class EditTagFeaturesForm(ObjectFrameTaskFeedbackForm):
+class EditTagFeaturesForm(ObjectFrameTaskForm):
'''
This form will allow the editing of nodes for a single given tag
'''
@@ -864,7 +965,7 @@
return features_string
def do_get_items(self, session):
- features = fetchFeatures(self, session)
+ features = fetchItems(self, session, WBTypes.FEATURES)
items = list()
if features:
@@ -876,7 +977,7 @@
return items
def get_items_count(self, session):
- return len(fetchFeatures(self, session))
+ return len(fetchItems(self, session, WBTypes.FEATURES))
def render_container_height(self, session):
''' scales-down the container height for lists that will never fill the max size '''
@@ -972,10 +1073,10 @@
self.form = EditTagNodesForm(app, self.name, self)
def get_title(self, session):
- return "Change nodes for this tag "
+ return "Edit hosts"
def do_enter(self, session, osession):
- self.form.tags.set(session, self.form.tags.get(osession))
+ self.form.tags.set(session, osession.values_by_path["main.grid.tag.id"])
self.form.node_name.set(session, self.form.node_name.get(osession))
def do_invoke(self, invoc, negotiator, tag, chosen_nodes):
@@ -1015,10 +1116,10 @@
self.form = EditTagFeaturesForm(app, self.name, self)
def get_title(self, session):
- return "Change features for this tag "
+ return "Edit features"
def do_enter(self, session, osession):
- self.form.tags.set(session, self.form.tags.get(osession))
+ self.form.tags.set(session, osession.values_by_path["main.grid.tag.id"])
self.form.feature_name.set(session, self.form.feature_name.get(osession))
def do_invoke(self, invoc, negotiator, tag, chosen_features):
@@ -1096,39 +1197,6 @@
invoc.end()
-class ChangeNodeTags(Task):
- '''
- This is the task that is invoked to change the tags for a given set of
- nodes and tags. It will go through each node in the list and set the
- tags list of that node to the supplied tags, supporting whatever "many to many"
- UI elements are in front of this.
- '''
- def __init__(self, app):
- super(ChangeNodeTags, self).__init__(app)
- cls = app.model.com_redhat_cumin_grid.Node
- self.form = TagToNodes(app, self.name, self, cls)
-
- def get_title(self, session, object):
- return "Edit node tags"
-
- def do_invoke(self, session, object, invoc, nodes, tags):
- tag_list = [x.strip() for x in tags.split(',')]
- node_list = [x.strip() for x in nodes.split(',')]
-
- try:
- for node in node_list:
- updated_tags = self.app.wallaby.get_tag_names(node)
- for tag in tag_list:
- if tag not in updated_tags:
- updated_tags.append(tag)
- call_async(invoc.make_callback(), self.app.wallaby.edit_tags, node, *updated_tags)
-
- except Exception, e:
- invoc.status = invoc.FAILED
- log.exception(e)
-
- invoc.end()
-
class RemoveNodeTags(ObjectSelectorTask):
'''
This is the task that is invoked to trigger the deletion of a given tag.
@@ -1165,61 +1233,16 @@
def get_item_content(self, session, item):
return item
-
-
-class TagsEditor(RadioModeSet):
- '''
- This defines the set of radio buttons that appear under the Tags tab in the UI
- '''
- def __init__(self, app, name):
- super(TagsEditor, self).__init__(app, name)
-
- inventory_tab = NodeInventory(app, "nodi")
- self.add_tab(inventory_tab)
-
- tag_inventory_tab = TagInventory(app, "tagi")
- self.add_tab(tag_inventory_tab)
-
- def render_title(self, session):
- return "Configuration"
-
-
-def fetchTags(self, session):
+
+def fetchItems(self, session, type):
'''
- fetch the list of tags from wallaby
+ fetch the list of <type> from wallaby
'''
- wallaby_tags = self.app.wallaby.get_data(WBTypes.TAGS)
- tag_list = list()
+ wallaby_items = self.app.wallaby.get_data(type)
+ item_list = list()
- for tag in wallaby_tags:
- tag_list.append(str(tag.name))
- tag_list.sort()
+ for item in wallaby_items:
+ item_list.append(str(item.name))
+ item_list.sort()
- return tag_list
-
-def fetchNodes(self, session):
- '''
- fetch the list of nodes from wallaby
- '''
- wallaby_nodes = self.app.wallaby.get_data(WBTypes.NODES)
- node_list = list()
-
- for node in wallaby_nodes:
- node_list.append(str(node.name))
- node_list.sort()
-
- return node_list
-
-def fetchFeatures(self, session):
- '''
- fetch the list of nodes from wallaby
- '''
- wallaby_features = self.app.wallaby.get_data(WBTypes.FEATURES)
- feature_list = list()
-
- for feature in wallaby_features:
- feature_list.append(str(feature.name))
- feature_list.sort()
-
- return feature_list
-
\ No newline at end of file
+ return item_list
\ No newline at end of file
Modified: trunk/cumin/python/cumin/grid/tags.strings
===================================================================
--- trunk/cumin/python/cumin/grid/tags.strings 2011-09-13 13:50:04 UTC (rev 4964)
+++ trunk/cumin/python/cumin/grid/tags.strings 2011-09-14 19:04:02 UTC (rev 4965)
@@ -83,4 +83,36 @@
//]]>
-</script>
\ No newline at end of file
+</script>
+
+[TagGeneral.css]
+table.PropertySet {
+ width: 100%;
+ font-size: 0.9em;
+ text-align: left;
+ border-collapse; collapse;
+}
+
+table.PropertySet tbody td {
+ border-top: 1px dotted #ccc;
+}
+
+table.PropertySet thead a {
+ color: #666;
+ font-weight: normal;
+ font-style: italic;
+}
+
+[TagGeneral.html]
+<table id="{id}" class="PropertySet">
+ <tbody>
+ <tr class="item" title="">
+ <th>{hosts_title}</th>
+ <td class="{class}">{hosts}</td>
+ </tr>
+ <tr class="item" title="">
+ <th>{features_title}</th>
+ <td class="{class}">{features}</td>
+ </tr>
+ </tbody>
+</table>
12 years, 8 months
r4964 - trunk/cumin/python/cumin/inventory
by croberts@fedoraproject.org
Author: croberts
Date: 2011-09-13 13:50:04 +0000 (Tue, 13 Sep 2011)
New Revision: 4964
Modified:
trunk/cumin/python/cumin/inventory/system.py
Log:
Fixing exception when sorting on non sql rows in the inventory table.
Modified: trunk/cumin/python/cumin/inventory/system.py
===================================================================
--- trunk/cumin/python/cumin/inventory/system.py 2011-09-13 13:13:38 UTC (rev 4963)
+++ trunk/cumin/python/cumin/inventory/system.py 2011-09-13 13:50:04 UTC (rev 4964)
@@ -6,7 +6,7 @@
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.sqladapter import ObjectSqlAdapter, ObjectSqlField
from cumin.util import *
from sage.wallaby.wallabyoperations import WallabyOperations, WBTypes
@@ -45,7 +45,7 @@
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")
+ col = SystemCheckinColumn(app, "Checkin", app.model.com_redhat_cumin_grid.Node.Checkin, "Checkin")
self.add_column(col)
self.enable_csv_export()
@@ -180,6 +180,10 @@
class WallabyAndSqlAdapter(ObjectSqlAdapter):
def get_data(self, values, options):
#first, fetch all the sql data
+ if not isinstance(options.sort_field, ObjectSqlField):
+ ##do some magic to handle non-sql sort fields
+ ##but for now, don't bother to sort
+ options.sort_field = None
sqldata = super(WallabyAndSqlAdapter, self).get_data(values, options)
#now get the wallaby data
@@ -210,7 +214,7 @@
def get_field(self):
raise "Not Implemented"
-
+
class TagsField(DataAdapterField):
def __init__(self, adapter, column):
super(TagsField, self).__init__(adapter, column, str)
@@ -247,7 +251,7 @@
class CheckinField(DataAdapterField):
def __init__(self, adapter, column):
super(CheckinField, self).__init__(adapter, column, str)
-
+
def get_content(self, session, record):
value = ""
try:
12 years, 8 months
r4963 - in trunk: cumin/bin cumin/etc cumin/python/cumin sage/python/sage/aviary
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-09-13 13:13:38 +0000 (Tue, 13 Sep 2011)
New Revision: 4963
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:
Separate job and query operations classes to allow functionality to be
enabled or disabled independently. Remove 'use-aviary' config parameter,
base choices on whether server lists are empty strings.
BZ733677
Modified: trunk/cumin/bin/cumin-web
===================================================================
--- trunk/cumin/bin/cumin-web 2011-09-12 19:49:40 UTC (rev 4962)
+++ trunk/cumin/bin/cumin-web 2011-09-13 13:13:38 UTC (rev 4963)
@@ -15,7 +15,6 @@
sys.stdout = sys.__stdout__
def set_aviary_configs(cumin, values):
- cumin.use_aviary = values.use_aviary
cumin.aviary_job_servers = values.aviary_job_servers
cumin.aviary_query_servers = values.aviary_query_servers
cumin.aviary_key = values.aviary_key
Modified: trunk/cumin/etc/cumin.conf
===================================================================
--- trunk/cumin/etc/cumin.conf 2011-09-12 19:49:40 UTC (rev 4962)
+++ trunk/cumin/etc/cumin.conf 2011-09-13 13:13:38 UTC (rev 4963)
@@ -16,13 +16,45 @@
# sasl-mech-list: [default, allow all available mechanisms]
# wallaby-broker: [default, first item in 'brokers' list]
# wallaby-refresh: 60
-# use-aviary: False
-# aviary-job-servers: http://localhost:9090
-# aviary-query-servers: http://localhost:9091
# log-level: info
# log-max-mb: 10
# log-max-archives: 1
+# ****************************************************
+# Aviary interface to condor
+
+# Default value for each of the following configuration
+# parameters is empty string unless otherwise specified.
+# Empty string means that no value is specified.
+
+# To use aviary job servers, uncomment the following line and edit as needed.
+# The value is a comma separated list of URLs. The default port condor uses
+# for an Aviary job server is 9090.
+# aviary-job-servers: http://localhost:9090
+
+# To use aviary query servers, uncomment the following line and edit as needed.
+# The value is a comma separated list of URLs. The default port condor uses
+# for an Aviary query server is 9091.
+# aviary-query-servers: http://localhost:9091
+
+# Full path to private key file used for ssl communication with aviary servers.
+# This is necessary to communicate with any aviary server using the https scheme.
+#aviary-key:
+
+# Full path to certificate file used for ssl communication with aviary severs.
+# This is necessary to communicate with any aviary server using the https scheme.
+#aviary-cert
+
+# Optional full path to root certificate file used for server certificate
+# validation with aviary servers using ssl. If this value is not specified, only
+# client certificate validation will be used.
+#aviary-root-cert:
+
+# Optional flag to control whether Cumin checks that the server host matches
+# the Common Name in the server certificate during server certificate validation.
+# Default value is False. Set to true to enable the check.
+#aviary-domain-verify: False
+
# *************** Master configuration ***************
# Controls the number and type of cumin-web and
Modified: trunk/cumin/python/cumin/config.py
===================================================================
--- trunk/cumin/python/cumin/config.py 2011-09-12 19:49:40 UTC (rev 4962)
+++ trunk/cumin/python/cumin/config.py 2011-09-13 13:13:38 UTC (rev 4963)
@@ -155,14 +155,11 @@
param = ConfigParameter(self, "wallaby-refresh", int)
param.default = 60
- param = ConfigParameter(self, "use-aviary", bool)
- param.default = False
-
param = ConfigParameter(self, "aviary-job-servers", str)
- param.default = "http://localhost:9090"
+ param.default = ""
param = ConfigParameter(self, "aviary-query-servers", str)
- param.default = "http://localhost:9091"
+ param.default = ""
param = ConfigParameter(self, "aviary-key", str)
param.default = ""
Modified: trunk/cumin/python/cumin/main.py
===================================================================
--- trunk/cumin/python/cumin/main.py 2011-09-12 19:49:40 UTC (rev 4962)
+++ trunk/cumin/python/cumin/main.py 2011-09-13 13:13:38 UTC (rev 4963)
@@ -80,11 +80,8 @@
# mechanisms, according to the sasl documentation
self.sasl_mech_list = None
- # Whether or not to use Aviary interface for
- # queries and job submissions plus configs.
- # These values are expected to be filled in if
- # use_aviary is left True.
- self.use_aviary = True
+ # Aviary interface. If server values are "",
+ # Aviary operations for that server type will not be used.
self.aviary_job_servers = ""
self.aviary_query_servers = ""
self.aviary_key = ""
@@ -172,17 +169,24 @@
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
+ (self.aviary_job_servers and "Enabling" or "Disabling"))
+
+ log.info("%s Aviary interface for query operations." % \
+ (self.aviary_query_servers and "Enabling" or "Disabling"))
+
+ if self.aviary_job_servers or self.aviary_query_servers:
+ from sage.aviary.aviaryoperations import AviaryOperationsFactory
aviary_dir = os.path.join(self.home, "rpc-defs/aviary")
+
+ # The factory will choose an impl that gives us jobs, queries, or both
+ aviary_itf = AviaryOperationsFactory("aviary", aviary_dir,
+ self.aviary_job_servers,
+ self.aviary_query_servers,
+ self.aviary_key, self.aviary_cert,
+ self.aviary_root_cert,
+ self.aviary_domain_verify)
+ ops.insert(0, aviary_itf)
- ops.insert(0, AviaryOperations("aviary", aviary_dir,
- self.aviary_job_servers,
- self.aviary_query_servers,
- 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-12 19:49:40 UTC (rev 4962)
+++ trunk/sage/python/sage/aviary/aviaryoperations.py 2011-09-13 13:13:38 UTC (rev 4963)
@@ -59,62 +59,25 @@
transport.
'''
-class AviaryOperations(object):
- def __init__(self, name, datadir, job_servers, query_servers,
- key="", cert="", root_cert="", domain_verify=True):
- self.name = name
- self.datadir = datadir
+class _AviaryJobMethods(object):
- # job_servers and query_servers are comma separated lists of
- # network locations. See comments on host_list() for format.
+ # Do this here rather than __init__ so we don't have to worry about
+ # matching parameter lists in multiple inheritance cases with super
+ def init(self, job_servers, datadir):
+
# Replace any occurrence of locahost with output of gethostname()
# before parsing to match Machine fields of QMF objects later on.
host = socket.gethostname()
job_servers = string.replace(job_servers, "localhost", host)
- query_servers = string.replace(query_servers, "localhost", host)
self.job_servers = host_list(job_servers,
default_scheme = "http",
default_port="9090",
default_path="/services/job/")
- self.query_servers = host_list(query_servers,
- default_scheme = "http",
- default_port="9091",
- default_path="/services/query/")
-
- job_wsdl = "file:" + os.path.join(self.datadir, "aviary-job.wsdl")
- query_wsdl = "file:" + os.path.join(self.datadir, "aviary-query.wsdl")
-
+ job_wsdl = "file:" + os.path.join(datadir, "aviary-job.wsdl")
self.job_client_pool = JobClientPool(job_wsdl, None)
- self.query_client_pool = QueryClientPool(query_wsdl, None)
- self.type_to_aviary = self._type_to_aviary()
- self.aviary_to_type = self._aviary_to_type()
-
- 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):
assert callback
@@ -162,7 +125,7 @@
# the aviary response has the job id available,
# we'll pass it anyway even though Cumin does not care
# at the present time
- status = AviaryOperations._get_status(result.status)
+ status = _AviaryCommon._get_status(result.status)
if status == "OK" and hasattr(result, "id"):
id = result.id
else:
@@ -272,10 +235,64 @@
"removeJob",
callback, default, timeout)
-#########################
-# query server ops
-########################
+ def _control_job(self, scheduler, job_id, reason, submission,
+ meth_name,
+ callback, default, timeout):
+ client = self._setup_client(self.job_client_pool,
+ scheduler.Machine,
+ self.job_servers,
+ meth_name)
+
+ meth = getattr(client.service, meth_name)
+
+ # Make a job id parameter (see job wsdl)
+ jobId = client.factory.create('ns0:JobID')
+ jobId.job = job_id
+ jobId.pool = scheduler.Pool
+ jobId.scheduler = scheduler.Name
+ jobId.submission.name = submission.Name
+ jobId.submission.owner = submission.Owner
+
+ if callback:
+ def my_callback(result):
+ self.job_client_pool.return_object(client)
+ # Fix up the exception message if necessary
+ result = self._pretty_result(result, scheduler.Machine)
+ cb_args = self._cb_args_dataless(result)
+ callback(*cb_args)
+
+ t = CallThread(meth, my_callback, jobId, reason)
+ t.start()
+ else:
+ def my_process_results(result):
+ # Fix up the exception message if necessary
+ result = self._pretty_result(result, scheduler.Machine)
+ return self._cb_args_dataless(result)
+
+ res = self._call_sync(my_process_results, meth, jobId, reason)
+ self.job_client_pool.return_object(client)
+ return res;
+
+class _AviaryQueryMethods(object):
+
+ # Do this here rather than __init__ so we don't have to worry about
+ # matching parameter lists in multiple inheritance cases with super
+ def init(self, query_servers, datadir):
+
+ # Replace any occurrence of locahost with output of gethostname()
+ # before parsing to match Machine fields of QMF objects later on.
+ host = socket.gethostname()
+ query_servers = string.replace(query_servers, "localhost", host)
+
+ self.query_servers = host_list(query_servers,
+ default_scheme = "http",
+ default_port="9091",
+ default_path="/services/query/")
+
+ query_wsdl = "file:" + os.path.join(datadir, "aviary-query.wsdl")
+ self.query_client_pool = QueryClientPool(query_wsdl, None)
+
def fetch_job_data(self, job_server, job_id, ftype, file, start, end,
scheduler_name, submission, *args, **kwargs):
'''
@@ -298,7 +315,7 @@
status = result
data = None
else:
- status = AviaryOperations._get_status(result.status)
+ status = _AviaryCommon._get_status(result.status)
if status == "OK" and hasattr(result, "content"):
# Match the format expected by Cumin. This is
# the format used by the QMF call...
@@ -373,7 +390,7 @@
status = result
data = None
else:
- status = AviaryOperations._get_status(result[0].status)
+ status = _AviaryCommon._get_status(result[0].status)
if status == "OK":
# Match the format expected by Cumin. This is
# the format used by the QMF call. We have a list
@@ -454,7 +471,7 @@
if isinstance(result, Exception):
callback(result, None)
else:
- status = AviaryOperations._get_status(result[0].status)
+ status = _AviaryCommon._get_status(result[0].status)
if status == "OK" and hasattr(result[0], "jobs"):
data = {"Jobs": adapt(result[0].jobs)}
else:
@@ -481,10 +498,34 @@
my_callback, subId)
t.start()
-########################################################
-# Secret private implementation stuff, don't look!
-########################################################
+class _AviaryCommon(object):
+ def __init__(self, name, key="", cert="", root_cert="", domain_verify=True):
+ self.name = name
+ self.type_to_aviary = self._type_to_aviary()
+ self.aviary_to_type = self._aviary_to_type()
+
+ 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)
+
@classmethod
def _type_to_aviary(cls):
# Need to be able to turn simple Python types into Aviary types for attributes
@@ -569,7 +610,7 @@
if isinstance(result, Exception):
status = result
else:
- status = AviaryOperations._get_status(result)
+ status = _AviaryCommon._get_status(result)
return (status, None)
@classmethod
@@ -592,45 +633,49 @@
sync.get_completion()(*cb_args)
return sync
- def _control_job(self, scheduler, job_id, reason, submission,
- meth_name,
- callback, default, timeout):
+class AviaryOperations(_AviaryCommon, _AviaryJobMethods, _AviaryQueryMethods):
+ def __init__(self, name, datadir, job_servers, query_servers,
+ key="", cert="", root_cert="", domain_verify=True):
- client = self._setup_client(self.job_client_pool,
- scheduler.Machine,
- self.job_servers,
- meth_name)
+ super(AviaryOperations, self).__init__(name, key, cert, root_cert,
+ domain_verify)
- meth = getattr(client.service, meth_name)
+ _AviaryJobMethods.init(self, job_servers, datadir)
+ _AviaryQueryMethods.init(self, query_servers, datadir)
- # Make a job id parameter (see job wsdl)
- jobId = client.factory.create('ns0:JobID')
- jobId.job = job_id
- jobId.pool = scheduler.Pool
- jobId.scheduler = scheduler.Name
- jobId.submission.name = submission.Name
- jobId.submission.owner = submission.Owner
- if callback:
- def my_callback(result):
- self.job_client_pool.return_object(client)
- # Fix up the exception message if necessary
- result = self._pretty_result(result, scheduler.Machine)
- cb_args = self._cb_args_dataless(result)
- callback(*cb_args)
+class AviaryJobOperations(_AviaryCommon, _AviaryJobMethods):
+ def __init__(self, name, datadir, job_servers,
+ key="", cert="", root_cert="", domain_verify=True):
- t = CallThread(meth, my_callback, jobId, reason)
- t.start()
- else:
- def my_process_results(result):
- # Fix up the exception message if necessary
- result = self._pretty_result(result, scheduler.Machine)
- return self._cb_args_dataless(result)
+ super(AviaryJobOperations, self).__init__(name, key, cert, root_cert,
+ domain_verify)
+
+ _AviaryJobMethods.init(self, job_servers, datadir)
- res = self._call_sync(my_process_results, meth, jobId, reason)
- self.job_client_pool.return_object(client)
- return res;
+class AviaryQueryOperations(_AviaryCommon, _AviaryQueryMethods):
+ def __init__(self, name, datadir, query_servers,
+ key="", cert="", root_cert="", domain_verify=True):
+ super(AviaryQueryOperations, self).__init__(name, key, cert, root_cert,
+ domain_verify)
+
+ _AviaryQueryMethods.init(self, query_servers, datadir)
+
+def AviaryOperationsFactory(name, datadir, job_servers=None, query_servers=None,
+ key="", cert="", root_cert="", domain_verify=True):
+
+ if job_servers and query_servers:
+ return AviaryOperations(name, datadir, job_servers, query_servers,
+ key, cert, root_cert, domain_verify)
+ elif job_servers:
+ return AviaryJobOperations(name, datadir, job_servers,
+ key, cert, root_cert, domain_verify)
+ elif query_servers:
+ return AviaryQueryOperations(name, datadir, query_servers,
+ key, cert, root_cert, domain_verify)
+ return None
+
try:
# Some of this stuff does not exist pre suds 0.4.1
# Make it work anyway for testing on such hosts by
12 years, 8 months