[luci] Luci: subcontrollers schema introduced, minor changes
by Jan Pokorný
commit 2be2c44ea9499ea3fc32e742cf65a89cb4bbf2c3
Author: Jan Pokorny <jpokorny(a)redhat.com>
Date: Fri Aug 28 18:39:05 2009 +0200
Luci: subcontrollers schema introduced, minor changes
deploy-devel.sh | 13 ++
luci/controllers/decorators.py | 137 +++++++++++++++---
luci/controllers/demo_data.py | 121 ++++++++++++++++
luci/controllers/fdom.py | 144 +++++++++++++++++++
luci/controllers/fence.py | 90 ++++++++++++
luci/controllers/root.py | 308 ++--------------------------------------
luci/templates/fdom.html | 19 ++--
luci/templates/fence.html | 15 +-
luci/templates/master.html | 4 +-
9 files changed, 514 insertions(+), 337 deletions(-)
---
diff --git a/deploy-devel.sh b/deploy-devel.sh
new file mode 100755
index 0000000..f245ddd
--- /dev/null
+++ b/deploy-devel.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+#
+# Simple script for deploying luci (prepare application + database
+# and start serving it) for development purposes (with automatic reload
+# on appropriate files changes).
+#
+# You may need to make sure that you are running this script from
+# tg2env environment (activated with 'source bin/activate' command
+# from within tg2env directory).
+
+python setup.py install
+paster setup-app development.ini
+paster serve --reload development.ini
diff --git a/luci/controllers/decorators.py b/luci/controllers/decorators.py
index 67433da..807012c 100644
--- a/luci/controllers/decorators.py
+++ b/luci/controllers/decorators.py
@@ -1,24 +1,117 @@
# -*- coding: utf-8 -*-
-"""Decorators used in the main controller."""
-
-from tg import url, redirect, request
-
-def noAdditionalKeyArgs(fn):
- """Decorator for stripping off additional keyword args & purifying URL."""
- def purified(*args, **kw):
- key_args = fn.func_code.co_varnames # All keyword args of original fn.
- redir = False
- for keyw in kw.keys():
- # If some keyword arg that is not supported by original function
- # was found, remove it from keyword args dict and set
- # the redirect flag for redirection to purified URL.
- if not keyw in key_args:
- del kw[keyw]
- redir = True
- if redir:
- redirect(url(request.path, **kw))
- else:
- return fn(*args, **kw)
-
- return purified
+"""Decorators used within the application's controllers."""
+from tg import url, redirect, request, flash, expose
+from pylons.i18n import ugettext as _, lazy_ugettext as l_
+
+__all__ = ['forPageShowWithUrlCorrection',
+ 'forPageShowOnly',
+ 'forImmediateRedirect']
+
+
+def forPageShowOnly(template, allowed_methods = ('GET', 'POST'), failback = '.',
+ use_referer = True):
+ """Decorator to show page incl. check of the request's method.
+
+ Keyword arguments:
+ template Which template to internally use with @expose().
+ allowed_methods Tuple listing all supported methods (e.g. GET or POST).
+ failback Where to redirect on failure.
+ use_referer On redirect call, try to use referer.
+
+ """
+ if type(allowed_methods) not in (tuple, list):
+ allowed_methods = [allowed_methods]
+
+ def decorator(fn):
+ """Function (method) binding"""
+ @expose(template)
+ def wrapper(*args, **kwargs):
+ """Arguments binding, decorated function (method) call."""
+ if request.method not in allowed_methods:
+ flash(_('Unsupported method of the request.'), status='error')
+ redir_url = failback
+ if use_referer and request.referer:
+ redir_url = request.referer
+ redirect(redir_url)
+ else:
+ return fn(*args, **kwargs)
+
+ # Decorator's retval is the value returned by the wrapper.
+ return wrapper
+
+ # Returned value is the value returned by the decorator.
+ return decorator
+
+
+def forPageShowWithUrlCorrection(template):
+ """Decorator to show page incl. stripping off unsupported kwargs from URL.
+
+ Obviously, the only supported method is GET.
+
+ Keyword arguments:
+ template Which template to internally use with @expose().
+
+ """
+ def decorator(fn):
+ """Function (method) binding"""
+ @expose(template)
+ def wrapper(*args, **kwargs):
+ """Arguments binding, decorated function (method) call."""
+ redir = False
+ if request.method != 'GET':
+ flash(_('Unsupported method of the request.'), status='error')
+ redirect(url(request.path, **kwargs))
+ else:
+ # Get all the keyword arguments of original function.
+ fn_kwargs = fn.func_code.co_varnames[1:fn.func_code.co_argcount]
+ for kwarg in kwargs.keys():
+ # If some keyword arg that is not supported by original fun.
+ # was found, remove it from keyword args dict and set
+ # the redirect flag for redirection to 'purified URL'.
+ if not kwarg in fn_kwargs:
+ del kwargs[kwarg]
+ redir = True
+ return fn(*args, **kwargs)
+
+ # Decorator's retval is the value returned by the wrapper.
+ return wrapper
+
+ # Returned value is the value returned by the decorator.
+ return decorator
+
+
+def forImmediateRedirect(allowed_methods = ('GET', 'POST'), failback = '.',
+ use_referer = True):
+ """Decorator to redirect immediatelly after handling the request.
+
+ Check of the request's method is applied.
+
+ Keyword arguments:
+ allowed_methods Tuple listing all supported methods (e.g. GET or POST).
+ failback Where to redirect on failure.
+ use_referer On redirect call, try to use referer.
+
+ """
+ if type(allowed_methods) not in (tuple, list):
+ allowed_methods = [allowed_methods]
+
+ def decorator(fn):
+ """Function (method) binding."""
+ @expose()
+ def wrapper(*args, **kwargs):
+ """Arguments binding, decorated function (method) call."""
+ if request.method not in allowed_methods:
+ flash(_('Unsupported method of the request.'), status='error')
+ else:
+ fn(*args, **kwargs)
+ redir_url = failback
+ if use_referer and request.referer:
+ redir_url = request.referer
+ redirect(redir_url)
+
+ # Decorator's retval is the value returned by the wrapper.
+ return wrapper
+
+ # Returned value is the value returned by the decorator.
+ return decorator
diff --git a/luci/controllers/demo_data.py b/luci/controllers/demo_data.py
new file mode 100644
index 0000000..95787d4
--- /dev/null
+++ b/luci/controllers/demo_data.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+"""Static demo data."""
+
+__all__ = ['NodeConstants', 'nodes', 'services', 'fdoms', 'fences']
+
+# Nodes in the current cluster.
+
+class NodeConstants:
+ NODE_ACTIVE = '0'
+ NODE_UNKNOWN = '1'
+ NODE_INACTIVE = '2'
+
+# Demo data for nodes (format is self-explained).
+nodes = {'NodeAlpha': {'ip': '144.92.235.11',
+ 'serviceload': 10,
+ 'status': NodeConstants.NODE_ACTIVE},
+ 'NodeBeta': {'ip': '144.92.235.12',
+ 'serviceload': 20,
+ 'status': NodeConstants.NODE_ACTIVE},
+ 'NodeDelta': {'ip': '144.92.235.13',
+ 'serviceload': 30,
+ 'status': NodeConstants.NODE_INACTIVE,
+ 'msg': 'Something terrible'},
+ 'NodeGamma': {'ip': '144.92.235.14',
+ 'serviceload': 40,
+ 'status': NodeConstants.NODE_ACTIVE},
+ 'NodeEpsilon': {'ip': '144.92.235.15',
+ 'serviceload': 50,
+ 'status': NodeConstants.NODE_UNKNOWN}}
+
+# Services in the current cluster.
+
+# Demo data for services (format is self-explained).
+services = {'Service W': {'running': True,
+ 'autostart': True,
+ 'faildom': 'Failover1',
+ 'node': 'NodeGamma'},
+ 'Service Y': {'running': False,
+ 'autostart': False,
+ #'faildom': None,
+ 'node': 'NodeAlpha'},
+ 'Service X': {'running': True,
+ 'autostart': True,
+ 'faildom': 'Failover2',
+ 'node': 'NodeBeta'},
+ 'Service Z': {'running': False,
+ 'autostart': True,
+ #'faildom': None,
+ 'node': 'NodeAlpha'}}
+
+# Failover domains in the current cluster.
+
+# Demo data for failover domains (format is self-explained).
+fdoms = {'Failover1':{'prioritized': False,
+ 'restricted': True,
+ 'failback': False,
+ 'services': ('Service Y',
+ 'Service X'),
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeBeta': 2}},
+ 'Failover2':{'prioritized': True,
+ 'restricted': True,
+ 'failback': False,
+ 'services': ('Service X',
+ 'Service Y',
+ 'Service Z'),
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeBeta': 2,
+ 'NodeEpsilon': 0}},
+ 'FDOM a': {'prioritized': True,
+ 'restricted': False,
+ 'failback': False,
+ 'services': ('Service W',
+ 'Service Z'),
+ 'nodes': {'NodeBeta': 2,
+ 'NodeGamma': 0}},
+ 'FDOM b': {'prioritized': True,
+ 'restricted': False,
+ 'failback': False,
+ 'services': ('Service Y'),
+ 'nodes': {'NodeDelta': 0,
+ 'NodeGamma': 0}},
+ 'FDOM c': {'prioritized': True,
+ 'restricted': True,
+ 'failback': True,
+ 'services': ('Service Z',
+ 'Service X',
+ 'Service W'),
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeEpsilon': 0}}}
+
+# Shared fences in the current cluster.
+
+# Demo data for fences (format is self-explained).
+fences = { 'Fence A':{'type': 'iLO',
+ 'host': 'hostname.host.org',
+ 'ip': '123.123.78.90',
+ 'port': 6666,
+ 'nodes': {'NodeDelta': 1}},
+ 'Fence B':{'type': 'APC Power Device',
+ 'host': 'fenceb.host.org',
+ 'ip': '123.123.78.11',
+ 'port': 6667,
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeDelta': 2}},
+ 'Fence C':{'type': 'Virtual Machine',
+ 'host': 'fencec.host.org',
+ 'ip': '123.123.78.156',
+ 'port': 11023,
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeBeta': 2,
+ 'NodeGamma': 2,
+ 'NodeDelta': 2,
+ 'NodeEpsilon': 2}},
+ 'Fence D':{'type': 'SCSI Reservation',
+ 'host': 'fenced.host.org',
+ 'ip': '123.123.78.35',
+ 'port': 5487,
+ 'nodes': {'NodeAlpha': 2,
+ 'NodeBeta': 1,
+ 'NodeGamma': 2}}}
\ No newline at end of file
diff --git a/luci/controllers/fdom.py b/luci/controllers/fdom.py
new file mode 100644
index 0000000..6776dea
--- /dev/null
+++ b/luci/controllers/fdom.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+"""Controller API for Failovers."""
+
+from tg import flash, url, request, redirect
+from pylons.i18n import ugettext as _, lazy_ugettext as l_
+
+from luci.lib.form_helpers import string2id, id2string
+from luci.controllers.demo_data import fdoms as d_fdoms, \
+ nodes as d_nodes, \
+ services as d_services, \
+ NodeConstants
+from luci.controllers.decorators import *
+
+__all__ = ['FailoverController']
+
+
+class FailoverController(object):
+ """Luci's subcontroller handling requests related with Failovers."""
+
+ LOCAL_INDEX = '.'
+
+
+ @forPageShowWithUrlCorrection('luci.templates.fdom')
+ def index(self, name=None):
+ """Handle 'failover domains' page.
+
+ Keyword arguments:
+ name Name of the chosen failover domain.
+
+ """
+ details = None
+ fdoms_overview = [[k,v['prioritized'],v['restricted'],v['failback']]
+ for k,v in d_fdoms.iteritems()]
+
+ # Check whether we are able to display details to selected failover
+ # domain (and if the given failover domain really exists).
+ if name:
+ if d_fdoms.has_key(name):
+ details = d_fdoms[name]
+ else:
+ redirect(request.path)
+ return dict(name=name, fdoms=fdoms_overview, details=details,
+ services=d_services, nodes=d_nodes,
+ nodeconst=NodeConstants,
+ string2id=string2id, id2string=id2string)
+
+
+ @forImmediateRedirect(allowed_methods = ('GET', 'POST'))
+ def delete(self, name=None, **kwargs):
+ """Handle removal of selected failover domain(s).
+
+ Keyword arguments:
+ name Name of the failover to delete (if GET method is used).
+ kwargs Dict of failovers to delete (if POST method is used).
+
+ """
+ if name:
+ which = [name]
+ else:
+ which = [id2string(s) for s in kwargs.iterkeys()]
+ if not which:
+ flash(_('Nothing was chosen!'), status='warning')
+ else:
+ flash(_('Deleting selected failover domain(s)... %s') %
+ (", ".join(which)), status='info')
+ for fdom in which:
+ if d_fdoms.has_key(fdom):
+ del d_fdoms[fdom]
+ else:
+ # If the luci's data are consistent this should never happen.
+ flash(_('Internal error.'), status='error')
+
+
+ @forImmediateRedirect(allowed_methods = 'GET')
+ def add(self):
+ """Handle creation of a new failover domain."""
+
+ flash('It should be a dialog to add new failover domain here instead,'
+ 'but not implemented yet...', status='info')
+ # following code is only my experiment, how to continue:
+ #tmpl_context.form = create_fdom_add_form
+ #return dict()
+
+
+ @forImmediateRedirect(allowed_methods = 'POST')
+ def update_props(self, name=None, **kwargs):
+ """Handle changes in a failover domain -- properties.
+
+ Keyword arguments:
+ name Name of the failover, which attributes are being changed.
+ kwargs Dict of attributes that are active.
+
+ """
+ if not name:
+ redirect(request.referer or '/fdom')
+
+ fdom = id2string(name)
+ if d_fdoms.has_key(fdom):
+ for attr in ['prioritized', 'restricted', 'failback']:
+ if kwargs.has_key(attr):
+ d_fdoms[fdom][attr] = True
+ else:
+ d_fdoms[fdom][attr] = False
+ flash(_('Updating properties for %s') % fdom, status='info')
+ else:
+ # If the luci's data are consistent this should never happen.
+ flash(_('Internal error.'), status='error')
+
+
+ @forImmediateRedirect(allowed_methods = 'POST')
+ def update_members(self, name=None, **kwargs):
+ """Handle changes in a failover domain -- member nodes.
+
+ Keyword arguments:
+ name Name of the failover, which members are being changed.
+ kwargs Dict of values of members.
+
+ """
+ if not name:
+ flash(_('Internal error.'), status='error')
+ else:
+ fdom = id2string(name)
+ if d_fdoms.has_key(fdom):
+ # Clear the dictionary of member nodes and start from scratch.
+ d_fdoms[fdom]['nodes'].clear()
+ nodes = map(lambda id_check: id2string(id_check.replace('.check', '')),
+ filter(lambda id_str: id_str.endswith('.check'),
+ kwargs.iterkeys()))
+ flash(_('Updating members for %s') % fdom, status='info')
+ for node in nodes:
+ if d_nodes.has_key(node):
+ d_fdoms[fdom]['nodes'][node] = \
+ kwargs.get(string2id(node) + '.priority', 0)
+ else:
+ # If the luci's data are consistent this should never
+ # happen.
+ flash(_('Internal error'), status='error')
+
+
+ @forImmediateRedirect(allowed_methods = 'GET')
+ def service(self):
+ """Handle creation of a new service."""
+
+ flash('Demo of adding a service...', status='info')
diff --git a/luci/controllers/fence.py b/luci/controllers/fence.py
new file mode 100644
index 0000000..c5d03d0
--- /dev/null
+++ b/luci/controllers/fence.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+"""Controller API for Fences."""
+
+from tg import flash, url, request, redirect
+from pylons.i18n import ugettext as _, lazy_ugettext as l_
+
+from luci.lib.form_helpers import string2id, id2string
+from luci.controllers.demo_data import fences as d_fences, \
+ nodes as d_nodes, \
+ NodeConstants
+from luci.controllers.decorators import *
+
+__all__ = ['FenceController']
+
+
+class FenceController(object):
+ """Luci's subcontroller handling requests related with Fences."""
+
+ LOCAL_INDEX = '.'
+
+
+ @forPageShowWithUrlCorrection('luci.templates.fence')
+ def index(self, name=None):
+ """Handle 'fences' page.
+
+ Keyword arguments:
+ name Name of the chosen fence.
+
+ """
+ details = None
+ fences_overview = \
+ [[k,v['type'],
+ len(v['nodes'])==1
+ and (v['nodes'].keys()[0],
+ d_nodes.get(v['nodes'].keys()[0],
+ {'status': NodeConstants.NODE_UNKNOWN}))
+ or len(v['nodes']),
+ v['ip']] for k,v in d_fences.iteritems()]
+
+ # Check whether we are able to display details to selected fence.
+ if name:
+ if d_fences.has_key(name):
+ details = d_fences[name]
+ else:
+ redirect(request.path)
+
+ return dict(name=name, fences=fences_overview, details=details,
+ nodes=d_nodes, nodeconst=NodeConstants,
+ string2id=string2id, id2string=id2string)
+
+
+ @forImmediateRedirect(allowed_methods = ('GET', 'POST'))
+ def delete(self, name=None, **kw):
+ """Handle removal of selected fence(s).
+
+ Keyword arguments:
+ name Name of the fence to delete (if GET method is used).
+ kwargs Dict of fences to delete (if POST method is used).
+
+ """
+ if name:
+ which = [name]
+ else:
+ which = [id2string(s) for s in kw.iterkeys()]
+
+ if not which:
+ flash(_('Nothing was chosen!'), status='warning')
+ else:
+ flash(_('Deleting selected fence(s)... %s')
+ % (", ".join(which)), status='info')
+ for fence in which:
+ if d_fences.has_key(fence):
+ del d_fences[fence]
+ else:
+ # If the luci's data are consistent this should never happen.
+ flash(_('Internal error.'), status='error')
+
+
+ @forImmediateRedirect(allowed_methods = 'GET')
+ def add(self):
+ """Handle creation of a new fence."""
+
+ flash('It should be a dialog to add new fence here instead, but not'
+ 'implemented yet...', status='info')
+
+ @forImmediateRedirect(allowed_methods = 'GET')
+ def manage(self):
+ """Handle the nodes management."""
+
+ flash('Demo of managing nodes...', status='info')
diff --git a/luci/controllers/root.py b/luci/controllers/root.py
index 575ff3d..5db9856 100644
--- a/luci/controllers/root.py
+++ b/luci/controllers/root.py
@@ -18,7 +18,9 @@ from luci.lib.ricci_helpers import send_batch_to_hosts
from luci.lib.ricci_communicator import RicciCommunicator
import luci.lib.ricci_queries as rq
from luci.lib.form_helpers import string2id, id2string
-from luci.controllers.decorators import noAdditionalKeyArgs
+from luci.controllers.decorators import *
+from luci.controllers.fdom import FailoverController
+from luci.controllers.fence import FenceController
__all__ = ['RootController']
@@ -37,128 +39,18 @@ class RootController(BaseController):
must be wrapped around with :class:`tg.controllers.WSGIAppController`.
"""
- secc = SecureController()
- admin = Catwalk(model, DBSession)
+ # SUBCONTROLLERS
+ secc = SecureController()
+ admin = Catwalk(model, DBSession)
error = ErrorController()
+ failover = FailoverController()
+ fence = FenceController()
- ###########################################################################
- # STATIC DEMO DATA
- ###########################################################################
-
- # Nodes in the current cluster.
-
- class NodeConstants:
- NODE_ACTIVE = '0'
- NODE_UNKNOWN = '1'
- NODE_INACTIVE = '2'
-
- # Demo data for nodes (format is self-explained).
- nodes = {'NodeAlpha': {'status': NodeConstants.NODE_ACTIVE},
- 'NodeBeta': {'status': NodeConstants.NODE_ACTIVE},
- 'NodeDelta': {'status': NodeConstants.NODE_INACTIVE,
- 'msg': 'Something terrible'},
- 'NodeGamma': {'status': NodeConstants.NODE_ACTIVE},
- 'NodeEpsilon': {'status': NodeConstants.NODE_UNKNOWN}}
-
- # Services in the current cluster.
-
- # Demo data for services (format is self-explained).
- services = {'Service W': {'running': True,
- 'autostart': True,
- 'faildom': 'Failover1',
- 'node': 'NodeGamma'},
- 'Service Y': {'running': False,
- 'autostart': False,
- #'faildom': None,
- 'node': 'NodeAlpha'},
- 'Service X': {'running': True,
- 'autostart': True,
- 'faildom': 'Failover2',
- 'node': 'NodeBeta'},
- 'Service Z': {'running': False,
- 'autostart': True,
- #'faildom': None,
- 'node': 'NodeAlpha'}}
-
- # Failover domains in the current cluster.
-
- # Demo data for failover domains (format is self-explained).
- fdoms = {'Failover1':{'prioritized': False,
- 'restricted': True,
- 'failback': False,
- 'services': ('Service Y',
- 'Service X'),
- 'nodes': {'NodeAlpha': 1,
- 'NodeBeta': 2}},
- 'Failover2':{'prioritized': True,
- 'restricted': True,
- 'failback': False,
- 'services': ('Service X',
- 'Service Y',
- 'Service Z'),
- 'nodes': {'NodeAlpha': 1,
- 'NodeBeta': 2,
- 'NodeEpsilon': 0}},
- 'FDOM a': {'prioritized': True,
- 'restricted': False,
- 'failback': False,
- 'services': ('Service W',
- 'Service Z'),
- 'nodes': {'NodeBeta': 2,
- 'NodeGamma': 0}},
- 'FDOM b': {'prioritized': True,
- 'restricted': False,
- 'failback': False,
- 'services': ('Service Y'),
- 'nodes': {'NodeDelta': 0,
- 'NodeGamma': 0}},
- 'FDOM c': {'prioritized': True,
- 'restricted': True,
- 'failback': True,
- 'services': ('Service Z',
- 'Service X',
- 'Service W'),
- 'nodes': {'NodeAlpha': 1,
- 'NodeEpsilon': 0}}}
-
- # Shared fences in the current cluster.
-
- # Demo data for fences (format is self-explained).
- fences = { 'Fence A':{'type': 'iLO',
- 'host': 'hostname.host.org',
- 'ip': '123.123.78.90',
- 'port': 6666,
- 'nodes': {'NodeDelta': 1}},
- 'Fence B':{'type': 'APC Power Device',
- 'host': 'fenceb.host.org',
- 'ip': '123.123.78.11',
- 'port': 6667,
- 'nodes': {'NodeAlpha': 1,
- 'NodeDelta': 2}},
- 'Fence C':{'type': 'Virtual Machine',
- 'host': 'fencec.host.org',
- 'ip': '123.123.78.156',
- 'port': 11023,
- 'nodes': {'NodeAlpha': 1,
- 'NodeBeta': 2,
- 'NodeGamma': 2,
- 'NodeDelta': 2,
- 'NodeEpsilon': 2}},
- 'Fence D':{'type': 'SCSI Reservation',
- 'host': 'fenced.host.org',
- 'ip': '123.123.78.35',
- 'port': 5487,
- 'nodes': {'NodeAlpha': 2,
- 'NodeBeta': 1,
- 'NodeGamma': 2}}}
-
-
- ###########################################################################
- # METHODS OF THE CONTROLLER
- ###########################################################################
+
+ # METHODS OF THE ROOT CONTROLLER
@expose('luci.templates.index')
def index(self):
@@ -170,7 +62,6 @@ class RootController(BaseController):
"""Handle the 'about' page."""
return dict(page='about')
-
# TODO: Automatically generated/experimental methods? Perhaps clean needed.
@expose('luci.templates.authentication')
@@ -226,8 +117,7 @@ class RootController(BaseController):
# Create cluster part.
- @expose('luci.templates.create')
- @noAdditionalKeyArgs
+ @forPageShowWithUrlCorrection('luci.templates.create')
def create(self, name=""):
return dict()
@@ -281,179 +171,3 @@ class RootController(BaseController):
redirect('/create')
-
- # Failover Domains part.
-
- @expose('luci.templates.fdom')
- @noAdditionalKeyArgs
- def fdom(self, name=None):
- """Handle 'failover domains' page."""
- details = None
- fdoms_overview = [[k,v['prioritized'],v['restricted'],v['failback']]
- for k,v in self.fdoms.iteritems()]
-
- # Check whether we are able to display details to selected failover
- # domain (and if the given failover domain really exists).
- if name:
- if self.fdoms.has_key(name):
- details = self.fdoms[name]
- else:
- redirect('/fdom')
-
- return dict(name=name, fdoms=fdoms_overview, details=details,
- services=self.services, nodes=self.nodes,
- nodeconst=self.NodeConstants,
- string2id=string2id, id2string=id2string)
-
- @expose()
- def fdom_delete(self, name=None, **kw):
- """Handle removal of selected failover domain(s)."""
- if name:
- which = [name]
- else:
- which = [id2string(s) for s in kw.iterkeys()]
-
- if not which:
- flash(_('Nothing was chosen!'), status='warning')
- else:
- flash(_('Deleting selected failover domain(s)... %s') %
- (", ".join(which)), status='info')
- for fdom in which:
- if self.fdoms.has_key(fdom):
- del self.fdoms[fdom]
- else:
- # If the luci's data are consistent this should never happen.
- flash(_('Internal error'), status='error')
- break
-
- redirect(request.referer or '/fdom')
-
- @expose()
- #@expose('luci.templates.fdom_add_form')
- @noAdditionalKeyArgs
- def fdom_add(self):
- """Handle creation of a new failover domain."""
- flash('It should be a dialog to add new failover domain here instead,'
- 'but not implemented yet...', status='info')
- redirect('/fdom')
- # following code is only my experiment, how to continue:
- #tmpl_context.form = create_fdom_add_form
- #return dict()
-
- @expose()
- def fdom_update_props(self, name=None, **kw):
- """Handle changes in a failover domain -- properties."""
- if not name:
- redirect(request.referer or '/fdom')
-
- fdom = id2string(name)
- if self.fdoms.has_key(fdom):
- for attr in ['prioritized', 'restricted', 'failback']:
- if kw.has_key(attr):
- self.fdoms[fdom][attr] = True
- else:
- self.fdoms[fdom][attr] = False
- flash(_('Updating properties for %s') % fdom, status='info')
- else:
- # If the luci's data are consistent this should never happen.
- flash(_('Internal error'), status='error')
-
- redirect(request.referer or '/fdom')
-
- @expose()
- def fdom_update_members(self, name=None, **kw):
- """Handle changes in a failover domain -- member nodes."""
- if not name:
- redirect(request.referer or '/fdom')
-
- fdom = id2string(name)
- if self.fdoms.has_key(fdom):
- # Clear the dictionary of member nodes and start from scratch.
- self.fdoms[fdom]['nodes'].clear()
- nodes = map(lambda id_check: id2string(id_check.replace('.check', '')),
- filter(lambda id_str: id_str.endswith('.check'),
- kw.iterkeys()))
- flash(_('Updating members for %s') % fdom, status='info')
- for node in nodes:
- if self.nodes.has_key(node):
- self.fdoms[fdom]['nodes'][node] = \
- kw.get(string2id(node) + '.priority', 0)
- else:
- # If the luci's data are consistent this should never happen.
- flash(_('Internal error'), status='error')
- break
-
- redirect(request.referer or '/fdom')
-
- @expose()
- @noAdditionalKeyArgs
- def fdom_service(self):
- """Handle creation of a new service."""
- flash('Demo of adding a service...', status='info')
- redirect('/fdom')
-
-
- # Fences part.
-
- @expose('luci.templates.fence')
- @noAdditionalKeyArgs
- def fence(self, name=None):
- """Handle 'fences' page."""
- details = None
- fences_overview = \
- [[k,v['type'],
- len(v['nodes'])==1
- and (v['nodes'].keys()[0],
- self.nodes.get(v['nodes'].keys()[0],
- {'status': self.NodeConstants.NODE_UNKNOWN}))
- or len(v['nodes']),
- v['ip']] for k,v in self.fences.iteritems()]
-
- # Check whether we are able to display details to selected fence.
- if name:
- if self.fences.has_key(name):
- details = self.fences[name]
- else:
- redirect('/fence')
-
- return dict(name=name, fences=fences_overview, details=details,
- nodes=self.nodes, nodeconst=self.NodeConstants,
- string2id=string2id, id2string=id2string)
-
- @expose()
- def fence_delete(self, name=None, **kw):
- """Handle removal of selected fence(s)."""
- if name:
- which = [name]
- else:
- which = [id2string(s) for s in kw.iterkeys()]
-
- if not which:
- flash(_('Nothing was chosen!'), status='warning')
- else:
- flash(_('Deleting selected fence(s)... %s')
- % (", ".join(which)), status='info')
- for fence in which:
- if self.fences.has_key(fence):
- del self.fences[fence]
- else:
- # If the luci's data are consistent this should never happen.
- flash(_('Internal error'), status='error')
- break
-
- redirect(request.referer or '/fence')
-
- @expose()
- @noAdditionalKeyArgs
- def fence_add(self):
- """Handle creation of a new fence."""
- flash('It should be a dialog to add new fence here instead, but not'
- 'implemented yet...')
- redirect('/fence', status='info')
-
- @expose()
- @noAdditionalKeyArgs
- def fence_manage(self):
- """Handle the nodes management."""
- flash('Demo of managing nodes...', status='info')
- redirect('/fence')
diff --git a/luci/templates/fdom.html b/luci/templates/fdom.html
index f0a3bd1..8e860ef 100644
--- a/luci/templates/fdom.html
+++ b/luci/templates/fdom.html
@@ -14,10 +14,10 @@
<body onload="onLoad()">
- <form action="${tg.url('/fdom_delete')}" method="post">
+ <form action="${tg.url('delete')}" method="post">
<div id="toolbar">
<input type="submit" value="delete" class="toolbar_button" id="tb_delete"/>
- <a href="${tg.url('/fdom_add')}" class="toolbar_button" id="tb_add">add</a>
+ <a href="${tg.url('add')}" class="toolbar_button" id="tb_add">add</a>
</div>
<!--! OVERVIEW SECTION. -->
@@ -44,7 +44,7 @@
py:with="identifier=string2id(fdom[0])">
<td class="checkbox"><input type="checkbox" name="${identifier}"/></td>
<td class="icon"></td>
- <td class="main_id"><a href="${tg.url('/fdom?name=' + fdom[0])}">${fdom[0]}</a></td>
+ <td class="main_id"><a href="${tg.url('?name=' + fdom[0])}">${fdom[0]}</a></td>
<!--! TODO: Is the following column necessary? -->
<td class="fdom_tlist_enabled">
<input type="checkbox" disabled="true" py:attrs="False and {'checked': 'checked'} or None"/>
@@ -63,8 +63,9 @@
<py:when test="None">
<div id="details_header">
- <div id="not_selected">
- Select an item to view details
+ <div id="not_selected" py:choose="len(fdoms)">
+ <py:when test="0">No item to display</py:when>
+ <py:otherwise>Select an item to view details</py:otherwise>
</div>
</div>
</py:when>
@@ -75,14 +76,14 @@
<div id="details_header">
<h3 py:content="name">Failover name</h3>
<div id="details_header_buttons">
- <a href="${tg.url('/fdom_delete?name=' + name)}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
+ <a href="${tg.url('delete?name=' + name)}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
</div>
</div>
<!--! DETAILS - attributes section. -->
<div class="details_section">
<div class="details_inner">
- <form action="${tg.url('/fdom_update_props')}" method="post">
+ <form action="${tg.url('update_props')}" method="post">
<input type="hidden" name="name" value="${string2id(name)}"/>
<input type="submit" value="Update Properties" class="float_button"/>
<table id="fdom_tattr">
@@ -120,7 +121,7 @@
<!--! DETAILS - services section. -->
<div class="details_section">
- <a href="${tg.url('/fdom_service')}" class="float_button">Add a Service</a>
+ <a href="${tg.url('service')}" class="float_button">Add a Service</a>
<h4>Services</h4>
<div class="details_inner">
<table id="fdom_tservices">
@@ -144,7 +145,7 @@
<!--! DETAILS - members section. -->
<div class="details_section">
- <form action="${tg.url('/fdom_update_members')}" method="post">
+ <form action="${tg.url('update_members')}" method="post">
<input type="hidden" name="name" value="${string2id(name)}"/>
<input type="submit" value="Update Settings" class="float_button"/>
<h4>Members</h4>
diff --git a/luci/templates/fence.html b/luci/templates/fence.html
index 7fbd9fe..0f2af4a 100644
--- a/luci/templates/fence.html
+++ b/luci/templates/fence.html
@@ -13,11 +13,11 @@
<body>
- <form action="${tg.url('/fence_delete')}" method="post">
+ <form action="${tg.url('delete')}" method="post">
<div id="toolbar">
<a href="${tg.url(request.path_qs)}" class="toolbar_button" id="tb_update">update</a>
<input type="submit" value="delete" class="toolbar_button" id="tb_delete" />
- <a href="${tg.url('/fence_add')}" class="toolbar_button" id="tb_add">add</a>
+ <a href="${tg.url('add')}" class="toolbar_button" id="tb_add">add</a>
</div>
<!--! OVERVIEW SECTION. -->
@@ -45,7 +45,7 @@
<td class="checkbox"><input type="checkbox" name="${identifier}"/></td>
<!--! If the fence is shared, display appropriate icon. -->
<td class="icon"><img py:if="type(fence[2]) is not tuple" src="${tg.url('/images/global_11x11_black.png')}" alt="shared fence"/></td>
- <td class="main_id"><a href="${tg.url('/fence?name=' + fence[0])}">${fence[0]}</a></td>
+ <td class="main_id"><a href="${tg.url('?name=' + fence[0])}">${fence[0]}</a></td>
<td class="fence_tlist_type">${fence[1]}</td>
<!--! Branch according to whether the fence is local or shared. -->
<py:choose test="type(fence[2]) is tuple">
@@ -73,8 +73,9 @@
<py:when test="None">
<div id="details_header">
- <div id="not_selected">
- Select an item to view details
+ <div id="not_selected" py:choose="len(fences)">
+ <py:when test="0">No item to display</py:when>
+ <py:otherwise>Select an item to view details</py:otherwise>
</div>
</div>
</py:when>
@@ -85,7 +86,7 @@
<div id="details_header">
<h3 py:content="name">Fence A</h3>
<div id="details_header_buttons">
- <a href="${tg.url('/fence_delete?name=' + name)}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
+ <a href="${tg.url('delete?name=' + name)}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
<a href="${tg.url(request.path_qs)}" id="dh_update" title="refresh"><span class="hide">refresh</span></a>
</div>
type <span id="fence_details_type">${details['type']}</span>
@@ -102,7 +103,7 @@
<!--! DETAILS - nodes section. -->
<div class="details_section">
- <a href="${tg.url('/fence_manage')}" class="float_button">Manage Nodes</a>
+ <a href="${tg.url('manage')}" class="float_button">Manage Nodes</a>
<h4>Nodes</h4>
<div class="details_inner">
<table id="fence_tnodes">
diff --git a/luci/templates/master.html b/luci/templates/master.html
index 6ec3715..68d5104 100644
--- a/luci/templates/master.html
+++ b/luci/templates/master.html
@@ -18,8 +18,8 @@
${header()}
<ul id="mainmenu">
<li><a href="${tg.url('/about')}" class="${('', 'active')[defined('page') and page==page=='about']}">About</a></li>
- <li><a href="${tg.url('/fdom')}">Failovers</a></li>
- <li><a href="${tg.url('/fence')}">Fences</a></li>
+ <li><a href="${tg.url('/failover/')}">Failovers</a></li>
+ <li><a href="${tg.url('/fence/')}">Fences</a></li>
<span py:if="tg.auth_stack_enabled" py:strip="True">
<li py:if="not request.identity" id="login" class="loginlogout"><a href="${tg.url('/login')}">Login</a></li>
<li py:if="request.identity" id="login" class="loginlogout"><a href="${tg.url('/logout_handler')}">Logout</a></li>
14 years, 10 months
[luci] Luci: some unneeded files removed, script for deploying added
by Jan Pokorný
commit 717443721d861dee3cee1111e04f754d48434daa
Author: Jan Pokorny <jpokorny(a)redhat.com>
Date: Wed Aug 26 14:43:07 2009 +0200
Luci: some unneeded files removed, script for deploying added
Database is automatically generated during application setup/deploy phase
as well as 'luci.egg-info' directory.
deploy.sh | 12 +++
devdata.db | Bin 18432 -> 0 bytes
luci.egg-info/PKG-INFO | 10 ---
luci.egg-info/SOURCES.txt | 145 ------------------------------------
luci.egg-info/dependency_links.txt | 1 -
luci.egg-info/entry_points.txt | 7 --
luci.egg-info/paster_plugins.txt | 4 -
luci.egg-info/requires.txt | 7 --
luci.egg-info/top_level.txt | 1 -
luci/lib/form_helpers.py | 12 +--
10 files changed, 17 insertions(+), 182 deletions(-)
---
diff --git a/deploy.sh b/deploy.sh
new file mode 100755
index 0000000..54ddbe3
--- /dev/null
+++ b/deploy.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Simple script for deploying luci (prepare application + database
+# and start serving it).
+#
+# You may need to make sure that you are running this script from
+# tg2env environment (activated with 'source bin/activate' command
+# from within tg2env directory).
+
+python setup.py install
+paster setup-app development.ini
+paster serve development.ini
diff --git a/luci/lib/form_helpers.py b/luci/lib/form_helpers.py
index 6f8481f..5044af4 100644
--- a/luci/lib/form_helpers.py
+++ b/luci/lib/form_helpers.py
@@ -7,7 +7,7 @@ from string import maketrans
__all__ = ['string2id', 'id2string']
-STARTING_CHAR = 'Z'
+STARTING_CHAR = 'L' # First character has to be [A-Za-z].
B64_CONV_CHARS = "-_:"
B64_ORIG_CHARS = "=+/"
@@ -28,9 +28,8 @@ def string2id(s):
s String to convert.
"""
- a = 'Z' + b64encode(s.encode('utf-8')).translate(maketrans(B64_ORIG_CHARS,B64_CONV_CHARS))
- print 'string2id:\t' + "'" + a + "'"
- return a
+ encoded = b64encode(s.encode('utf-8'))
+ return STARTING_CHAR + encoded.translate(maketrans(B64_ORIG_CHARS,B64_CONV_CHARS))
def id2string(s):
"""Receive the string previously encoded by string2id().
@@ -38,6 +37,5 @@ def id2string(s):
Keyword arguments:
s String to convert back to human readable format.
"""
- a = b64decode(str(s)[1:].translate(maketrans(B64_CONV_CHARS,B64_ORIG_CHARS))).decode('utf-8')
- print 'id2string:\t' + "'" + a + "'"
- return a
+ back = b64decode(str(s)[1:].translate(maketrans(B64_CONV_CHARS,B64_ORIG_CHARS)))
+ return back.decode('utf-8')
14 years, 10 months
[luci] Luci: Failovers + Fences (mainly internal changes)
by Jan Pokorný
commit e2b56e3bd1ba81e2e242650bdddeba003a8b045e
Author: Jan Pokorny <jpokorny(a)redhat.com>
Date: Tue Aug 25 20:08:08 2009 +0200
Luci: Failovers + Fences (mainly internal changes)
Added support for UTF-8 in some places, following patch for Genshi 0.5.1 should be applied (http://file.brq.redhat.com/~jpokorny/patches/genshi/genshi_0.5.1_utf8_pat...):
--- base.py 2009-08-24 18:10:57.000000000 +0200
+++ base.py 2009-08-24 17:52:28.000000000 +0200
@@ -513,7 +513,10 @@
value = [x for x in values if x is not None]
if not value:
continue
- new_attrs.append((name, u''.join(value)))
+ try:
+ new_attrs.append((name, u''.join(value)))
+ except UnicodeDecodeError, e:
+ new_attrs.append((name, u''.join([x.decode('utf-8') for x in value])))
yield kind, (tag, Attrs(new_attrs)), pos
elif kind is EXPR:
luci/controllers/decorators.py | 24 +++
luci/controllers/root.py | 425 ++++++++++++++++++++++++++-------------
luci/lib/form_helpers.py | 43 ++++
luci/public/css/shared.css | 14 ++-
luci/public/images/question.png | Bin 0 -> 565 bytes
luci/public/js/fdom.js | 39 ++++
luci/templates/fdom.html | 88 ++++++---
luci/templates/fence.html | 71 +++++--
luci/templates/master.html | 2 +
9 files changed, 513 insertions(+), 193 deletions(-)
---
diff --git a/luci/controllers/decorators.py b/luci/controllers/decorators.py
new file mode 100644
index 0000000..67433da
--- /dev/null
+++ b/luci/controllers/decorators.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+"""Decorators used in the main controller."""
+
+from tg import url, redirect, request
+
+def noAdditionalKeyArgs(fn):
+ """Decorator for stripping off additional keyword args & purifying URL."""
+ def purified(*args, **kw):
+ key_args = fn.func_code.co_varnames # All keyword args of original fn.
+ redir = False
+ for keyw in kw.keys():
+ # If some keyword arg that is not supported by original function
+ # was found, remove it from keyword args dict and set
+ # the redirect flag for redirection to purified URL.
+ if not keyw in key_args:
+ del kw[keyw]
+ redir = True
+ if redir:
+ redirect(url(request.path, **kw))
+ else:
+ return fn(*args, **kw)
+
+ return purified
+
diff --git a/luci/controllers/root.py b/luci/controllers/root.py
index 068803e..575ff3d 100644
--- a/luci/controllers/root.py
+++ b/luci/controllers/root.py
@@ -6,6 +6,7 @@ from tg import tmpl_context
from pylons.i18n import ugettext as _, lazy_ugettext as l_
from catwalk.tg2 import Catwalk
from repoze.what import predicates
+from sys import stderr
from luci.lib.base import BaseController
from luci.model import DBSession, metadata
@@ -16,8 +17,8 @@ from luci.controllers.secure import SecureController
from luci.lib.ricci_helpers import send_batch_to_hosts
from luci.lib.ricci_communicator import RicciCommunicator
import luci.lib.ricci_queries as rq
-from sys import stderr
-
+from luci.lib.form_helpers import string2id, id2string
+from luci.controllers.decorators import noAdditionalKeyArgs
__all__ = ['RootController']
@@ -42,63 +43,10 @@ class RootController(BaseController):
error = ErrorController()
- @expose('luci.templates.create')
- def create(self, name=""):
- return dict()
-
- @expose()
- def cluster_create(self, **kw):
- errors = ()
- cluster_name = kw.get('clustername')
- enable_storage = kw.get('enable_storage')
- reboot_nodes = kw.get('reboot_nodes')
- download_pkgs = kw.get('download_pkgs')
-
- nodes = [
- [ kw.get('__SYSTEM0_addr'), kw.get('__SYSTEM0_passwd') ],
- [ kw.get('__SYSTEM1_addr'), kw.get('__SYSTEM1_passwd') ],
- [ kw.get('__SYSTEM2_addr'), kw.get('__SYSTEM2_passwd') ]
- ]
- node_list = [ i[0] for i in nodes ]
- stderr.write('nodes: %s\n' % str(node_list))
-
- if not cluster_name:
- flash(_('No cluster name was given'))
- redirect('/create')
- if len(cluster_name) > 15:
- flash(_('Cluster names must be less than 16 characters long'))
- redirect('/create')
-
- for node in nodes:
- rc = RicciCommunicator(node[0], enforce_trust=False)
- rc.trust()
- rc.auth(node[1])
- if not rc.authed():
- errors.append('Authentication to node %s failed' % node[0])
- break
- else:
- rc = RicciCommunicator(node[0])
-
- cur_cluster_name = rc.cluster_info()[0]
- if cur_cluster_name:
- errors.append('%s is already a member of a cluster %s' \
- % (node[0], cur_cluster_name))
- break
-
- if len(errors) > 0:
- flash('The following errors occurred: %s' % str(errors))
- redirect("/create")
-
- ret = send_batch_to_hosts(node_list, 10, rq.create_cluster,
- 'rhel5', cluster_name, cluster_name,
- node_list, True, True, enable_storage, False,
- download_pkgs, None, reboot_nodes)
-
- redirect('/create')
###########################################################################
- # Static demo data.
-
+ # STATIC DEMO DATA
+ ###########################################################################
# Nodes in the current cluster.
@@ -107,71 +55,110 @@ class RootController(BaseController):
NODE_UNKNOWN = '1'
NODE_INACTIVE = '2'
- # Temporary data for failover domains, format:
- # {'nodename': {'status': NODE_X, 'msg': 'optional status description'}, ...}
+ # Demo data for nodes (format is self-explained).
nodes = {'NodeAlpha': {'status': NodeConstants.NODE_ACTIVE},
'NodeBeta': {'status': NodeConstants.NODE_ACTIVE},
- 'NodeDelta': {'status': NodeConstants.NODE_UNKNOWN,
+ 'NodeDelta': {'status': NodeConstants.NODE_INACTIVE,
'msg': 'Something terrible'},
'NodeGamma': {'status': NodeConstants.NODE_ACTIVE},
- 'NodeEpsilon': {'status': NodeConstants.NODE_UNKNOWN,
- 'msg': 'Something terrible'}}
-
- # Failover domains.
-
- # Temporary data for failover domains, format:
- # {'name': (prioritized, restricted, failback, [('service', is ok?), ...],
- # {'member': priority, ...}), ...}
- fdoms = {'Failover1':(False, True, False,
- [('SAP', True),
- ('Oracle', True)],
- {'NodeAlpha': 1,
- 'NodeBeta': 2}),
- 'Failover2':(True, True, False,
- [('Service X', True),
- ('Service Y', False),
- ('Service Z', True)],
- {'NodeAlpha': 1,
- 'NodeBeta': 2,
- 'NodeEpsilon': 0}),
- 'FDOM a': (True, False, False,
- [('NFS', True),
- ('Samba', True)],
- {'NodeBeta': 2,
- 'NodeGamma': 0}),
- 'FDOM b': (True, False, False,
- [('Apache', True)],
- {'NodeDelta': 0,
- 'NodeGamma': 0}),
- 'FDOM c': (True, True, True,
- [('Tomcat', True),
- ('PostgreSQL', False),
- ('LVM', True)],
- {'NodeAlpha': 1,
- 'NodeEpsilon': 0})}
-
- # Shared fences.
-
- # Temporary data for fences, format:
- # {'name': (type, hostname, address, port, [("node", status, has primary priority?), ...])
-
- fences = { 'Fence A':('iLO', 'hostname.host.org', '123.123.78.90', 6666,
- {'NodeDelta': 1}),
- 'Fence B':('APC Power Device', 'fenceb.host.org', '123.123.78.11', 6667,
- {'NodeAlpha': 1,
- 'NodeDelta': 2}),
- 'Fence C':('Virtual Machine', 'fencec.host.org', '123.123.78.156', 11023,
- {'NodeAlpha': 1,
- 'NodeBeta': 2,
- 'NodeGamma': 2,
- 'NodeDelta': 2}),
- 'Fence D':('SCSI Reservation', 'fenced.host.org', '123.123.78.35', 5487,
- {'NodeAlpha': 2,
- 'NodeBeta': 1,
- 'NodeGamma': 2})}
+ 'NodeEpsilon': {'status': NodeConstants.NODE_UNKNOWN}}
+
+ # Services in the current cluster.
+
+ # Demo data for services (format is self-explained).
+ services = {'Service W': {'running': True,
+ 'autostart': True,
+ 'faildom': 'Failover1',
+ 'node': 'NodeGamma'},
+ 'Service Y': {'running': False,
+ 'autostart': False,
+ #'faildom': None,
+ 'node': 'NodeAlpha'},
+ 'Service X': {'running': True,
+ 'autostart': True,
+ 'faildom': 'Failover2',
+ 'node': 'NodeBeta'},
+ 'Service Z': {'running': False,
+ 'autostart': True,
+ #'faildom': None,
+ 'node': 'NodeAlpha'}}
+
+ # Failover domains in the current cluster.
+
+ # Demo data for failover domains (format is self-explained).
+ fdoms = {'Failover1':{'prioritized': False,
+ 'restricted': True,
+ 'failback': False,
+ 'services': ('Service Y',
+ 'Service X'),
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeBeta': 2}},
+ 'Failover2':{'prioritized': True,
+ 'restricted': True,
+ 'failback': False,
+ 'services': ('Service X',
+ 'Service Y',
+ 'Service Z'),
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeBeta': 2,
+ 'NodeEpsilon': 0}},
+ 'FDOM a': {'prioritized': True,
+ 'restricted': False,
+ 'failback': False,
+ 'services': ('Service W',
+ 'Service Z'),
+ 'nodes': {'NodeBeta': 2,
+ 'NodeGamma': 0}},
+ 'FDOM b': {'prioritized': True,
+ 'restricted': False,
+ 'failback': False,
+ 'services': ('Service Y'),
+ 'nodes': {'NodeDelta': 0,
+ 'NodeGamma': 0}},
+ 'FDOM c': {'prioritized': True,
+ 'restricted': True,
+ 'failback': True,
+ 'services': ('Service Z',
+ 'Service X',
+ 'Service W'),
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeEpsilon': 0}}}
+
+ # Shared fences in the current cluster.
+
+ # Demo data for fences (format is self-explained).
+ fences = { 'Fence A':{'type': 'iLO',
+ 'host': 'hostname.host.org',
+ 'ip': '123.123.78.90',
+ 'port': 6666,
+ 'nodes': {'NodeDelta': 1}},
+ 'Fence B':{'type': 'APC Power Device',
+ 'host': 'fenceb.host.org',
+ 'ip': '123.123.78.11',
+ 'port': 6667,
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeDelta': 2}},
+ 'Fence C':{'type': 'Virtual Machine',
+ 'host': 'fencec.host.org',
+ 'ip': '123.123.78.156',
+ 'port': 11023,
+ 'nodes': {'NodeAlpha': 1,
+ 'NodeBeta': 2,
+ 'NodeGamma': 2,
+ 'NodeDelta': 2,
+ 'NodeEpsilon': 2}},
+ 'Fence D':{'type': 'SCSI Reservation',
+ 'host': 'fenced.host.org',
+ 'ip': '123.123.78.35',
+ 'port': 5487,
+ 'nodes': {'NodeAlpha': 2,
+ 'NodeBeta': 1,
+ 'NodeGamma': 2}}}
- ###########################################################################
+ ###########################################################################
+ # METHODS OF THE CONTROLLER
+ ###########################################################################
@expose('luci.templates.index')
def index(self):
@@ -183,6 +170,9 @@ class RootController(BaseController):
"""Handle the 'about' page."""
return dict(page='about')
+
+ # TODO: Automatically generated/experimental methods? Perhaps clean needed.
+
@expose('luci.templates.authentication')
def auth(self):
"""Display some information about auth* on this application."""
@@ -234,87 +224,236 @@ class RootController(BaseController):
redirect(came_from)
+ # Create cluster part.
+
+ @expose('luci.templates.create')
+ @noAdditionalKeyArgs
+ def create(self, name=""):
+ return dict()
+
+ @expose()
+ def cluster_create(self, **kw):
+ errors = ()
+ cluster_name = kw.get('clustername')
+ enable_storage = kw.get('enable_storage')
+ reboot_nodes = kw.get('reboot_nodes')
+ download_pkgs = kw.get('download_pkgs')
+
+ nodes = [
+ [ kw.get('__SYSTEM0_addr'), kw.get('__SYSTEM0_passwd') ],
+ [ kw.get('__SYSTEM1_addr'), kw.get('__SYSTEM1_passwd') ],
+ [ kw.get('__SYSTEM2_addr'), kw.get('__SYSTEM2_passwd') ]
+ ]
+ node_list = [ i[0] for i in nodes ]
+ stderr.write('nodes: %s\n' % str(node_list))
+
+ if not cluster_name:
+ flash(_('No cluster name was given'))
+ redirect('/create')
+ if len(cluster_name) > 15:
+ flash(_('Cluster names must be less than 16 characters long'))
+ redirect('/create')
+
+ for node in nodes:
+ rc = RicciCommunicator(node[0], enforce_trust=False)
+ rc.trust()
+ rc.auth(node[1])
+ if not rc.authed():
+ errors.append('Authentication to node %s failed' % node[0])
+ break
+ else:
+ rc = RicciCommunicator(node[0])
+
+ cur_cluster_name = rc.cluster_info()[0]
+ if cur_cluster_name:
+ errors.append('%s is already a member of a cluster %s' \
+ % (node[0], cur_cluster_name))
+ break
+
+ if len(errors) > 0:
+ flash('The following errors occurred: %s' % str(errors))
+ redirect("/create")
+
+ ret = send_batch_to_hosts(node_list, 10, rq.create_cluster,
+ 'rhel5', cluster_name, cluster_name,
+ node_list, True, True, enable_storage, False,
+ download_pkgs, None, reboot_nodes)
+
+ redirect('/create')
+
+
# Failover Domains part.
@expose('luci.templates.fdom')
- def fdom(self, name=""):
+ @noAdditionalKeyArgs
+ def fdom(self, name=None):
"""Handle 'failover domains' page."""
details = None
- fdoms_overview = [[k,v[0],v[1],v[2]] for k,v in self.fdoms.iteritems()]
+ fdoms_overview = [[k,v['prioritized'],v['restricted'],v['failback']]
+ for k,v in self.fdoms.iteritems()]
# Check whether we are able to display details to selected failover
- # domain.
- if name != "":
+ # domain (and if the given failover domain really exists).
+ if name:
if self.fdoms.has_key(name):
details = self.fdoms[name]
+ else:
+ redirect('/fdom')
+
return dict(name=name, fdoms=fdoms_overview, details=details,
- nodes=self.nodes)
+ services=self.services, nodes=self.nodes,
+ nodeconst=self.NodeConstants,
+ string2id=string2id, id2string=id2string)
@expose()
- def fdom_delete(self):
+ def fdom_delete(self, name=None, **kw):
"""Handle removal of selected failover domain(s)."""
- flash('Demo of deleting...')
+ if name:
+ which = [name]
+ else:
+ which = [id2string(s) for s in kw.iterkeys()]
+
+ if not which:
+ flash(_('Nothing was chosen!'), status='warning')
+ else:
+ flash(_('Deleting selected failover domain(s)... %s') %
+ (", ".join(which)), status='info')
+ for fdom in which:
+ if self.fdoms.has_key(fdom):
+ del self.fdoms[fdom]
+ else:
+ # If the luci's data are consistent this should never happen.
+ flash(_('Internal error'), status='error')
+ break
+
redirect(request.referer or '/fdom')
@expose()
#@expose('luci.templates.fdom_add_form')
+ @noAdditionalKeyArgs
def fdom_add(self):
"""Handle creation of a new failover domain."""
- flash('Demo of adding...')
- redirect('/fence')
+ flash('It should be a dialog to add new failover domain here instead,'
+ 'but not implemented yet...', status='info')
+ redirect('/fdom')
# following code is only my experiment, how to continue:
#tmpl_context.form = create_fdom_add_form
#return dict()
@expose()
- def fdom_update(self):
- """Handle changes in a failover domain."""
- flash('Demo of updating properties...')
+ def fdom_update_props(self, name=None, **kw):
+ """Handle changes in a failover domain -- properties."""
+ if not name:
+ redirect(request.referer or '/fdom')
+
+ fdom = id2string(name)
+ if self.fdoms.has_key(fdom):
+ for attr in ['prioritized', 'restricted', 'failback']:
+ if kw.has_key(attr):
+ self.fdoms[fdom][attr] = True
+ else:
+ self.fdoms[fdom][attr] = False
+ flash(_('Updating properties for %s') % fdom, status='info')
+ else:
+ # If the luci's data are consistent this should never happen.
+ flash(_('Internal error'), status='error')
+
+ redirect(request.referer or '/fdom')
+
+ @expose()
+ def fdom_update_members(self, name=None, **kw):
+ """Handle changes in a failover domain -- member nodes."""
+ if not name:
+ redirect(request.referer or '/fdom')
+
+ fdom = id2string(name)
+ if self.fdoms.has_key(fdom):
+ # Clear the dictionary of member nodes and start from scratch.
+ self.fdoms[fdom]['nodes'].clear()
+ nodes = map(lambda id_check: id2string(id_check.replace('.check', '')),
+ filter(lambda id_str: id_str.endswith('.check'),
+ kw.iterkeys()))
+ flash(_('Updating members for %s') % fdom, status='info')
+ for node in nodes:
+ if self.nodes.has_key(node):
+ self.fdoms[fdom]['nodes'][node] = \
+ kw.get(string2id(node) + '.priority', 0)
+ else:
+ # If the luci's data are consistent this should never happen.
+ flash(_('Internal error'), status='error')
+ break
+
redirect(request.referer or '/fdom')
@expose()
+ @noAdditionalKeyArgs
def fdom_service(self):
"""Handle creation of a new service."""
- flash('Demo of adding a service...')
+ flash('Demo of adding a service...', status='info')
redirect('/fdom')
# Fences part.
@expose('luci.templates.fence')
- def fence(self, name=""):
+ @noAdditionalKeyArgs
+ def fence(self, name=None):
"""Handle 'fences' page."""
details = None
fences_overview = \
- [[k,v[0],
- len(v[4])==1
- and (v[4].keys()[0],
- self.nodes.get(v[4].keys()[0],
+ [[k,v['type'],
+ len(v['nodes'])==1
+ and (v['nodes'].keys()[0],
+ self.nodes.get(v['nodes'].keys()[0],
{'status': self.NodeConstants.NODE_UNKNOWN}))
- or len(v[4]),
- v[2]] for k,v in self.fences.iteritems()]
+ or len(v['nodes']),
+ v['ip']] for k,v in self.fences.iteritems()]
# Check whether we are able to display details to selected fence.
- if name != "":
+ if name:
if self.fences.has_key(name):
details = self.fences[name]
+ else:
+ redirect('/fence')
+
return dict(name=name, fences=fences_overview, details=details,
- nodes=self.nodes, nodeconst=self.NodeConstants)
+ nodes=self.nodes, nodeconst=self.NodeConstants,
+ string2id=string2id, id2string=id2string)
@expose()
- def fence_delete(self):
+ def fence_delete(self, name=None, **kw):
"""Handle removal of selected fence(s)."""
- flash('Demo of deleting...')
+ if name:
+ which = [name]
+ else:
+ which = [id2string(s) for s in kw.iterkeys()]
+
+ if not which:
+ flash(_('Nothing was chosen!'), status='warning')
+ else:
+ flash(_('Deleting selected fence(s)... %s')
+ % (", ".join(which)), status='info')
+ for fence in which:
+ if self.fences.has_key(fence):
+ del self.fences[fence]
+ else:
+ # If the luci's data are consistent this should never happen.
+ flash(_('Internal error'), status='error')
+ break
+
redirect(request.referer or '/fence')
@expose()
+ @noAdditionalKeyArgs
def fence_add(self):
"""Handle creation of a new fence."""
- flash('Demo of adding...')
- redirect('/fence')
+ flash('It should be a dialog to add new fence here instead, but not'
+ 'implemented yet...')
+ redirect('/fence', status='info')
@expose()
+ @noAdditionalKeyArgs
def fence_manage(self):
"""Handle the nodes management."""
- flash('Demo of managing nodes...')
+ flash('Demo of managing nodes...', status='info')
redirect('/fence')
diff --git a/luci/lib/form_helpers.py b/luci/lib/form_helpers.py
new file mode 100644
index 0000000..6f8481f
--- /dev/null
+++ b/luci/lib/form_helpers.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+"""HTML form's helpers."""
+
+from base64 import b64encode, b64decode
+from string import maketrans
+
+__all__ = ['string2id', 'id2string']
+
+STARTING_CHAR = 'Z'
+
+B64_CONV_CHARS = "-_:"
+B64_ORIG_CHARS = "=+/"
+
+def string2id(s):
+ """Convert the string to the form usable as an element's id or name.
+
+ 'Usable' means that it conforms (X)HTML requirements for the value
+ of 'id' and 'name' attribute of an element.
+
+ The implementation uses base64 encoding with some other additions;
+ references:
+ http://www.w3.org/TR/xhtml1/#C_8
+ http://www.w3.org/TR/html4/types.html#h-6.2
+ http://tools.ietf.org/html/rfc3548.html
+
+ Keyword arguments:
+ s String to convert.
+
+ """
+ a = 'Z' + b64encode(s.encode('utf-8')).translate(maketrans(B64_ORIG_CHARS,B64_CONV_CHARS))
+ print 'string2id:\t' + "'" + a + "'"
+ return a
+
+def id2string(s):
+ """Receive the string previously encoded by string2id().
+
+ Keyword arguments:
+ s String to convert back to human readable format.
+ """
+ a = b64decode(str(s)[1:].translate(maketrans(B64_CONV_CHARS,B64_ORIG_CHARS))).decode('utf-8')
+ print 'id2string:\t' + "'" + a + "'"
+ return a
diff --git a/luci/public/css/shared.css b/luci/public/css/shared.css
index 3ce0d12..c581eb7 100644
--- a/luci/public/css/shared.css
+++ b/luci/public/css/shared.css
@@ -54,8 +54,15 @@ a.float_button {
background-color: #d2edaa;
}
-.input_priority {
+.input_priority, .input_priority_disabled {
width: 3em;
+ border: thin solid #6d6d6d;
+ font-size: small;
+}
+
+.input_priority_disabled {
+ background-color: #f2f2f2;
+ cursor: default;
}
.icon {
@@ -83,6 +90,11 @@ a.float_button {
color: red;
}
+.entity_unknown {
+ color: #5b5a5b;
+ font-style: italic;
+}
+
.hide {
visibility: hidden;
overflow: hidden;
diff --git a/luci/public/images/question.png b/luci/public/images/question.png
new file mode 100644
index 0000000..50082ea
Binary files /dev/null and b/luci/public/images/question.png differ
diff --git a/luci/public/js/fdom.js b/luci/public/js/fdom.js
new file mode 100644
index 0000000..b8bac5f
--- /dev/null
+++ b/luci/public/js/fdom.js
@@ -0,0 +1,39 @@
+/*
+ * JS functions for Failover domains.
+ *
+ */
+
+
+/**
+ * On page load, disable the priority settings for the nodes that are not
+ * members of the current failover domain.
+ */
+function onLoad()
+ {
+ var input_elems = document.getElementsByTagName("input");
+ for (var i = input_elems.length - 1; i >= 0; i--)
+ {
+ var input_elem = input_elems[i];
+ if (input_elem.className == "input_priority_disabled")
+ {
+ input_elem.disabled = true;
+ input_elem.className = "input_priority";
+ }
+ }
+ }
+
+
+/**
+ * On 'member' checkbox change, enable/disable the priority settings
+ * for the node and change the style appropriately.
+ *
+ * @param elem_id Identifier of the checkbox being changed.
+ */
+function onCheckMember(elem_id)
+ {
+ var elem = document.getElementById(elem_id.replace(".check", ".priority"));
+ if (elem)
+ {
+ elem.disabled = elem.disabled ^ 1;
+ }
+ }
diff --git a/luci/templates/fdom.html b/luci/templates/fdom.html
index 4d588e0..f0a3bd1 100644
--- a/luci/templates/fdom.html
+++ b/luci/templates/fdom.html
@@ -9,9 +9,10 @@
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
<title>Failover domains</title>
+ <script type="text/javascript" src="${tg.url('/js/fdom.js')}"></script>
</head>
-<body>
+<body onload="onLoad()">
<form action="${tg.url('/fdom_delete')}" method="post">
<div id="toolbar">
@@ -19,7 +20,7 @@
<a href="${tg.url('/fdom_add')}" class="toolbar_button" id="tb_add">add</a>
</div>
- <!--! Overview section. -->
+ <!--! OVERVIEW SECTION. -->
<div id="overview">
<table id="fdom_tlist">
<thead>
@@ -39,8 +40,9 @@
<tbody>
<!--! List all the failover domains. -->
<tr py:for="i, fdom in enumerate(fdoms)"
- py:attrs="fdom[0]==name and {'class': 'chosen'} or (not i%2 and {'class': 'even'} or None)">
- <td class="checkbox"><input type="checkbox"/></td>
+ py:attrs="fdom[0]==name and {'class': 'chosen'} or (not i%2 and {'class': 'even'} or None)"
+ py:with="identifier=string2id(fdom[0])">
+ <td class="checkbox"><input type="checkbox" name="${identifier}"/></td>
<td class="icon"></td>
<td class="main_id"><a href="${tg.url('/fdom?name=' + fdom[0])}">${fdom[0]}</a></td>
<!--! TODO: Is the following column necessary? -->
@@ -56,7 +58,7 @@
</div>
</form>
- <!--! Details section. -->
+ <!--! DETAILS SECTION. -->
<div id="details" py:choose="details">
<py:when test="None">
@@ -69,23 +71,24 @@
<py:otherwise>
- <!--! Details - Header. -->
+ <!--! DETAILS - header section. -->
<div id="details_header">
- <h3 py:content="name">Failover1</h3>
+ <h3 py:content="name">Failover name</h3>
<div id="details_header_buttons">
- <a href="${tg.url('/fdom_delete')}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
+ <a href="${tg.url('/fdom_delete?name=' + name)}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
</div>
</div>
- <!--! Details - Attributes section. -->
+ <!--! DETAILS - attributes section. -->
<div class="details_section">
<div class="details_inner">
- <form action="${tg.url('/fdom_update')}" method="post">
+ <form action="${tg.url('/fdom_update_props')}" method="post">
+ <input type="hidden" name="name" value="${string2id(name)}"/>
<input type="submit" value="Update Properties" class="float_button"/>
<table id="fdom_tattr">
<tr>
<td class="fdom_tattr_checkbox">
- <input type="checkbox" id="prioritized" py:attrs="details[0] and {'checked': 'checked'} or None"/>
+ <input type="checkbox" id="prioritized" name="prioritized" py:attrs="details['prioritized'] and {'checked': 'checked'} or None"/>
</td><td class="fdom_tattr_caption">
<label for="prioritized">Prioritized</label>
</td><td class="fdom_tattr_hint">
@@ -94,7 +97,7 @@
</tr>
<tr>
<td class="fdom_tattr_checkbox">
- <input type="checkbox" id="restricted" py:attrs="details[1] and {'checked': 'checked'} or None"/>
+ <input type="checkbox" id="restricted" name="restricted" py:attrs="details['restricted'] and {'checked': 'checked'} or None"/>
</td><td class="fdom_tattr_caption">
<label for="restricted">Restricted</label>
</td><td class="fdom_tattr_hint">
@@ -103,7 +106,7 @@
</tr>
<tr>
<td class="fdom_tattr_checkbox">
- <input type="checkbox" id="failback" py:attrs="details[1] and {'checked': 'checked'} or None"/>
+ <input type="checkbox" id="failback" name="failback" py:attrs="details['failback'] and {'checked': 'checked'} or None"/>
</td><td class="fdom_tattr_caption">
<label for="failback">Failback</label>
</td><td class="fdom_tattr_hint">
@@ -115,7 +118,7 @@
</div>
</div>
- <!--! Details - Services section. -->
+ <!--! DETAILS - services section. -->
<div class="details_section">
<a href="${tg.url('/fdom_service')}" class="float_button">Add a Service</a>
<h4>Services</h4>
@@ -123,11 +126,15 @@
<table id="fdom_tservices">
<tbody>
<!--! List all the services connected with the current failover domain. -->
- <tr py:for="service in details[3]" class="grid_row">
+ <!--! Note: "('Service')" is syntactically a string, not a tuple,
+ so this case is also solved. -->
+ <tr py:for="service in (type(details['services']) is tuple and details['services'] or (details['services'],) )" class="grid_row">
+ <py:with vars="running = services.has_key(service) and (services[service]['running'])">
<td class="icon">
- <img py:if="not service[1]" src="${tg.url('/images/exclamation.png')}" alt="Service has a problem." />
+ <img py:if="not running" src="${tg.url('/images/exclamation.png')}" alt="Service has a problem." />
</td>
- <td class="fdom_tservices_service"><span py:attrs="service[1] and {'class': 'entity_ok'} or {'class': 'entity_fail'}">${service[0]}</span></td>
+ <td class="fdom_tservices_service"><span py:attrs="running and {'class': 'entity_ok'} or {'class': 'entity_fail'}">${service}</span></td>
+ </py:with>
<td class="table_space"></td>
</tr>
</tbody>
@@ -135,9 +142,10 @@
</div>
</div>
- <!--! Details - Members section. -->
+ <!--! DETAILS - members section. -->
<div class="details_section">
- <form action="${tg.url('/fdom_update')}" method="post">
+ <form action="${tg.url('/fdom_update_members')}" method="post">
+ <input type="hidden" name="name" value="${string2id(name)}"/>
<input type="submit" value="Update Settings" class="float_button"/>
<h4>Members</h4>
<div class="details_inner">
@@ -152,29 +160,49 @@
</tr>
</thead>
<tbody>
- <!--! List all the members of the current failover domain. -->
+ <!--! List all the nodes (of the current cluster). -->
<tr py:for="node, node_dict in nodes.iteritems()" class="grid_row">
+ <!--! Branch according to the status of the node. -->
+ <py:choose test="node_dict.get('status', nodeconst.NODE_UNKNOWN)">
+ <!--! 1) Node is active. -->
+ <py:when test="nodeconst.NODE_ACTIVE">
<td class="icon"></td>
- <td class="fdom_tmembers_name">${node}</td>
- <!--! Test whether the current node from the list is a member
- of this failover domains. -->
- <py:choose test="details[4].has_key(node)">
+ <td class="fdom_tmembers_name"><span class="entity_ok">${node}</span></td>
+ </py:when>
+ <!--! 2) Node is inactive. -->
+ <py:when test="nodeconst.NODE_INACTIVE">
+ <td class="icon">
+ <img src="${tg.url('/images/exclamation.png')}" alt="Node has a problem." />
+ </td>
+ <td class="fdom_tmembers_name"><span class="entity_fail">${node}</span></td>
+ </py:when>
+ <!--! 3) Status of the node is unknown. -->
+ <py:when test="nodeconst.NODE_UNKNOWN">
+ <td class="icon">
+ <img src="${tg.url('/images/question.png')}" alt="Status of the node is unknown." />
+ </td>
+ <td class="fdom_tmembers_name"><span class="entity_unknown">${node}</span></td>
+ </py:when>
+ </py:choose>
+ <!--! Branch according to whether the current node is a member
+ of this failover domain. -->
+ <py:choose test="details['nodes'].has_key(node)" py:with="identifier=string2id(node)">
+ <!--! 1) Yes. -->
<py:when test="True">
- <!--! Yes. -->
<td class="fdom_tmembers_member">
- <input type="checkbox" checked="checked" />
+ <input id="${identifier+'.check'}" name="${identifier+'.check'}" type="checkbox" checked="checked" onchange="onCheckMember(this.id)"/>
</td>
<td class="fdom_tmembers_priority">
- <input type="text" value="${details[4][node]}" maxlength="3" class="input_priority"/>
+ <input id="${identifier + '.priority'}" name="${identifier + '.priority'}" type="text" value="${details['nodes'][node]}" maxlength="3" class="input_priority"/>
</td>
</py:when>
+ <!--! 2) No. -->
<py:otherwise>
- <!--! No. -->
<td class="fdom_tmembers_member">
- <input type="checkbox" />
+ <input id="${identifier + '.check'}" name="${identifier+'.check'}" type="checkbox" onchange="onCheckMember(this.id)"/>
</td>
<td class="fdom_tmembers_priority">
- <input type="text" value="0" maxlength="3" class="input_priority"/>
+ <input id="${identifier + '.priority'}" name="${identifier + '.priority'}" type="text" value="0" maxlength="3" class="input_priority_disabled"/>
</td>
</py:otherwise>
</py:choose>
diff --git a/luci/templates/fence.html b/luci/templates/fence.html
index ef45f38..7fbd9fe 100644
--- a/luci/templates/fence.html
+++ b/luci/templates/fence.html
@@ -20,7 +20,7 @@
<a href="${tg.url('/fence_add')}" class="toolbar_button" id="tb_add">add</a>
</div>
- <!--! Overview section. -->
+ <!--! OVERVIEW SECTION. -->
<div id="overview">
<table id="fence_tlist">
<thead>
@@ -40,13 +40,24 @@
<tbody>
<!--! List all the shared fences. -->
<tr py:for="i, fence in enumerate(fences)"
- py:attrs="fence[0]==name and {'class': 'chosen'} or (not i%2 and {'class': 'even'} or None)">
- <td class="checkbox"><input type="checkbox"/></td>
+ py:attrs="fence[0]==name and {'class': 'chosen'} or (not i%2 and {'class': 'even'} or None)"
+ py:with="identifier=string2id(fence[0])">
+ <td class="checkbox"><input type="checkbox" name="${identifier}"/></td>
+ <!--! If the fence is shared, display appropriate icon. -->
<td class="icon"><img py:if="type(fence[2]) is not tuple" src="${tg.url('/images/global_11x11_black.png')}" alt="shared fence"/></td>
<td class="main_id"><a href="${tg.url('/fence?name=' + fence[0])}">${fence[0]}</a></td>
<td class="fence_tlist_type">${fence[1]}</td>
+ <!--! Branch according to whether the fence is local or shared. -->
<py:choose test="type(fence[2]) is tuple">
- <td py:when="True" class="fence_tlist_members"><span py:attrs="fence[2][1].get('status', nodeconst.NODE_UNKNOWN) == nodeconst.NODE_ACTIVE and {'class': 'entity_ok'} or {'class': 'entity_fail'}">${fence[2][0]}</span></td>
+ <!--! 1) Local. -->
+ <td py:when="True" class="fence_tlist_members">
+ <py:choose test="fence[2][1].get('status', nodeconst.NODE_UNKNOWN)">
+ <span py:when="nodeconst.NODE_ACTIVE" class="entity_ok">${fence[2][0]}</span>
+ <span py:when="nodeconst.NODE_INACTIVE" class="entity_fail">${fence[2][0]}</span>
+ <span py:when="nodeconst.NODE_UNKNOWN" class="entity_unknown">${fence[2][0]}</span>
+ </py:choose>
+ </td>
+ <!--! 2) Shared. -->
<td py:otherwise="" class="fence_tlist_members">${fence[2]}</td>
</py:choose>
<td class="fence_tlist_ip">${fence[3]}</td>
@@ -57,7 +68,7 @@
</div>
</form>
- <!--! Details section. -->
+ <!--! DETAILS SECTION. -->
<div id="details" py:choose="details">
<py:when test="None">
@@ -70,26 +81,26 @@
<py:otherwise>
- <!--! Details - Header. -->
+ <!--! DETAILS - header section. -->
<div id="details_header">
<h3 py:content="name">Fence A</h3>
<div id="details_header_buttons">
- <a href="${tg.url('/fence_delete')}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
+ <a href="${tg.url('/fence_delete?name=' + name)}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
<a href="${tg.url(request.path_qs)}" id="dh_update" title="refresh"><span class="hide">refresh</span></a>
</div>
- type <span id="fence_details_type">${details[0]}</span>
+ type <span id="fence_details_type">${details['type']}</span>
</div>
- <!--! Details - Hostname/IP/port section. -->
+ <!--! DETAILS - hostname/IP/port section. -->
<div class="details_section">
<ul id="fence_details_list">
- <li><span class="fence_details_label">Hostname</span>${details[1]}</li>
- <li><span class="fence_details_label">IP Address</span>${details[2]}</li>
- <li><span class="fence_details_label">Port</span>${details[3]}</li>
+ <li><span class="fence_details_label">Hostname</span>${details['host']}</li>
+ <li><span class="fence_details_label">IP Address</span>${details['ip']}</li>
+ <li><span class="fence_details_label">Port</span>${details['port']}</li>
</ul>
</div>
- <!--! Details - Nodes section. -->
+ <!--! DETAILS - nodes section. -->
<div class="details_section">
<a href="${tg.url('/fence_manage')}" class="float_button">Manage Nodes</a>
<h4>Nodes</h4>
@@ -106,14 +117,36 @@
</thead>
<tbody>
<!--! List all the nodes connected with the current shared fence. -->
- <!--! Test whether the current node is connected with the fence. -->
- <tr py:for="node, node_dict in nodes.iteritems()" class="grid_row" py:if="details[4].has_key(node)">
+ <tr py:for="node, node_dict in nodes.iteritems()" class="grid_row" py:if="details['nodes'].has_key(node)">
+ <!--! Branch according to the status of the node. -->
+ <py:choose test="node_dict.get('status', nodeconst.NODE_UNKNOWN)">
+ <!--! 1) Node is active. -->
+ <py:when test="nodeconst.NODE_ACTIVE">
+ <td class="icon"></td>
+ <td class="fence_tnodes_name"><span class="entity_ok">${node}</span></td>
+ <td class="fence_tnodes_status">${_('OK')}</td>
+ </py:when>
+ <!--! 2) Node is inactive. -->
+ <py:when test="nodeconst.NODE_INACTIVE">
<td class="icon">
- <img py:if="node_dict['status'] != nodeconst.NODE_ACTIVE" src="${tg.url('/images/exclamation.png')}" alt="Node has a problem." />
+ <img src="${tg.url('/images/exclamation.png')}" alt="Node has a problem." />
</td>
- <td class="fence_tnodes_name"><span py:attrs="node_dict['status'] == nodeconst.NODE_ACTIVE and {'class': 'entity_ok'} or {'class': 'entity_fail'}">${node}</span></td>
- <td class="fence_tnodes_status">${node_dict['status'] != nodeconst.NODE_ACTIVE and node_dict['msg'] or _('OK')}</td>
- <td class="fence_tnodes_level">${details[4][node] == 1 and _('Primary Fence') or _('Secondary Fence')}</td>
+ <td class="fence_tnodes_name"><span class="entity_fail">${node}</span></td>
+ <td class="fence_tnodes_status">${node_dict.get('msg', _('Uknown problem'))}</td>
+ </py:when>
+ <!--! 3) Status of the node is unknown. -->
+ <py:when test="nodeconst.NODE_UNKNOWN">
+ <td class="icon">
+ <img src="${tg.url('/images/question.png')}" alt="Status of the node is unknown." />
+ </td>
+ <td class="fence_tnodes_name"><span class="entity_unknown">${node}</span></td>
+ <td class="fence_tnodes_status">${node_dict.get('msg', _('Status uknown'))}</td>
+ </py:when>
+ </py:choose>
+ <py:choose test="details['nodes'][node]">
+ <td py:when="1" class="fence_tnodes_level">${_('Primary Fence')}</td>
+ <td py:when="2" class="fence_tnodes_level">${_('Secondary Fence')}</td>
+ </py:choose>
<td class="table_space"></td>
</tr>
</tbody>
diff --git a/luci/templates/master.html b/luci/templates/master.html
index 24c9377..6ec3715 100644
--- a/luci/templates/master.html
+++ b/luci/templates/master.html
@@ -18,6 +18,8 @@
${header()}
<ul id="mainmenu">
<li><a href="${tg.url('/about')}" class="${('', 'active')[defined('page') and page==page=='about']}">About</a></li>
+ <li><a href="${tg.url('/fdom')}">Failovers</a></li>
+ <li><a href="${tg.url('/fence')}">Fences</a></li>
<span py:if="tg.auth_stack_enabled" py:strip="True">
<li py:if="not request.identity" id="login" class="loginlogout"><a href="${tg.url('/login')}">Login</a></li>
<li py:if="request.identity" id="login" class="loginlogout"><a href="${tg.url('/logout_handler')}">Logout</a></li>
14 years, 10 months
[luci] luci: add files missing from the last commit
by Ryan McCabe
commit 0bb421be0bfad096ad0da8aaf2c920af15cb8621
Author: Ryan McCabe <ryan(a)chipotle.numb.lan>
Date: Tue Aug 25 13:30:32 2009 -0400
luci: add files missing from the last commit
luci/lib/ricci_helpers.py | 192 ++++++++++++++++++++++++++++++++++++++++++++
luci/public/css/create.css | 6 ++
luci/templates/create.html | 147 +++++++++++++++++++++++++++++++++
3 files changed, 345 insertions(+), 0 deletions(-)
---
diff --git a/luci/lib/ricci_helpers.py b/luci/lib/ricci_helpers.py
new file mode 100644
index 0000000..5dd993a
--- /dev/null
+++ b/luci/lib/ricci_helpers.py
@@ -0,0 +1,192 @@
+# Copyright (C) 2006-2009 Red Hat, Inc.
+#
+# This program is free software; you can redistribute
+# it and/or modify it under the terms of version 2 of the
+# GNU General Public License as published by the
+# Free Software Foundation.
+
+from ricci_communicator import RicciCommunicator
+import threading
+
+class PWorker(threading.Thread):
+ # triple := [ host, function, *args ]
+ def __init__(self, mutex, ret, host_triples):
+ threading.Thread.__init__(self)
+ self.mutex = mutex
+ self.ret = ret
+ self.triples = host_triples
+
+ def run(self):
+ while True:
+ self.mutex.acquire()
+ if len(self.triples) == 0:
+ self.mutex.release()
+ return
+ triple = self.triples.pop()
+ self.mutex.release()
+
+ r = { 'ricci': None }
+
+ try:
+ rc = RicciCommunicator(triple[0], enforce_trust=False)
+ rc.trust()
+ r['ricci'] = rc
+
+ if triple[1] is not None:
+ if triple[2]:
+ args = list(triple[2])
+ else:
+ args = list()
+ args.insert(0, rc)
+ r['batch_result'] = triple[1](*args)
+ except Exception, e:
+ r['error'] = True
+ r['err_msg'] = str(e)
+ r['ricci'] = None
+
+ self.mutex.acquire()
+ self.ret[triple[0]] = r
+ self.mutex.release()
+
+class Worker(threading.Thread):
+ def __init__(self, mutex, hosts, riccis, func=None, *args):
+ threading.Thread.__init__(self)
+ self.mutex = mutex
+ self.hosts = hosts
+ self.riccis = riccis
+ self.query_func = func
+ self.query_args = args
+
+ def run(self):
+ while True:
+ self.mutex.acquire()
+ if len(self.hosts) == 0:
+ self.mutex.release()
+ return
+ host = self.hosts.pop()
+ self.mutex.release()
+
+ r = { 'ricci': None }
+
+ try:
+ rc = RicciCommunicator(host, enforce_trust=False)
+ rc.trust()
+ r['ricci'] = rc
+ try:
+ r['cluster_name'] = rc.cluster_info()[0]
+ except:
+ pass
+
+ if self.query_func is not None:
+ if self.query_args:
+ args = list(self.query_args)
+ else:
+ args = list()
+ args.insert(0, rc)
+ r['batch_result'] = self.query_func(*args)
+ except Exception, e:
+ r['error'] = True
+ r['err_msg'] = str(e)
+ r['ricci'] = None
+
+ self.mutex.acquire()
+ self.riccis[host] = r
+ self.mutex.release()
+
+def send_batch_to_hosts(system_list, max_threads, func, *args):
+ mutex = threading.RLock()
+ threads = list()
+ hosts = list()
+ num_hosts = 0
+ ret = {}
+
+ for host in system_list:
+ hosts.append(host)
+ num_hosts += 1
+ if num_hosts <= max_threads:
+ threads.append(Worker(mutex, hosts, ret, func, *args))
+
+ for thread in threads:
+ thread.start()
+ for thread in threads:
+ thread.join()
+ return ret
+
+def send_batch_parallel(triples, max_threads):
+ mutex = threading.RLock()
+ threads = list()
+ num_trips = 0
+ trips = list()
+ ret = {}
+
+ for trip in triples:
+ trips.append(trip)
+ num_trips += 1
+ if num_trips <= max_threads:
+ threads.append(PWorker(mutex, ret, triples))
+
+ for thread in threads:
+ thread.start()
+ for thread in threads:
+ thread.join()
+ return ret
+
+def get_system_info(self, system_list):
+ mutex = threading.RLock()
+ hive = []
+ ss = {}
+ hosts = []
+
+ for system in system_list:
+ hosts.append(system[0])
+ if len(hosts) < 10:
+ hive.append(Worker(mutex, hosts, ss))
+
+ for bee in hive:
+ bee.start()
+
+ for bee in hive:
+ bee.join()
+
+ for hostname in ss.keys():
+ OS = ''
+ cluname = ''
+ cluali = ''
+ key_fp = ''
+ trusted = ''
+ authed = False
+ trusted = False
+ ricci = ss[hostname]['ricci']
+
+ if ricci is not None:
+ OS = ricci.os()
+ cluname = ricci.cluster_info()[0]
+ cluali = ricci.cluster_info()[1]
+ authed = ricci.authed()
+ key_fp = ricci.fingerprint()[1]
+ trusted = ricci.trusted()
+ else:
+ OS = 'System not accessible'
+
+ s = {
+ 'hostname' : hostname,
+ 'os' : OS,
+ 'cluname' : cluname,
+ 'key_fp' : key_fp,
+ 'clualias' : cluali,
+ 'available' : ricci is not None,
+ 'trusted' : trusted,
+ 'authed' : authed,
+ 'err_msg' : ss[hostname].get('err_msg')
+ }
+
+ # replace ricci with system's info
+ ss[hostname] = s
+
+ ss_list = []
+ sorted_keys = ss.keys()
+ sorted_keys.sort()
+
+ for name in sorted_keys:
+ ss_list.append(ss[name])
+ return ss_list
diff --git a/luci/public/css/create.css b/luci/public/css/create.css
new file mode 100644
index 0000000..acfe004
--- /dev/null
+++ b/luci/public/css/create.css
@@ -0,0 +1,6 @@
+ul.vanilla, li.vanilla {
+ list-style-type: none ! important;
+ list-style-image: none !important;
+ margin-left: 0 ! important;
+}
+
diff --git a/luci/templates/create.html b/luci/templates/create.html
new file mode 100644
index 0000000..78805cf
--- /dev/null
+++ b/luci/templates/create.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<xi:include href="master.html" />
+
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title>Create a cluster</title>
+</head>
+
+<body>
+
+ <form action="${tg.url('/cluster_create')}" method="post">
+
+ <h1>Create a new cluster</h1>
+
+ <table id="systemsTable" class="systemsTable" cellspacing="0">
+ <thead class="systemsTable">
+ <tr class="systemsTable"><td class="systemsTable" colspan="2">
+ <div class="systemsTableTop">
+ <strong>Cluster Name</strong>
+ <input class="hbInputSys" type="text"
+ id="clustername" name="clustername" />
+ </div>
+ </td></tr>
+ <tr class="systemsTable">
+ <th class="systemsTable">Node Hostname</th>
+ <th class="systemsTable">Root Password</th>
+ <th class="systemsTable">Key ID</th>
+ <th></th>
+ </tr>
+ </thead>
+
+ <tfoot class="systemsTable">
+ <tr class="systemsTable"><td class="systemsTable" colspan="2">
+ <div class="systemsTableEnd">
+ <input type="button" value="Add a cluster node" />
+ </div>
+ </td></tr>
+
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ </td></tr>
+
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ <input type="radio" name="download_pkgs" value="1" />
+ Download packages
+ </td></tr>
+
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ <input type="radio" name="download_pkgs" value="0" />
+ Use locally installed packages.
+ </td></tr>
+
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ </td></tr>
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ </td></tr>
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ <input type="checkbox" name="enable_storage" />Enable shared storage support
+ </td></tr>
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ <input type="checkbox" id="reboot_nodes" name="reboot_nodes"
+ />Reboot nodes before joining cluster
+ </td></tr>
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ <input type="checkbox"
+ name="allSameCheckBox" id="allSameCheckBox" />
+
+ Check if node passwords are identical.
+ </td></tr>
+ </tfoot>
+
+ <tbody>
+ <tr class="systemsTable" id="__SYSTEM_ROW_0">
+ <td class="systemsTable">
+ <input class="hbInputSys" type="text"
+ id="__SYSTEM0_addr" name="__SYSTEM0_addr" />
+ </td>
+ <td class="systemsTable">
+ <input type="password"
+ class="hbInputPass" autocomplete="off"
+ id="__SYSTEM0_passwd" name="__SYSTEM0_passwd" />
+ </td>
+ <td class="systemsTable">
+ <img id="__SYSTEM0Fingerprint" src="lock-open.png"
+ title="no key fingerprint available" />
+ </td>
+ <td class="systemsTable">
+ <img src="delete-row.png" class="deleteRow"
+ title="delete this row" />
+ </td>
+ </tr>
+ <tr class="systemsTable" id="__SYSTEM_ROW_1">
+ <td class="systemsTable">
+ <input class="hbInputSys" type="text"
+ id="__SYSTEM1_addr" name="__SYSTEM1_addr" />
+ </td>
+ <td class="systemsTable">
+ <input type="password"
+ class="hbInputPass" autocomplete="off"
+ id="__SYSTEM1_passwd" name="__SYSTEM1_passwd" />
+ </td>
+ <td class="systemsTable">
+ <img id="__SYSTEM1Fingerprint" src="lock-open.png"
+ title="no key fingerprint available" />
+ </td>
+ <td class="systemsTable">
+ <img src="delete-row.png" class="deleteRow"
+ title="delete this row" />
+ </td>
+ </tr>
+ <tr class="systemsTable" id="__SYSTEM_ROW_2">
+ <td class="systemsTable">
+ <input class="hbInputSys" type="text"
+ id="__SYSTEM2_addr" name="__SYSTEM2_addr" />
+ </td>
+ <td class="systemsTable">
+ <input type="password"
+ class="hbInputPass" autocomplete="off"
+ id="__SYSTEM2_passwd" name="__SYSTEM2_passwd" />
+ </td>
+ <td class="systemsTable">
+ <img id="__SYSTEM2Fingerprint" src="lock-open.png"
+ title="no key fingerprint available" />
+ </td>
+ <td class="systemsTable">
+ <img src="delete-row.png" class="deleteRow"
+ title="delete this row" />
+ </td>
+ </tr>
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ </td></tr>
+ <tr class="systemsTable"><td colspan="2" class="systemsTable">
+ </td></tr>
+ </tbody>
+ </table>
+
+ <div class="hbSubmit" id="hbSubmit">
+ <input type="hidden" name="cluster_create" value="1" />
+ <input type="submit" name="Submit" value="Submit" />
+ </div>
+ </form>
+</body>
+</html>
14 years, 10 months
[luci] ricci_communicator: update to use the new python ssl library instead of our own custom library
by Ryan McCabe
commit 23f80ab520ae9956b51b58b09451619e479f9f27
Author: Ryan McCabe <ryan(a)chipotle.numb.lan>
Date: Tue Aug 25 11:20:05 2009 -0400
ricci_communicator: update to use the new python ssl library instead of our own custom library
luci: add code for cluster deploy
devdata.db | Bin 0 -> 18432 bytes
development.ini | 2 +-
luci.egg-info/SOURCES.txt | 77 +++++++++++++++++++++++++++++++
luci/config/app_cfg.py | 2 +-
luci/controllers/root.py | 58 ++++++++++++++++++++++++
luci/lib/ricci_communicator.py | 97 ++++++++++++++++++++++------------------
luci/model/objects.py | 30 ++++++++----
luci/public/css/style.css | 1 +
8 files changed, 211 insertions(+), 56 deletions(-)
---
diff --git a/devdata.db b/devdata.db
index e69de29..03a2a65 100644
Binary files a/devdata.db and b/devdata.db differ
diff --git a/development.ini b/development.ini
index f041749..1b13c7f 100644
--- a/development.ini
+++ b/development.ini
@@ -16,7 +16,7 @@ error_email_from = paste@localhost
[server:main]
use = egg:Paste#http
-host = 127.0.0.1
+host = 0.0.0.0
port = 8080
[app:main]
diff --git a/luci.egg-info/SOURCES.txt b/luci.egg-info/SOURCES.txt
index ba19da0..07c6ed5 100644
--- a/luci.egg-info/SOURCES.txt
+++ b/luci.egg-info/SOURCES.txt
@@ -24,13 +24,82 @@ luci/i18n/ru/LC_MESSAGES/luci.po
luci/lib/__init__.py
luci/lib/app_globals.py
luci/lib/base.py
+luci/lib/conga_ssl.py
luci/lib/helpers.py
+luci/lib/ricci_communicator.py
+luci/lib/ricci_defines.py
+luci/lib/ricci_helpers.py
+luci/lib/ricci_queries.py
+luci/lib/ClusterConf/Altname.py
+luci/lib/ClusterConf/Apache.py
+luci/lib/ClusterConf/BaseResource.py
+luci/lib/ClusterConf/Cluster.py
+luci/lib/ClusterConf/ClusterNode.py
+luci/lib/ClusterConf/ClusterNodes.py
+luci/lib/ClusterConf/Clusterfs.py
+luci/lib/ClusterConf/Cman.py
+luci/lib/ClusterConf/Device.py
+luci/lib/ClusterConf/FailoverDomain.py
+luci/lib/ClusterConf/FailoverDomainNode.py
+luci/lib/ClusterConf/FailoverDomains.py
+luci/lib/ClusterConf/Fence.py
+luci/lib/ClusterConf/FenceDaemon.py
+luci/lib/ClusterConf/FenceDevice.py
+luci/lib/ClusterConf/FenceDeviceAttr.py
+luci/lib/ClusterConf/FenceDevices.py
+luci/lib/ClusterConf/FenceXVMd.py
+luci/lib/ClusterConf/Fs.py
+luci/lib/ClusterConf/Gulm.py
+luci/lib/ClusterConf/Heuristic.py
+luci/lib/ClusterConf/Ip.py
+luci/lib/ClusterConf/LVM.py
+luci/lib/ClusterConf/Lockserver.py
+luci/lib/ClusterConf/Method.py
+luci/lib/ClusterConf/ModelBuilder.py
+luci/lib/ClusterConf/Multicast.py
+luci/lib/ClusterConf/MySQL.py
+luci/lib/ClusterConf/NFSClient.py
+luci/lib/ClusterConf/NFSExport.py
+luci/lib/ClusterConf/Netfs.py
+luci/lib/ClusterConf/OpenLDAP.py
+luci/lib/ClusterConf/OracleDB.py
+luci/lib/ClusterConf/Postgres8.py
+luci/lib/ClusterConf/QuorumD.py
+luci/lib/ClusterConf/RefObject.py
+luci/lib/ClusterConf/Resources.py
+luci/lib/ClusterConf/Rm.py
+luci/lib/ClusterConf/SAPDatabase.py
+luci/lib/ClusterConf/SAPInstance.py
+luci/lib/ClusterConf/Samba.py
+luci/lib/ClusterConf/Script.py
+luci/lib/ClusterConf/Service.py
+luci/lib/ClusterConf/SybaseASE.py
+luci/lib/ClusterConf/TagObject.py
+luci/lib/ClusterConf/Tomcat5.py
+luci/lib/ClusterConf/Totem.py
+luci/lib/ClusterConf/Vm.py
+luci/lib/ClusterConf/__init__.py
luci/model/__init__.py
luci/model/auth.py
+luci/model/objects.py
luci/public/favicon.ico
+luci/public/css/create.css
+luci/public/css/fdom.css
+luci/public/css/fence.css
+luci/public/css/shared.css
luci/public/css/style.css
+luci/public/images/100wait.gif
+luci/public/images/add-grey.png
+luci/public/images/add-white.png
luci/public/images/contentbg.png
+luci/public/images/delete-grey.png
+luci/public/images/delete-white.png
+luci/public/images/details_header_line.png
+luci/public/images/dot.png
+luci/public/images/empty.png
luci/public/images/error.png
+luci/public/images/exclamation.png
+luci/public/images/global_11x11_black.png
luci/public/images/header_inner2.png
luci/public/images/headerbg.png
luci/public/images/info.png
@@ -39,21 +108,29 @@ luci/public/images/loginbg.png
luci/public/images/loginbottombg.png
luci/public/images/loginheader-left.png
luci/public/images/loginheader-right.png
+luci/public/images/logo.jpg
luci/public/images/menu-item-actibg-first.png
luci/public/images/menu-item-actibg.png
luci/public/images/menu-item-border.png
luci/public/images/menubg.png
luci/public/images/ok.png
luci/public/images/pagebg.png
+luci/public/images/reboot-grey.png
+luci/public/images/reboot-white.png
+luci/public/images/spinner.gif
luci/public/images/star.png
luci/public/images/strype2.png
+luci/public/images/toolbar_line.png
luci/public/images/under_the_hood_blue.png
luci/public/images/warning.png
luci/templates/__init__.py
luci/templates/about.html
luci/templates/authentication.html
+luci/templates/create.html
luci/templates/debug.html
luci/templates/error.html
+luci/templates/fdom.html
+luci/templates/fence.html
luci/templates/footer.html
luci/templates/header.html
luci/templates/index.html
diff --git a/luci/config/app_cfg.py b/luci/config/app_cfg.py
index 895e1d9..bb6a03b 100644
--- a/luci/config/app_cfg.py
+++ b/luci/config/app_cfg.py
@@ -17,7 +17,7 @@ from tg.configuration import AppConfig
import luci
from luci import model
-from luci.lib import app_globals, helpers
+from luci.lib import app_globals, helpers, ricci_communicator, ricci_defines, ricci_helpers
base_config = AppConfig()
base_config.renderers = []
diff --git a/luci/controllers/root.py b/luci/controllers/root.py
index afc3bd9..068803e 100644
--- a/luci/controllers/root.py
+++ b/luci/controllers/root.py
@@ -13,6 +13,11 @@ from luci.controllers.error import ErrorController
from luci import model
from luci.controllers.secure import SecureController
#from luci.widgets.fdom_add_form import create_fdom_add_form
+from luci.lib.ricci_helpers import send_batch_to_hosts
+from luci.lib.ricci_communicator import RicciCommunicator
+import luci.lib.ricci_queries as rq
+from sys import stderr
+
__all__ = ['RootController']
@@ -37,6 +42,59 @@ class RootController(BaseController):
error = ErrorController()
+ @expose('luci.templates.create')
+ def create(self, name=""):
+ return dict()
+
+ @expose()
+ def cluster_create(self, **kw):
+ errors = ()
+ cluster_name = kw.get('clustername')
+ enable_storage = kw.get('enable_storage')
+ reboot_nodes = kw.get('reboot_nodes')
+ download_pkgs = kw.get('download_pkgs')
+
+ nodes = [
+ [ kw.get('__SYSTEM0_addr'), kw.get('__SYSTEM0_passwd') ],
+ [ kw.get('__SYSTEM1_addr'), kw.get('__SYSTEM1_passwd') ],
+ [ kw.get('__SYSTEM2_addr'), kw.get('__SYSTEM2_passwd') ]
+ ]
+ node_list = [ i[0] for i in nodes ]
+ stderr.write('nodes: %s\n' % str(node_list))
+
+ if not cluster_name:
+ flash(_('No cluster name was given'))
+ redirect('/create')
+ if len(cluster_name) > 15:
+ flash(_('Cluster names must be less than 16 characters long'))
+ redirect('/create')
+
+ for node in nodes:
+ rc = RicciCommunicator(node[0], enforce_trust=False)
+ rc.trust()
+ rc.auth(node[1])
+ if not rc.authed():
+ errors.append('Authentication to node %s failed' % node[0])
+ break
+ else:
+ rc = RicciCommunicator(node[0])
+
+ cur_cluster_name = rc.cluster_info()[0]
+ if cur_cluster_name:
+ errors.append('%s is already a member of a cluster %s' \
+ % (node[0], cur_cluster_name))
+ break
+
+ if len(errors) > 0:
+ flash('The following errors occurred: %s' % str(errors))
+ redirect("/create")
+
+ ret = send_batch_to_hosts(node_list, 10, rq.create_cluster,
+ 'rhel5', cluster_name, cluster_name,
+ node_list, True, True, enable_storage, False,
+ download_pkgs, None, reboot_nodes)
+
+ redirect('/create')
###########################################################################
# Static demo data.
diff --git a/luci/lib/ricci_communicator.py b/luci/lib/ricci_communicator.py
index ebb251f..fbf61c4 100644
--- a/luci/lib/ricci_communicator.py
+++ b/luci/lib/ricci_communicator.py
@@ -7,7 +7,9 @@
from xml.dom import minidom, Node
from ricci_defines import CERTS_DIR_PATH
-from conga_ssl import SSLSocket
+import socket
+import ssl
+from sys import stderr
LUCI_DEBUG_NET = False
LUCI_DEBUG_MODE = False
@@ -17,7 +19,7 @@ class RicciError(Exception):
pass
class RicciCommunicator:
- def __init__(self, hostname, enforce_trust=True, port=11111):
+ def __init__(self, hostname, port=11111, enforce_trust=False):
self.__hostname = hostname
self.__port = port
@@ -30,17 +32,16 @@ class RicciCommunicator:
self.__cert_file = '%scacert.pem' % CERTS_DIR_PATH
try:
- self.ss = SSLSocket(self.__hostname,
- self.__port,
- self.__timeout_init)
- if enforce_trust:
- if not self.ss.trusted():
- luci_log.info('The SSL certificate for "%s:%d" is not trusted. Aborting connection attempt.' % (self.__hostname, self.__port))
- raise RicciError, 'The SSL certificate for %s:%d is not trusted' % (self.__hostname, self.__port)
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.ss = ssl.wrap_socket(s,
+ certfile=self.__cert_file,
+ keyfile=self.__privkey_file,
+ cert_reqs=ssl.CERT_NONE)
+ self.ss.connect((self.__hostname, self.__port))
except Exception, e:
errmsg = 'Unable to establish an SSL connection to %s:%d: %s' \
% ((self.__hostname, self.__port, str(e)))
- luci_log.info(errmsg)
+ #luci_log.info(errmsg)
raise RicciError, errmsg
# receive ricci header
@@ -53,8 +54,9 @@ class RicciCommunicator:
if LUCI_DEBUG_MODE is True:
luci_log.debug_verbose('RC:init0: error receiving header from %s:%d: %r %s' % (self.__hostname, self.__port, e, str(e)))
else:
- luci_log.info('Error reading from %s:%d: %s' \
- % (self.__hostname, self.__port, str(e)))
+ pass
+ #luci_log.info('Error reading from %s:%d: %s' \
+ # % (self.__hostname, self.__port, str(e)))
self.__authed = hello.firstChild.getAttribute('authenticated') == 'true'
self.__cluname = hello.firstChild.getAttribute('clustername')
@@ -109,13 +111,13 @@ class RicciCommunicator:
return fp
def trust(self):
- return self.ss.trust()
+ return True
def untrust(self):
- return self.ss.untrust()
+ return True
def trusted(self):
- return self.ss.trusted()
+ return True
def auth(self, password):
if self.authed():
@@ -178,7 +180,7 @@ class RicciCommunicator:
except:
errstr = 'Error authenticating to the ricci agent at %s:%d: %s' \
% (self.__hostname, self.__port, str(ret))
- luci_log.info(errstr)
+ #luci_log.info(errstr)
raise RicciError, errstr
return True
@@ -216,7 +218,7 @@ class RicciCommunicator:
except Exception, e:
errstr = 'Error sending XML batch command to %s:%d: %s' \
% (self.__hostname, self.__port, str(e))
- luci_log.info(errstr)
+ #luci_log.info(errstr)
if LUCI_DEBUG_NET is True:
luci_log.debug_net_priv('RC:PB2: sending "%s" to %s:%d: %r %s' \
@@ -309,7 +311,7 @@ class RicciCommunicator:
if new_err_msg:
err_msg = 'Unable to retrieve batch %s status from %s:%d: %s' \
% (batch_id, self.__hostname, self.__port, str(new_err_msg))
- luci_log.info(err_msg)
+ #luci_log.info(err_msg)
except Exception, e:
if LUCI_DEBUG_MODE is True:
luci_log.debug_verbose('RCCB2: batch %s stat on %s:%d: %r %s' \
@@ -370,13 +372,15 @@ class RicciCommunicator:
def __send(self, xml_doc, timeout):
+ stderr.write(xml_doc.toxml())
+ stderr.write('\n')
try:
- self.ss.send(xml_doc.toxml(), timeout)
- self.ss.send('\n', timeout)
+ self.ss.write(xml_doc.toxml())
+ self.ss.write('\n')
except Exception, e:
errstr = 'Error sending batch command to %s:%d: %s' \
% (self.__hostname, self.__port, str(e))
- luci_log.info(errstr)
+ #luci_log.info(errstr)
if LUCI_DEBUG_NET is True:
luci_log.debug_net_priv('RC:send0: send XML "%s" to %s:%d: %s' \
@@ -393,30 +397,34 @@ class RicciCommunicator:
def __receive(self, timeout):
xml_in = ''
- try:
- xml_in = self.ss.recv(timeout)
- except Exception, e:
- errstr = 'Error reading from %s:%d: %s' \
- % (self.__hostname, self.__port, str(e))
- luci_log.info(errstr)
- raise RicciError, errstr
-
- if LUCI_DEBUG_NET is True:
- luci_log.debug_net_priv('RC:recv1: recv XML "%s" from %s:%d' \
- % (xml_in, self.__hostname, self.__port))
-
doc = None
- try:
- doc = minidom.parseString(xml_in)
- except Exception, e:
- errstr = 'Error parsing XML from %s:%d: %s' \
- % (self.__hostname, self.__port, str(e))
- luci_log.info(errstr)
+
+ while doc is None:
+ try:
+ xml_in += self.ss.read()
+ except Exception, e:
+ errstr = 'Error reading from %s:%d: %s' \
+ % (self.__hostname, self.__port, str(e))
+ #luci_log.info(errstr)
+ raise RicciError, errstr
if LUCI_DEBUG_NET is True:
- luci_log.debug_net_priv('RC:recv2: "%s" from %s:%d: %r %s' \
- % (xml_in, self.__hostname, self.__port, e, str(e)))
- raise RicciError, errstr
+ luci_log.debug_net_priv('RC:recv1: recv XML "%s" from %s:%d' \
+ % (xml_in, self.__hostname, self.__port))
+
+ doc = None
+ try:
+ doc = minidom.parseString(xml_in)
+ except Exception, e:
+ doc = None
+ errstr = 'Error parsing XML from %s:%d: %s' \
+ % (self.__hostname, self.__port, str(e))
+ #luci_log.info(errstr)
+
+ if LUCI_DEBUG_NET is True:
+ luci_log.debug_net_priv('RC:recv2: "%s" from %s:%d: %r %s' \
+ % (xml_in, self.__hostname, self.__port, e, str(e)))
+ continue
if not doc or not doc.firstChild:
if LUCI_DEBUG_MODE is True:
@@ -451,8 +459,9 @@ def get_ricci_communicator(self, hostname, allowed_systems):
raise Exception, 'initialization failed'
return rc
except Exception, e:
- luci_log.info('Error creating a ricci connection to %s: %s' \
- % (hostname, str(e)))
+ pass
+ #luci_log.info('Error creating a ricci connection to %s: %s' \
+ # % (hostname, str(e)))
return None
def ricci_get_called_hostname(self, ricci):
diff --git a/luci/model/objects.py b/luci/model/objects.py
index 2cb3980..bf6a627 100644
--- a/luci/model/objects.py
+++ b/luci/model/objects.py
@@ -8,17 +8,7 @@ from sqlalchemy.orm import relation, synonym
from luci.model import DeclarativeBase, metadata, DBSession
-#{ The auth* model itself
-
-
class Node(DeclarativeBase):
- """
- Group definition for :mod:`repoze.what`.
-
- Only the ``group_name`` column is required by :mod:`repoze.what`.
-
- """
-
__tablename__ = 'system'
#{ Columns
@@ -28,6 +18,8 @@ class Node(DeclarativeBase):
group_name = Column(Unicode(16), unique=True, nullable=False)
display_name = Column(Unicode(255))
+ hostname = Column(Unicode(255))
+ cluster = Column(Integer)
created = Column(DateTime, default=datetime.now)
@@ -65,3 +57,21 @@ class Cluster(DeclarativeBase):
def __unicode__(self):
return self.display_name or self.name
+
+class Task(DeclarativeBase):
+ __tablename__ = 'tasks'
+
+ task_id = Column(Integer, autoincrement=True, primary_key=True)
+ batch_id = Column(Integer)
+ batch_status = Column(Integer)
+ started = Column(DateTime, default=datetime.now)
+ ended = Column(DateTime)
+ description = Column(Unicode(255))
+ status_msg = Column(Unicode(255))
+ system = Column(Integer)
+
+ def __repr__(self):
+ return '<Task: id=%d>' % self.task_id
+
+ def __unicode__(self):
+ return self.task_id
diff --git a/luci/public/css/style.css b/luci/public/css/style.css
index 0aecce5..0eefaa8 100644
--- a/luci/public/css/style.css
+++ b/luci/public/css/style.css
@@ -1,6 +1,7 @@
@import url("shared.css");
@import url("fdom.css");
@import url("fence.css");
+@import url("create.css");
html {
background: #555555 url('../images/pagebg.png') top left repeat-x;
14 years, 10 months
[luci] Move cluster.conf model code to lib
by Ryan McCabe
commit 3977860744d3b438f0bd9df2eb292f89b28ffaa7
Author: Ryan McCabe <ryan(a)chipotle.numb.lan>
Date: Fri Aug 21 09:14:29 2009 -0400
Move cluster.conf model code to lib
luci/{model => lib}/ClusterConf/Altname.py | 0
luci/{model => lib}/ClusterConf/Apache.py | 0
luci/{model => lib}/ClusterConf/BaseResource.py | 0
luci/{model => lib}/ClusterConf/Cluster.py | 0
luci/{model => lib}/ClusterConf/ClusterNode.py | 0
luci/{model => lib}/ClusterConf/ClusterNodes.py | 0
luci/{model => lib}/ClusterConf/Clusterfs.py | 0
luci/{model => lib}/ClusterConf/Cman.py | 0
luci/{model => lib}/ClusterConf/Device.py | 0
luci/{model => lib}/ClusterConf/FailoverDomain.py | 0
.../ClusterConf/FailoverDomainNode.py | 0
luci/{model => lib}/ClusterConf/FailoverDomains.py | 0
luci/{model => lib}/ClusterConf/Fence.py | 0
luci/{model => lib}/ClusterConf/FenceDaemon.py | 0
luci/{model => lib}/ClusterConf/FenceDevice.py | 0
luci/{model => lib}/ClusterConf/FenceDeviceAttr.py | 0
luci/{model => lib}/ClusterConf/FenceDevices.py | 0
luci/{model => lib}/ClusterConf/FenceXVMd.py | 0
luci/{model => lib}/ClusterConf/Fs.py | 0
luci/{model => lib}/ClusterConf/Gulm.py | 0
luci/{model => lib}/ClusterConf/Heuristic.py | 0
luci/{model => lib}/ClusterConf/Ip.py | 0
luci/{model => lib}/ClusterConf/LVM.py | 0
luci/{model => lib}/ClusterConf/Lockserver.py | 0
luci/{model => lib}/ClusterConf/Method.py | 0
luci/{model => lib}/ClusterConf/ModelBuilder.py | 0
luci/{model => lib}/ClusterConf/Multicast.py | 0
luci/{model => lib}/ClusterConf/MySQL.py | 0
luci/{model => lib}/ClusterConf/NFSClient.py | 0
luci/{model => lib}/ClusterConf/NFSExport.py | 0
luci/{model => lib}/ClusterConf/Netfs.py | 0
luci/{model => lib}/ClusterConf/OpenLDAP.py | 0
luci/{model => lib}/ClusterConf/OracleDB.py | 0
luci/{model => lib}/ClusterConf/Postgres8.py | 0
luci/{model => lib}/ClusterConf/QuorumD.py | 0
luci/{model => lib}/ClusterConf/RefObject.py | 0
luci/{model => lib}/ClusterConf/Resources.py | 0
luci/{model => lib}/ClusterConf/Rm.py | 0
luci/{model => lib}/ClusterConf/SAPDatabase.py | 0
luci/{model => lib}/ClusterConf/SAPInstance.py | 0
luci/{model => lib}/ClusterConf/Samba.py | 0
luci/{model => lib}/ClusterConf/Script.py | 0
luci/{model => lib}/ClusterConf/Service.py | 0
luci/{model => lib}/ClusterConf/SybaseASE.py | 0
luci/{model => lib}/ClusterConf/TagObject.py | 0
luci/{model => lib}/ClusterConf/Tomcat5.py | 0
luci/{model => lib}/ClusterConf/Totem.py | 0
luci/{model => lib}/ClusterConf/Vm.py | 0
luci/{model => lib}/ClusterConf/__init__.py | 0
49 files changed, 0 insertions(+), 0 deletions(-)
---
diff --git a/luci/model/ClusterConf/Altname.py b/luci/lib/ClusterConf/Altname.py
similarity index 100%
rename from luci/model/ClusterConf/Altname.py
rename to luci/lib/ClusterConf/Altname.py
diff --git a/luci/model/ClusterConf/Apache.py b/luci/lib/ClusterConf/Apache.py
similarity index 100%
rename from luci/model/ClusterConf/Apache.py
rename to luci/lib/ClusterConf/Apache.py
diff --git a/luci/model/ClusterConf/BaseResource.py b/luci/lib/ClusterConf/BaseResource.py
similarity index 100%
rename from luci/model/ClusterConf/BaseResource.py
rename to luci/lib/ClusterConf/BaseResource.py
diff --git a/luci/model/ClusterConf/Cluster.py b/luci/lib/ClusterConf/Cluster.py
similarity index 100%
rename from luci/model/ClusterConf/Cluster.py
rename to luci/lib/ClusterConf/Cluster.py
diff --git a/luci/model/ClusterConf/ClusterNode.py b/luci/lib/ClusterConf/ClusterNode.py
similarity index 100%
rename from luci/model/ClusterConf/ClusterNode.py
rename to luci/lib/ClusterConf/ClusterNode.py
diff --git a/luci/model/ClusterConf/ClusterNodes.py b/luci/lib/ClusterConf/ClusterNodes.py
similarity index 100%
rename from luci/model/ClusterConf/ClusterNodes.py
rename to luci/lib/ClusterConf/ClusterNodes.py
diff --git a/luci/model/ClusterConf/Clusterfs.py b/luci/lib/ClusterConf/Clusterfs.py
similarity index 100%
rename from luci/model/ClusterConf/Clusterfs.py
rename to luci/lib/ClusterConf/Clusterfs.py
diff --git a/luci/model/ClusterConf/Cman.py b/luci/lib/ClusterConf/Cman.py
similarity index 100%
rename from luci/model/ClusterConf/Cman.py
rename to luci/lib/ClusterConf/Cman.py
diff --git a/luci/model/ClusterConf/Device.py b/luci/lib/ClusterConf/Device.py
similarity index 100%
rename from luci/model/ClusterConf/Device.py
rename to luci/lib/ClusterConf/Device.py
diff --git a/luci/model/ClusterConf/FailoverDomain.py b/luci/lib/ClusterConf/FailoverDomain.py
similarity index 100%
rename from luci/model/ClusterConf/FailoverDomain.py
rename to luci/lib/ClusterConf/FailoverDomain.py
diff --git a/luci/model/ClusterConf/FailoverDomainNode.py b/luci/lib/ClusterConf/FailoverDomainNode.py
similarity index 100%
rename from luci/model/ClusterConf/FailoverDomainNode.py
rename to luci/lib/ClusterConf/FailoverDomainNode.py
diff --git a/luci/model/ClusterConf/FailoverDomains.py b/luci/lib/ClusterConf/FailoverDomains.py
similarity index 100%
rename from luci/model/ClusterConf/FailoverDomains.py
rename to luci/lib/ClusterConf/FailoverDomains.py
diff --git a/luci/model/ClusterConf/Fence.py b/luci/lib/ClusterConf/Fence.py
similarity index 100%
rename from luci/model/ClusterConf/Fence.py
rename to luci/lib/ClusterConf/Fence.py
diff --git a/luci/model/ClusterConf/FenceDaemon.py b/luci/lib/ClusterConf/FenceDaemon.py
similarity index 100%
rename from luci/model/ClusterConf/FenceDaemon.py
rename to luci/lib/ClusterConf/FenceDaemon.py
diff --git a/luci/model/ClusterConf/FenceDevice.py b/luci/lib/ClusterConf/FenceDevice.py
similarity index 100%
rename from luci/model/ClusterConf/FenceDevice.py
rename to luci/lib/ClusterConf/FenceDevice.py
diff --git a/luci/model/ClusterConf/FenceDeviceAttr.py b/luci/lib/ClusterConf/FenceDeviceAttr.py
similarity index 100%
rename from luci/model/ClusterConf/FenceDeviceAttr.py
rename to luci/lib/ClusterConf/FenceDeviceAttr.py
diff --git a/luci/model/ClusterConf/FenceDevices.py b/luci/lib/ClusterConf/FenceDevices.py
similarity index 100%
rename from luci/model/ClusterConf/FenceDevices.py
rename to luci/lib/ClusterConf/FenceDevices.py
diff --git a/luci/model/ClusterConf/FenceXVMd.py b/luci/lib/ClusterConf/FenceXVMd.py
similarity index 100%
rename from luci/model/ClusterConf/FenceXVMd.py
rename to luci/lib/ClusterConf/FenceXVMd.py
diff --git a/luci/model/ClusterConf/Fs.py b/luci/lib/ClusterConf/Fs.py
similarity index 100%
rename from luci/model/ClusterConf/Fs.py
rename to luci/lib/ClusterConf/Fs.py
diff --git a/luci/model/ClusterConf/Gulm.py b/luci/lib/ClusterConf/Gulm.py
similarity index 100%
rename from luci/model/ClusterConf/Gulm.py
rename to luci/lib/ClusterConf/Gulm.py
diff --git a/luci/model/ClusterConf/Heuristic.py b/luci/lib/ClusterConf/Heuristic.py
similarity index 100%
rename from luci/model/ClusterConf/Heuristic.py
rename to luci/lib/ClusterConf/Heuristic.py
diff --git a/luci/model/ClusterConf/Ip.py b/luci/lib/ClusterConf/Ip.py
similarity index 100%
rename from luci/model/ClusterConf/Ip.py
rename to luci/lib/ClusterConf/Ip.py
diff --git a/luci/model/ClusterConf/LVM.py b/luci/lib/ClusterConf/LVM.py
similarity index 100%
rename from luci/model/ClusterConf/LVM.py
rename to luci/lib/ClusterConf/LVM.py
diff --git a/luci/model/ClusterConf/Lockserver.py b/luci/lib/ClusterConf/Lockserver.py
similarity index 100%
rename from luci/model/ClusterConf/Lockserver.py
rename to luci/lib/ClusterConf/Lockserver.py
diff --git a/luci/model/ClusterConf/Method.py b/luci/lib/ClusterConf/Method.py
similarity index 100%
rename from luci/model/ClusterConf/Method.py
rename to luci/lib/ClusterConf/Method.py
diff --git a/luci/model/ClusterConf/ModelBuilder.py b/luci/lib/ClusterConf/ModelBuilder.py
similarity index 100%
rename from luci/model/ClusterConf/ModelBuilder.py
rename to luci/lib/ClusterConf/ModelBuilder.py
diff --git a/luci/model/ClusterConf/Multicast.py b/luci/lib/ClusterConf/Multicast.py
similarity index 100%
rename from luci/model/ClusterConf/Multicast.py
rename to luci/lib/ClusterConf/Multicast.py
diff --git a/luci/model/ClusterConf/MySQL.py b/luci/lib/ClusterConf/MySQL.py
similarity index 100%
rename from luci/model/ClusterConf/MySQL.py
rename to luci/lib/ClusterConf/MySQL.py
diff --git a/luci/model/ClusterConf/NFSClient.py b/luci/lib/ClusterConf/NFSClient.py
similarity index 100%
rename from luci/model/ClusterConf/NFSClient.py
rename to luci/lib/ClusterConf/NFSClient.py
diff --git a/luci/model/ClusterConf/NFSExport.py b/luci/lib/ClusterConf/NFSExport.py
similarity index 100%
rename from luci/model/ClusterConf/NFSExport.py
rename to luci/lib/ClusterConf/NFSExport.py
diff --git a/luci/model/ClusterConf/Netfs.py b/luci/lib/ClusterConf/Netfs.py
similarity index 100%
rename from luci/model/ClusterConf/Netfs.py
rename to luci/lib/ClusterConf/Netfs.py
diff --git a/luci/model/ClusterConf/OpenLDAP.py b/luci/lib/ClusterConf/OpenLDAP.py
similarity index 100%
rename from luci/model/ClusterConf/OpenLDAP.py
rename to luci/lib/ClusterConf/OpenLDAP.py
diff --git a/luci/model/ClusterConf/OracleDB.py b/luci/lib/ClusterConf/OracleDB.py
similarity index 100%
rename from luci/model/ClusterConf/OracleDB.py
rename to luci/lib/ClusterConf/OracleDB.py
diff --git a/luci/model/ClusterConf/Postgres8.py b/luci/lib/ClusterConf/Postgres8.py
similarity index 100%
rename from luci/model/ClusterConf/Postgres8.py
rename to luci/lib/ClusterConf/Postgres8.py
diff --git a/luci/model/ClusterConf/QuorumD.py b/luci/lib/ClusterConf/QuorumD.py
similarity index 100%
rename from luci/model/ClusterConf/QuorumD.py
rename to luci/lib/ClusterConf/QuorumD.py
diff --git a/luci/model/ClusterConf/RefObject.py b/luci/lib/ClusterConf/RefObject.py
similarity index 100%
rename from luci/model/ClusterConf/RefObject.py
rename to luci/lib/ClusterConf/RefObject.py
diff --git a/luci/model/ClusterConf/Resources.py b/luci/lib/ClusterConf/Resources.py
similarity index 100%
rename from luci/model/ClusterConf/Resources.py
rename to luci/lib/ClusterConf/Resources.py
diff --git a/luci/model/ClusterConf/Rm.py b/luci/lib/ClusterConf/Rm.py
similarity index 100%
rename from luci/model/ClusterConf/Rm.py
rename to luci/lib/ClusterConf/Rm.py
diff --git a/luci/model/ClusterConf/SAPDatabase.py b/luci/lib/ClusterConf/SAPDatabase.py
similarity index 100%
rename from luci/model/ClusterConf/SAPDatabase.py
rename to luci/lib/ClusterConf/SAPDatabase.py
diff --git a/luci/model/ClusterConf/SAPInstance.py b/luci/lib/ClusterConf/SAPInstance.py
similarity index 100%
rename from luci/model/ClusterConf/SAPInstance.py
rename to luci/lib/ClusterConf/SAPInstance.py
diff --git a/luci/model/ClusterConf/Samba.py b/luci/lib/ClusterConf/Samba.py
similarity index 100%
rename from luci/model/ClusterConf/Samba.py
rename to luci/lib/ClusterConf/Samba.py
diff --git a/luci/model/ClusterConf/Script.py b/luci/lib/ClusterConf/Script.py
similarity index 100%
rename from luci/model/ClusterConf/Script.py
rename to luci/lib/ClusterConf/Script.py
diff --git a/luci/model/ClusterConf/Service.py b/luci/lib/ClusterConf/Service.py
similarity index 100%
rename from luci/model/ClusterConf/Service.py
rename to luci/lib/ClusterConf/Service.py
diff --git a/luci/model/ClusterConf/SybaseASE.py b/luci/lib/ClusterConf/SybaseASE.py
similarity index 100%
rename from luci/model/ClusterConf/SybaseASE.py
rename to luci/lib/ClusterConf/SybaseASE.py
diff --git a/luci/model/ClusterConf/TagObject.py b/luci/lib/ClusterConf/TagObject.py
similarity index 100%
rename from luci/model/ClusterConf/TagObject.py
rename to luci/lib/ClusterConf/TagObject.py
diff --git a/luci/model/ClusterConf/Tomcat5.py b/luci/lib/ClusterConf/Tomcat5.py
similarity index 100%
rename from luci/model/ClusterConf/Tomcat5.py
rename to luci/lib/ClusterConf/Tomcat5.py
diff --git a/luci/model/ClusterConf/Totem.py b/luci/lib/ClusterConf/Totem.py
similarity index 100%
rename from luci/model/ClusterConf/Totem.py
rename to luci/lib/ClusterConf/Totem.py
diff --git a/luci/model/ClusterConf/Vm.py b/luci/lib/ClusterConf/Vm.py
similarity index 100%
rename from luci/model/ClusterConf/Vm.py
rename to luci/lib/ClusterConf/Vm.py
diff --git a/luci/model/ClusterConf/__init__.py b/luci/lib/ClusterConf/__init__.py
similarity index 100%
rename from luci/model/ClusterConf/__init__.py
rename to luci/lib/ClusterConf/__init__.py
14 years, 10 months
[luci] Failovers + Fences: changes in internal logic.
by Jan Pokorný
commit 14044f55bf961847a9ca0a457857ba6d9ce525ff
Author: Jan Pokorny <jpokorny(a)redhat.com>
Date: Tue Aug 18 16:43:05 2009 +0200
Failovers + Fences: changes in internal logic.
Still no connection with 'live' data.
luci/controllers/root.py | 162 ++++++++++++++++++++++++++++++++------------
luci/public/css/fence.css | 4 +-
luci/public/css/shared.css | 4 +-
luci/templates/fdom.html | 24 ++++++-
luci/templates/fence.html | 20 +++--
5 files changed, 154 insertions(+), 60 deletions(-)
---
diff --git a/luci/controllers/root.py b/luci/controllers/root.py
index 86c8eb9..afc3bd9 100644
--- a/luci/controllers/root.py
+++ b/luci/controllers/root.py
@@ -2,6 +2,7 @@
"""Main Controller"""
from tg import expose, flash, require, url, request, redirect
+from tg import tmpl_context
from pylons.i18n import ugettext as _, lazy_ugettext as l_
from catwalk.tg2 import Catwalk
from repoze.what import predicates
@@ -11,6 +12,7 @@ from luci.model import DBSession, metadata
from luci.controllers.error import ErrorController
from luci import model
from luci.controllers.secure import SecureController
+#from luci.widgets.fdom_add_form import create_fdom_add_form
__all__ = ['RootController']
@@ -35,6 +37,84 @@ class RootController(BaseController):
error = ErrorController()
+
+ ###########################################################################
+ # Static demo data.
+
+
+ # Nodes in the current cluster.
+
+ class NodeConstants:
+ NODE_ACTIVE = '0'
+ NODE_UNKNOWN = '1'
+ NODE_INACTIVE = '2'
+
+ # Temporary data for failover domains, format:
+ # {'nodename': {'status': NODE_X, 'msg': 'optional status description'}, ...}
+ nodes = {'NodeAlpha': {'status': NodeConstants.NODE_ACTIVE},
+ 'NodeBeta': {'status': NodeConstants.NODE_ACTIVE},
+ 'NodeDelta': {'status': NodeConstants.NODE_UNKNOWN,
+ 'msg': 'Something terrible'},
+ 'NodeGamma': {'status': NodeConstants.NODE_ACTIVE},
+ 'NodeEpsilon': {'status': NodeConstants.NODE_UNKNOWN,
+ 'msg': 'Something terrible'}}
+
+ # Failover domains.
+
+ # Temporary data for failover domains, format:
+ # {'name': (prioritized, restricted, failback, [('service', is ok?), ...],
+ # {'member': priority, ...}), ...}
+ fdoms = {'Failover1':(False, True, False,
+ [('SAP', True),
+ ('Oracle', True)],
+ {'NodeAlpha': 1,
+ 'NodeBeta': 2}),
+ 'Failover2':(True, True, False,
+ [('Service X', True),
+ ('Service Y', False),
+ ('Service Z', True)],
+ {'NodeAlpha': 1,
+ 'NodeBeta': 2,
+ 'NodeEpsilon': 0}),
+ 'FDOM a': (True, False, False,
+ [('NFS', True),
+ ('Samba', True)],
+ {'NodeBeta': 2,
+ 'NodeGamma': 0}),
+ 'FDOM b': (True, False, False,
+ [('Apache', True)],
+ {'NodeDelta': 0,
+ 'NodeGamma': 0}),
+ 'FDOM c': (True, True, True,
+ [('Tomcat', True),
+ ('PostgreSQL', False),
+ ('LVM', True)],
+ {'NodeAlpha': 1,
+ 'NodeEpsilon': 0})}
+
+ # Shared fences.
+
+ # Temporary data for fences, format:
+ # {'name': (type, hostname, address, port, [("node", status, has primary priority?), ...])
+
+ fences = { 'Fence A':('iLO', 'hostname.host.org', '123.123.78.90', 6666,
+ {'NodeDelta': 1}),
+ 'Fence B':('APC Power Device', 'fenceb.host.org', '123.123.78.11', 6667,
+ {'NodeAlpha': 1,
+ 'NodeDelta': 2}),
+ 'Fence C':('Virtual Machine', 'fencec.host.org', '123.123.78.156', 11023,
+ {'NodeAlpha': 1,
+ 'NodeBeta': 2,
+ 'NodeGamma': 2,
+ 'NodeDelta': 2}),
+ 'Fence D':('SCSI Reservation', 'fenced.host.org', '123.123.78.35', 5487,
+ {'NodeAlpha': 2,
+ 'NodeBeta': 1,
+ 'NodeGamma': 2})}
+
+ ###########################################################################
+
+
@expose('luci.templates.index')
def index(self):
"""Handle the front-page."""
@@ -96,93 +176,87 @@ class RootController(BaseController):
redirect(came_from)
- # Failover Domains
+ # Failover Domains part.
@expose('luci.templates.fdom')
def fdom(self, name=""):
- fdom_table = { \
- # Temporary data for failover domains, format:
- # name: (prioritizied, restricted, failback, [("service", is ok?), ...], [("member", is member?, priority), ...])
- "Failover1":(False, True, False,
- [("SAP", True), ("Oracle", True)],
- [("Uno", True, 1),("Duno", False, 2),("Tre", True, 0)]),
- "Failover2":(True, True, False,
- [("Service X", True), ("Service Y", False), ("Service Z", True)],
- [("NodeAlpha", True, 1),("NodeBeta", False, 2),("NodeDelta", True, 0),
- ("NodeGamma", True, 0),("NodeEpsilon", True, 0)]),
- "FDOM a": (True, False, False,
- [("NFS", True), ("Samba", True)],
- [("No1", True, 1),("No2", True, 2),("No3", True, 0),
- ("No4", True, 0)]),
- "FDOM b": (True, False, False,
- [("Apache", True)],
- [("PrimaryApache", True, 1),("SecondaryApache", False, 2)]),
- "FDOM c": (True, True, True,
- [("Tomcat", True), ("PostgreSQL", False), ("LVM", True)],
- [("NodeExtraStrong", True, 1)])}
+ """Handle 'failover domains' page."""
details = None
- fdoms = [[k,v[0],v[1],v[2]] for k,v in fdom_table.iteritems()]
+ fdoms_overview = [[k,v[0],v[1],v[2]] for k,v in self.fdoms.iteritems()]
+
+ # Check whether we are able to display details to selected failover
+ # domain.
if name != "":
- if fdom_table.has_key(name):
- details = fdom_table[name]
- return dict(name=name, fdoms=fdoms, details=details)
+ if self.fdoms.has_key(name):
+ details = self.fdoms[name]
+ return dict(name=name, fdoms=fdoms_overview, details=details,
+ nodes=self.nodes)
@expose()
def fdom_delete(self):
+ """Handle removal of selected failover domain(s)."""
flash('Demo of deleting...')
redirect(request.referer or '/fdom')
@expose()
+ #@expose('luci.templates.fdom_add_form')
def fdom_add(self):
+ """Handle creation of a new failover domain."""
flash('Demo of adding...')
- redirect('/fdom')
+ redirect('/fence')
+ # following code is only my experiment, how to continue:
+ #tmpl_context.form = create_fdom_add_form
+ #return dict()
@expose()
def fdom_update(self):
+ """Handle changes in a failover domain."""
flash('Demo of updating properties...')
redirect(request.referer or '/fdom')
@expose()
def fdom_service(self):
+ """Handle creation of a new service."""
flash('Demo of adding a service...')
redirect('/fdom')
- # Shared Fences
+ # Fences part.
@expose('luci.templates.fence')
def fence(self, name=""):
- fence_table = { \
- # Temporary data for shared fences, format:
- # name: (type, hostname, address, port, [("node", status, level), ...])
- "Fence A":("iLO", "hostname.host.org", "123.123.78.90", 6666,
- [("NodeDelta", "Something Terrible", 1)]),
- "Fence B":("APC Power Device", "fenceb.host.org", "123.123.78.11", 6667,
- [("NodeAlpha", None, 1), ("NodeDelta", "Something Terrible", 2)]),
- "Fence C":("Virtual Machine", "fencec.host.org", "123.123.78.156", 11023,
- [("NodeAlpha", None, 1), ("NodeBeta", None, 2),
- ("NodeGamma", None, 3),("NodeDelta", "Something Terrible", 4)]),
- "Fence D":("SCSI Reservation", "fenced.host.org", "123.123.78.35", 5487,
- [("NodeAlpha", None, 1), ("NodeBeta", None, 2),
- ("NodeGamma", None, 3)])}
+ """Handle 'fences' page."""
details = None
- fences = [[k,v[0],len(v[4]),v[2]] for k,v in fence_table.iteritems()]
+ fences_overview = \
+ [[k,v[0],
+ len(v[4])==1
+ and (v[4].keys()[0],
+ self.nodes.get(v[4].keys()[0],
+ {'status': self.NodeConstants.NODE_UNKNOWN}))
+ or len(v[4]),
+ v[2]] for k,v in self.fences.iteritems()]
+
+ # Check whether we are able to display details to selected fence.
if name != "":
- if fence_table.has_key(name):
- details = fence_table[name]
- return dict(name=name, fences=fences, details=details)
+ if self.fences.has_key(name):
+ details = self.fences[name]
+ return dict(name=name, fences=fences_overview, details=details,
+ nodes=self.nodes, nodeconst=self.NodeConstants)
@expose()
def fence_delete(self):
+ """Handle removal of selected fence(s)."""
flash('Demo of deleting...')
redirect(request.referer or '/fence')
@expose()
def fence_add(self):
+ """Handle creation of a new fence."""
flash('Demo of adding...')
redirect('/fence')
@expose()
def fence_manage(self):
+ """Handle the nodes management."""
flash('Demo of managing nodes...')
redirect('/fence')
diff --git a/luci/public/css/fence.css b/luci/public/css/fence.css
index 6682c76..aadf9ed 100644
--- a/luci/public/css/fence.css
+++ b/luci/public/css/fence.css
@@ -88,8 +88,8 @@ th.fence_tnodes_status {
}
th.fence_tnodes_level {
- width: 15%;
- min-width: 15%;
+ width: 20%;
+ min-width: 20%;
}
.fence_tnodes_level {
diff --git a/luci/public/css/shared.css b/luci/public/css/shared.css
index 724358b..3ce0d12 100644
--- a/luci/public/css/shared.css
+++ b/luci/public/css/shared.css
@@ -59,7 +59,7 @@ a.float_button {
}
.icon {
- padding: 0 2px;
+ padding: 0 4px;
width: 11px;
min-width: 11px;
text-align: center;
@@ -196,7 +196,7 @@ a.float_button {
}
#overview .main_id {
- padding: 1px 10px 1px 0;
+ padding: 1px 10px 1px 2px;
text-align: left;
}
diff --git a/luci/templates/fdom.html b/luci/templates/fdom.html
index 20ecbaa..4d588e0 100644
--- a/luci/templates/fdom.html
+++ b/luci/templates/fdom.html
@@ -153,15 +153,31 @@
</thead>
<tbody>
<!--! List all the members of the current failover domain. -->
- <tr py:for="member in details[4]" class="grid_row">
+ <tr py:for="node, node_dict in nodes.iteritems()" class="grid_row">
<td class="icon"></td>
- <td class="fdom_tmembers_name">${member[0]}</td>
+ <td class="fdom_tmembers_name">${node}</td>
+ <!--! Test whether the current node from the list is a member
+ of this failover domains. -->
+ <py:choose test="details[4].has_key(node)">
+ <py:when test="True">
+ <!--! Yes. -->
<td class="fdom_tmembers_member">
- <input type="checkbox" py:attrs="member[1] and {'checked': 'checked'} or None"/>
+ <input type="checkbox" checked="checked" />
</td>
<td class="fdom_tmembers_priority">
- <input type="text" value="${member[2]}" maxlength="3" class="input_priority"/>
+ <input type="text" value="${details[4][node]}" maxlength="3" class="input_priority"/>
</td>
+ </py:when>
+ <py:otherwise>
+ <!--! No. -->
+ <td class="fdom_tmembers_member">
+ <input type="checkbox" />
+ </td>
+ <td class="fdom_tmembers_priority">
+ <input type="text" value="0" maxlength="3" class="input_priority"/>
+ </td>
+ </py:otherwise>
+ </py:choose>
<td class="table_space"></td>
</tr>
</tbody>
diff --git a/luci/templates/fence.html b/luci/templates/fence.html
index 55926a2..ef45f38 100644
--- a/luci/templates/fence.html
+++ b/luci/templates/fence.html
@@ -26,7 +26,7 @@
<thead>
<tr>
<th class="checkbox"></th>
- <th class="icon"></th>
+ <th class="icon"><img src="${tg.url('/images/global_11x11_black.png')}" alt="shared fences"/></th>
<th class="main_id">name</th>
<th class="fence_tlist_type">fence type</th>
<th class="fence_tlist_members">member nodes</th>
@@ -42,10 +42,13 @@
<tr py:for="i, fence in enumerate(fences)"
py:attrs="fence[0]==name and {'class': 'chosen'} or (not i%2 and {'class': 'even'} or None)">
<td class="checkbox"><input type="checkbox"/></td>
- <td class="icon"></td>
+ <td class="icon"><img py:if="type(fence[2]) is not tuple" src="${tg.url('/images/global_11x11_black.png')}" alt="shared fence"/></td>
<td class="main_id"><a href="${tg.url('/fence?name=' + fence[0])}">${fence[0]}</a></td>
<td class="fence_tlist_type">${fence[1]}</td>
- <td class="fence_tlist_members">${fence[2]}</td>
+ <py:choose test="type(fence[2]) is tuple">
+ <td py:when="True" class="fence_tlist_members"><span py:attrs="fence[2][1].get('status', nodeconst.NODE_UNKNOWN) == nodeconst.NODE_ACTIVE and {'class': 'entity_ok'} or {'class': 'entity_fail'}">${fence[2][0]}</span></td>
+ <td py:otherwise="" class="fence_tlist_members">${fence[2]}</td>
+ </py:choose>
<td class="fence_tlist_ip">${fence[3]}</td>
<td class="table_space"></td>
</tr>
@@ -103,13 +106,14 @@
</thead>
<tbody>
<!--! List all the nodes connected with the current shared fence. -->
- <tr py:for="node in details[4]" class="grid_row">
+ <!--! Test whether the current node is connected with the fence. -->
+ <tr py:for="node, node_dict in nodes.iteritems()" class="grid_row" py:if="details[4].has_key(node)">
<td class="icon">
- <img py:if="node[1] is not None" src="${tg.url('/images/exclamation.png')}" alt="Node has a problem." />
+ <img py:if="node_dict['status'] != nodeconst.NODE_ACTIVE" src="${tg.url('/images/exclamation.png')}" alt="Node has a problem." />
</td>
- <td class="fence_tnodes_name"><span py:attrs="node[1] is None and {'class': 'entity_ok'} or {'class': 'entity_fail'}">${node[0]}</span></td>
- <td class="fence_tnodes_status">${node[1] is not None and node[1] or 'OK'}</td>
- <td class="fence_tnodes_level">${node[2]}</td>
+ <td class="fence_tnodes_name"><span py:attrs="node_dict['status'] == nodeconst.NODE_ACTIVE and {'class': 'entity_ok'} or {'class': 'entity_fail'}">${node}</span></td>
+ <td class="fence_tnodes_status">${node_dict['status'] != nodeconst.NODE_ACTIVE and node_dict['msg'] or _('OK')}</td>
+ <td class="fence_tnodes_level">${details[4][node] == 1 and _('Primary Fence') or _('Secondary Fence')}</td>
<td class="table_space"></td>
</tr>
</tbody>
14 years, 10 months
[luci] Failover domains + Shared fences ready for connection with "live data".
by Jan Pokorný
commit 34c125a6c5e5a1686c86461200d1cc81a951dc23
Author: Jan Pokorny <jpokorny(a)redhat.com>
Date: Wed Aug 12 19:19:52 2009 +0200
Failover domains + Shared fences ready for connection with "live data".
Commit includes changed *.css files (most of shared parts are in "shared.css").
In the later stage, I suppose to merge all particular files with style into
one standalone file, but for now it brings me better orientation having it
split.
luci/controllers/root.py | 57 +++++++++++--
luci/public/css/fdom.css | 70 +++++++--------
luci/public/css/fence.css | 81 ++++++++++++++++--
luci/public/css/shared.css | 202 +++++++++++++++++++++++++++++++++-----------
luci/templates/fdom.html | 158 ++++++++++++++++++-----------------
luci/templates/fence.html | 93 +++++++++++++++-----
6 files changed, 456 insertions(+), 205 deletions(-)
---
diff --git a/luci/controllers/root.py b/luci/controllers/root.py
index 0f3df25..86c8eb9 100644
--- a/luci/controllers/root.py
+++ b/luci/controllers/root.py
@@ -95,6 +95,9 @@ class RootController(BaseController):
flash(_('We hope to see you soon!'))
redirect(came_from)
+
+ # Failover Domains
+
@expose('luci.templates.fdom')
def fdom(self, name=""):
fdom_table = { \
@@ -124,24 +127,62 @@ class RootController(BaseController):
details = fdom_table[name]
return dict(name=name, fdoms=fdoms, details=details)
+ @expose()
+ def fdom_delete(self):
+ flash('Demo of deleting...')
+ redirect(request.referer or '/fdom')
+
+ @expose()
+ def fdom_add(self):
+ flash('Demo of adding...')
+ redirect('/fdom')
+
+ @expose()
+ def fdom_update(self):
+ flash('Demo of updating properties...')
+ redirect(request.referer or '/fdom')
+
+ @expose()
+ def fdom_service(self):
+ flash('Demo of adding a service...')
+ redirect('/fdom')
+
+
+ # Shared Fences
+
@expose('luci.templates.fence')
def fence(self, name=""):
fence_table = { \
- # Temporary data for fences, format:
- # name: (type, address, port, [("node", status, level), ...])
- "Fence A":("iLO", "123.123.78.90", 6666,
+ # Temporary data for shared fences, format:
+ # name: (type, hostname, address, port, [("node", status, level), ...])
+ "Fence A":("iLO", "hostname.host.org", "123.123.78.90", 6666,
[("NodeDelta", "Something Terrible", 1)]),
- "Fence B":("APC Power Device", "123.123.78.11", 6667,
+ "Fence B":("APC Power Device", "fenceb.host.org", "123.123.78.11", 6667,
[("NodeAlpha", None, 1), ("NodeDelta", "Something Terrible", 2)]),
- "Fence C":("Virtual Machine", "123.123.78.156", 11023,
+ "Fence C":("Virtual Machine", "fencec.host.org", "123.123.78.156", 11023,
[("NodeAlpha", None, 1), ("NodeBeta", None, 2),
- ("NodeGamma", None, 3),("NodeDelta", "Something Terrible")]),
- "Fence D":("SCSI Reservation", "123.123.78.35", 5487,
+ ("NodeGamma", None, 3),("NodeDelta", "Something Terrible", 4)]),
+ "Fence D":("SCSI Reservation", "fenced.host.org", "123.123.78.35", 5487,
[("NodeAlpha", None, 1), ("NodeBeta", None, 2),
("NodeGamma", None, 3)])}
details = None
- fences = [[k,v[0],len(v[3]),v[1]] for k,v in fence_table.iteritems()]
+ fences = [[k,v[0],len(v[4]),v[2]] for k,v in fence_table.iteritems()]
if name != "":
if fence_table.has_key(name):
details = fence_table[name]
return dict(name=name, fences=fences, details=details)
+
+ @expose()
+ def fence_delete(self):
+ flash('Demo of deleting...')
+ redirect(request.referer or '/fence')
+
+ @expose()
+ def fence_add(self):
+ flash('Demo of adding...')
+ redirect('/fence')
+
+ @expose()
+ def fence_manage(self):
+ flash('Demo of managing nodes...')
+ redirect('/fence')
diff --git a/luci/public/css/fdom.css b/luci/public/css/fdom.css
index 799910f..2d06132 100644
--- a/luci/public/css/fdom.css
+++ b/luci/public/css/fdom.css
@@ -2,86 +2,80 @@
/* -------------------------------- overview -------------------------------- */
-.fdom_list_enabled {
+.fdom_tlist_enabled {
text-align: center;
+ padding: 1px 10px;
}
-.fdom_list_prioritizied {
+.fdom_tlist_prioritizied {
text-align: center;
+ padding: 1px 10px;
}
-.fdom_list_restricted {
+.fdom_tlist_restricted {
text-align: center;
+ padding: 1px 10px;
}
+
/* --------------------------------- details -------------------------------- */
/* attributes setting */
#fdom_tattr {
- border-collapse: collapse;
+ margin: 0 15em 0 0;
}
-#fdom_tattr td {
- padding: 0 2em 0 0;
+.fdom_tattr_caption {
+ padding: 10px 0 10px 5px;
}
-fdom_tattr_hint {
+.fdom_tattr_hint {
+ padding: 10px 0 10px 25px;
font-size: small;
}
-/* list of services */
-#fdom_services {
- padding: 0;
- margin: 0;
-}
+/* list of services */
-#fdom_services li {
- margin: 2px 0;
- padding-left: 8px;
+#fdom_tservices tr {
background-color: #f2f2f2;
- list-style-type: none;
-}
-
-#fdom_services li.service_ok {
- color: green;
-}
-
-#fdom_services li.service_fail {
- color: red;
+ border-bottom: 2px solid white;
}
-#fdom_services span.hide {
- display: none;
- overflow: hidden;
+.fdom_tservices_service {
+ width: 20%;
+ min-width: 20%;
}
/* table of members */
#fdom_tmembers {
- border-collapse: collapse;
+ position: relative;
+ top: -15px;
}
-#fdom_tmembers td,th {
- /*padding: 0 1em;*/
+#fdom_tmembers thead tr {
+ background-color: inherit;
}
-#fdom_tmembers th {
- font-weight: normal;
+.fdom_tmembers_name {
+ padding: 0 10px 0 0;
}
-#fdom_tmembers tr {
- border-bottom: solid 2px white;
- background-color: #f2f2f2;
+th.fdom_tmembers_name {
+ width: 15%;
+ min-width: 15%;
}
-#fdom_tmembers tr#fdom_tmembers_header {
- background-color: inherit;
+.fdom_tmembers_member {
+ padding: 1px 10px;
+ text-align: center;
}
-.fdom_tmembers_member {
+.fdom_tmembers_priority {
+ padding: 1px 10px;
text-align: center;
}
diff --git a/luci/public/css/fence.css b/luci/public/css/fence.css
index 77aad4b..6682c76 100644
--- a/luci/public/css/fence.css
+++ b/luci/public/css/fence.css
@@ -2,31 +2,96 @@
/* -------------------------------- overview -------------------------------- */
-.fence_list_type {
+.fence_tlist_type {
text-align: left;
}
-th.fence_list_type {
- min-width: 20%;
+th.fence_tlist_type {
+ width: 25%;
+ min-width: 25%;
}
-.fence_list_members {
+.fence_tlist_members {
text-align: left;
}
-th.fence_list_members {
+th.fence_tlist_members {
+ width: 20%;
min-width: 20%;
}
-td.fence_list_members {
+td.fence_tlist_members {
margin: 0 0 0 11px;
}
-.fence_list_ip {
+.fence_tlist_ip {
+ text-align: left;
+}
+
+th.fence_tlist_ip {
+ width: 15%;
+ min-width: 15%;
+}
+
+
+/* --------------------------------- details -------------------------------- */
+
+/* header */
+
+#fence_details_type {
+ font-weight: bold;
+}
+
+/* details */
+
+.fence_details_label {
+ font-weight: bold;
+ margin-right: 1em;
+}
+
+#fence_details_list {
+ padding: 0;
+}
+
+#fence_details_list li {
+ display: inline;
+ margin-right: 3em;
+}
+
+/* list of nodes */
+
+#fence_tnodes {
+ position: relative;
+ top: -15px;
+}
+
+#fence_tnodes thead tr {
+ background-color: inherit;
+}
+
+.fence_tnodes_name {
text-align: left;
}
-th.fence_list_ip {
+th.fence_tnodes_name {
+ width: 20%;
+ min-width: 20%;
+}
+
+th.fence_tnodes_status {
+ width: 25%;
+ min-width: 25%;
+}
+
+.fence_tnodes_status {
+ text-align: left;
+}
+
+th.fence_tnodes_level {
+ width: 15%;
min-width: 15%;
}
+.fence_tnodes_level {
+ text-align: left;
+}
diff --git a/luci/public/css/shared.css b/luci/public/css/shared.css
index 2b503c8..724358b 100644
--- a/luci/public/css/shared.css
+++ b/luci/public/css/shared.css
@@ -1,44 +1,116 @@
/*=============================== G E N E R A L ============================= */
-/* ---------------------------------- misc. --------------------------------- */
+/* --------------------------------- globals -------------------------------- */
+
+table {
+ border-collapse: collapse;
+}
+
+table th {
+ font-weight: normal;
+}
+
+table input {
+ margin: 0;
+}
+
+img {
+ border: none;
+}
+
+/* --------------------------------- common --------------------------------- */
.table_space {
width: 100%;
}
-.float_right {
+.float_button {
+ position: relative;
float: right;
+ right: 13em;
+ margin: 0 -12em 0 0;
+ width: 12em;
+ display: block;
+ background-color: #f2f2f2;
+ text-align: center;
+ border: 2px outset;
+ color: black;
+ text-decoration: none;
+ font-size: medium;
+ z-index: 99;
+ cursor: pointer;
+ -moz-border-radius: 10px 10px;
}
-.float_left {
- float: left;
+a.float_button {
+ padding: 1px 0;
+}
+
+.float_button:hover {
+ background-color: #e8f6d4;
+}
+
+.float_button:active {
+ background-color: #d2edaa;
}
.input_priority {
width: 3em;
}
+.icon {
+ padding: 0 2px;
+ width: 11px;
+ min-width: 11px;
+ text-align: center;
+}
+
+.grid_row {
+ border-bottom: solid 2px white;
+ background-color: #f2f2f2;
+}
+
+#not_selected {
+ text-align: center;
+ line-height: 200%;
+}
+
+.entity_ok {
+ color: green;
+}
+
+.entity_fail {
+ color: red;
+}
+
+.hide {
+ visibility: hidden;
+ overflow: hidden;
+ display: block;
+ width: 1px;
+}
+
+
/* --------------------------------- toolbar -------------------------------- */
-.toolbar {
+#toolbar {
padding-left: 1em;
line-height: 23px;
height: 31px;
background: #484646 url(../images/toolbar_line.png) top left repeat-x;
}
-.toolbar a {
- margin: 0 1em 0 1em;
+.toolbar_button {
+ margin: 0 .5em 0 .5em;
color: #ffffff;
text-decoration: none;
}
-.toolbar a:hover {
+.toolbar_button:hover {
color: #8e8e8e;
- text-decoration: none;
}
-/* toolbar's buttons */
+/* specific toolbar's buttons */
/* update */
#tb_update {
@@ -64,6 +136,9 @@
#tb_delete {
background: url('../images/delete-white.png') center left no-repeat;
padding-left: 17px;
+ border: none;
+ cursor: pointer;
+ font-size: inherit;
}
#tb_delete:hover {
@@ -73,39 +148,27 @@
/* -------------------------------- overview -------------------------------- */
-.overview {
+#overview {
margin: 0 0 5px 0;
}
-.overview table {
- border-collapse: collapse;
-}
-
-.overview table td,th {
- padding: 1px 10px;
-}
-
-.overview table th {
- font-weight: normal;
-}
-
-.overview table tr.even {
+#overview tbody tr.even {
background-color: #f4f4f4;
}
-.overview table tr.chosen {
+#overview tbody tr.chosen {
background-color: #d2edaa;
}
-.overview table tbody tr:hover {
+#overview tbody tr:hover {
background-color: #fafdf5;
}
-.overview table tbody tr.chosen:hover {
+#overview tbody tr.chosen:hover {
background-color: #d2edaa;
}
-.overview table tbody tr.even:hover {
+#overview tbody tr.even:hover {
background-color: #e8f6d4;
}
@@ -121,37 +184,32 @@
/* common columns */
/* first checkbox */
-.overview table .checkbox {
- padding: 0 0 0 5px;
- text-align: center;
-}
-
-/* place for icon (icon itself not always used) */
-.overview table .icon {
- padding: 0 2px;
- width: 11px;
- display: block;
+#overview .checkbox {
+ padding: 0 0 0 10px;
text-align: center;
}
/* main identifier (with active link) */
-.overview table .main_id {
- text-align: left;
+#overview th.main_id {
+ width: 20%;
+ min-width: 20%;
}
-.overview table th.main_id {
- min-width: 20%;
+#overview .main_id {
+ padding: 1px 10px 1px 0;
+ text-align: left;
}
-.overview table td.main_id a {
+#overview .main_id a {
text-decoration: none;
color: inherit;
}
-.overview table td.main_id a:hover {
+#overview .main_id a:hover {
text-decoration: underline;
}
+
/* --------------------------------- details -------------------------------- */
.details_section {
@@ -166,16 +224,58 @@
margin-left: 1em;
}
-.details_header {
- padding: 0;
- margin: 0;
- height: 44px;
+#details_header {
+ padding: 1px 1em;
+ margin: 0 0 1em 0;
+ height: 42px;
color: #ffffff;
background: #5b5a5b url(../images/details_header_line.png) top left repeat-x;
}
-.details_header h3 {
- margin: 0 0 0 1em;
- font-weight: bold;
+#details_header h3 {
+ margin: 0;
+}
+
+#details_header_buttons {
+ float: right;
+ position: relative;
+ top: -5px;
+ width: 50%;
+}
+
+#details_header_buttons a {
+ float: right;
+ display: block;
+ width: 20px;
+ text-decoration: none;
+}
+
+/* specific buttons of details header */
+
+/* update */
+#dh_update {
+ background: url('../images/reboot-grey.png') center left no-repeat;
+}
+
+#dh_update:hover {
+ background-image: url('../images/reboot-white.png');
+}
+
+/* add */
+#dh_add {
+ background: url('../images/add-grey.png') center left no-repeat;
+}
+
+#dh_add:hover {
+ background-image: url('../images/add-white.png');
+}
+
+/* delete */
+#dh_delete {
+ background: url('../images/delete-grey.png') center left no-repeat;
+}
+
+#dh_delete:hover {
+ background-image: url('../images/delete-white.png');
}
diff --git a/luci/templates/fdom.html b/luci/templates/fdom.html
index 72229a4..20ecbaa 100644
--- a/luci/templates/fdom.html
+++ b/luci/templates/fdom.html
@@ -13,24 +13,23 @@
<body>
- <form>
- <div class="toolbar">
- <input type="submit" value="delete"/>
- <!-- <a href="./" id="tb_delete">delete</a> -->
- <a href="./" id="tb_add">add</a>
+ <form action="${tg.url('/fdom_delete')}" method="post">
+ <div id="toolbar">
+ <input type="submit" value="delete" class="toolbar_button" id="tb_delete"/>
+ <a href="${tg.url('/fdom_add')}" class="toolbar_button" id="tb_add">add</a>
</div>
<!--! Overview section. -->
- <div class="overview">
- <table>
+ <div id="overview">
+ <table id="fdom_tlist">
<thead>
<tr>
<th class="checkbox"></th>
<th class="icon"></th>
<th class="main_id">name</th>
- <th class="fdom_list_enabled">enabled</th>
- <th class="fdom_list_prioritizied">prioritizied</th>
- <th class="fdom_list_restricted">restricted</th>
+ <th class="fdom_tlist_enabled">enabled</th>
+ <th class="fdom_tlist_prioritizied">prioritizied</th>
+ <th class="fdom_tlist_restricted">restricted</th>
<td class="table_space"></td>
</tr>
<tr>
@@ -44,13 +43,13 @@
<td class="checkbox"><input type="checkbox"/></td>
<td class="icon"></td>
<td class="main_id"><a href="${tg.url('/fdom?name=' + fdom[0])}">${fdom[0]}</a></td>
- <td class="fdom_list_enabled" py:choose="False">
- <input py:when="True" type="checkbox" disabled="true" checked="checked"/>
- <input py:when="False" type="checkbox" disabled="true"/>
+ <!--! TODO: Is the following column necessary? -->
+ <td class="fdom_tlist_enabled">
+ <input type="checkbox" disabled="true" py:attrs="False and {'checked': 'checked'} or None"/>
</td>
- <td class="fdom_list_prioritizied"><img py:if="fdom[1]" src="${tg.url('/images/dot.png')}" alt="*" title="Attribute is active."/></td>
- <td class="fdom_list_restricted"><img py:if="fdom[2]" src="${tg.url('/images/dot.png')}" alt="*" title="Attribute is active."/></td>
- <td></td>
+ <td class="fdom_tlist_prioritizied"><img py:if="fdom[1]" src="${tg.url('/images/dot.png')}" alt="*" /></td>
+ <td class="fdom_tlist_restricted"><img py:if="fdom[2]" src="${tg.url('/images/dot.png')}" alt="*" /></td>
+ <td class="table_space"></td>
</tr>
</tbody>
</table>
@@ -58,52 +57,56 @@
</form>
<!--! Details section. -->
- <div class="details" py:choose="details">
-
- <div class="details_header" py:if="details != None">
- <h3 py:content="name">Failover1</h3>
- </div>
+ <div id="details" py:choose="details">
<py:when test="None">
- <div class="details_section">
- Please select a failover domain from the list.
+ <div id="details_header">
+ <div id="not_selected">
+ Select an item to view details
+ </div>
</div>
</py:when>
<py:otherwise>
+
+ <!--! Details - Header. -->
+ <div id="details_header">
+ <h3 py:content="name">Failover1</h3>
+ <div id="details_header_buttons">
+ <a href="${tg.url('/fdom_delete')}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
+ </div>
+ </div>
+
<!--! Details - Attributes section. -->
<div class="details_section">
<div class="details_inner">
- <form>
- <input type="submit" value="Update Properties" class="float_right"/>
+ <form action="${tg.url('/fdom_update')}" method="post">
+ <input type="submit" value="Update Properties" class="float_button"/>
<table id="fdom_tattr">
<tr>
- <td py:choose="details[0]">
- <input py:when="True" type="checkbox" id="prioritized" checked="checked"/>
- <input py:when="False" type="checkbox" id="prioritized"/>
+ <td class="fdom_tattr_checkbox">
+ <input type="checkbox" id="prioritized" py:attrs="details[0] and {'checked': 'checked'} or None"/>
+ </td><td class="fdom_tattr_caption">
<label for="prioritized">Prioritized</label>
- </td>
- <td class="fdom_tattr_hint">
+ </td><td class="fdom_tattr_hint">
order the nodes to which services failover
</td>
</tr>
<tr>
- <td py:choose="details[1]">
- <input py:when="True" type="checkbox" id="restricted" checked="checked"/>
- <input py:when="False" type="checkbox" id="restricted"/>
+ <td class="fdom_tattr_checkbox">
+ <input type="checkbox" id="restricted" py:attrs="details[1] and {'checked': 'checked'} or None"/>
+ </td><td class="fdom_tattr_caption">
<label for="restricted">Restricted</label>
- </td>
- <td class="fdom_tattr_hint">
+ </td><td class="fdom_tattr_hint">
kill service when all member nodes fail
</td>
</tr>
<tr>
- <td py:choose="details[2]">
- <input py:when="True" type="checkbox" id="failback" checked="checked"/>
- <input py:when="False" type="checkbox" id="failback"/>
+ <td class="fdom_tattr_checkbox">
+ <input type="checkbox" id="failback" py:attrs="details[1] and {'checked': 'checked'} or None"/>
+ </td><td class="fdom_tattr_caption">
<label for="failback">Failback</label>
- </td>
- <td class="fdom_tattr_hint">
+ </td><td class="fdom_tattr_hint">
send service back to 1st priority node when it becomes available again
</td>
</tr>
@@ -114,56 +117,59 @@
<!--! Details - Services section. -->
<div class="details_section">
- <input type="submit" value="Add a Service" class="float_right"/>
+ <a href="${tg.url('/fdom_service')}" class="float_button">Add a Service</a>
<h4>Services</h4>
<div class="details_inner">
- <ul id="fdom_services">
- <!--! List all the services connected with the current failover domain. -->
- <py:for each="service in details[3]" py:choose="service[1]">
- <!--! Service is ok. -->
- <li py:when="True" class="service_ok">
- <img src="${tg.url('/images/empty.png')}" alt="[ OK ]" title="Service is running." />
- ${service[0]}
- </li>
- <!--! Service is not ok ('py:otherwise' can be used instead). -->
- <li py:when="False" class="service_fail">
- <img src="${tg.url('/images/exclamation.png')}" alt="[FAIL]" title="Service has a problem." />
- ${service[0]}
- </li>
- </py:for>
- </ul>
+ <table id="fdom_tservices">
+ <tbody>
+ <!--! List all the services connected with the current failover domain. -->
+ <tr py:for="service in details[3]" class="grid_row">
+ <td class="icon">
+ <img py:if="not service[1]" src="${tg.url('/images/exclamation.png')}" alt="Service has a problem." />
+ </td>
+ <td class="fdom_tservices_service"><span py:attrs="service[1] and {'class': 'entity_ok'} or {'class': 'entity_fail'}">${service[0]}</span></td>
+ <td class="table_space"></td>
+ </tr>
+ </tbody>
+ </table>
</div>
</div>
<!--! Details - Members section. -->
<div class="details_section">
- <form>
- <input type="submit" value="Update Settings" class="float_right"/>
+ <form action="${tg.url('/fdom_update')}" method="post">
+ <input type="submit" value="Update Settings" class="float_button"/>
<h4>Members</h4>
<div class="details_inner">
<table id="fdom_tmembers">
- <tr id="fdom_tmembers_header">
- <th></th>
- <th class="fdom_tmembers_member">member</th>
- <th>priority</th>
- <td class="table_space"></td>
- </tr>
- <!--! List all the members of the current failover domain. -->
- <tr py:for="member in details[4]">
- <td>${member[0]}</td>
- <td class="fdom_tmembers_member" py:choose="member[1]">
- <input py:when="True" type="checkbox" checked="checked" />
- <input py:when="False" type="checkbox" />
- </td>
- <td>
- <input type="text" value="${member[2]}" maxlength="3" class="input_priority"/>
- </td>
- <td></td>
- </tr>
+ <thead>
+ <tr class="grid_row">
+ <th class="icon"></th>
+ <th class="fdom_tmembers_name"></th>
+ <th class="fdom_tmembers_member">member</th>
+ <th class="fdom_tmembers_priority">priority</th>
+ <td class="table_space"></td>
+ </tr>
+ </thead>
+ <tbody>
+ <!--! List all the members of the current failover domain. -->
+ <tr py:for="member in details[4]" class="grid_row">
+ <td class="icon"></td>
+ <td class="fdom_tmembers_name">${member[0]}</td>
+ <td class="fdom_tmembers_member">
+ <input type="checkbox" py:attrs="member[1] and {'checked': 'checked'} or None"/>
+ </td>
+ <td class="fdom_tmembers_priority">
+ <input type="text" value="${member[2]}" maxlength="3" class="input_priority"/>
+ </td>
+ <td class="table_space"></td>
+ </tr>
+ </tbody>
</table>
</div>
</form>
</div>
+
</py:otherwise>
</div>
diff --git a/luci/templates/fence.html b/luci/templates/fence.html
index 68003f2..55926a2 100644
--- a/luci/templates/fence.html
+++ b/luci/templates/fence.html
@@ -13,25 +13,24 @@
<body>
- <form>
- <div class="toolbar">
- <a href="./" id="tb_update">update</a>
- <input type="submit" value="delete"/>
- <!-- <a href="./" id="tb_delete">delete</a> -->
- <a href="./" id="tb_add">add</a>
+ <form action="${tg.url('/fence_delete')}" method="post">
+ <div id="toolbar">
+ <a href="${tg.url(request.path_qs)}" class="toolbar_button" id="tb_update">update</a>
+ <input type="submit" value="delete" class="toolbar_button" id="tb_delete" />
+ <a href="${tg.url('/fence_add')}" class="toolbar_button" id="tb_add">add</a>
</div>
<!--! Overview section. -->
- <div class="overview">
- <table>
+ <div id="overview">
+ <table id="fence_tlist">
<thead>
<tr>
<th class="checkbox"></th>
<th class="icon"></th>
<th class="main_id">name</th>
- <th class="fence_list_type">fence type</th>
- <th class="fence_list_members">member nodes</th>
- <th class="fence_list_ip">IP address</th>
+ <th class="fence_tlist_type">fence type</th>
+ <th class="fence_tlist_members">member nodes</th>
+ <th class="fence_tlist_ip">IP address</th>
<td class="table_space"></td>
</tr>
<tr>
@@ -45,32 +44,78 @@
<td class="checkbox"><input type="checkbox"/></td>
<td class="icon"></td>
<td class="main_id"><a href="${tg.url('/fence?name=' + fence[0])}">${fence[0]}</a></td>
- <td class="fence_list_type">${fence[1]}</td>
- <td class="fence_list_members">${fence[2]}</td>
- <td class="fence_list_ip">${fence[3]}</td>
- <td></td>
+ <td class="fence_tlist_type">${fence[1]}</td>
+ <td class="fence_tlist_members">${fence[2]}</td>
+ <td class="fence_tlist_ip">${fence[3]}</td>
+ <td class="table_space"></td>
</tr>
</tbody>
</table>
</div>
</form>
-
<!--! Details section. -->
- <div class="details" py:choose="details">
-
- <div class="details_header" py:if="details != None">
- <h3 py:content="name">Fence1</h3>
- </div>
+ <div id="details" py:choose="details">
<py:when test="None">
- <div class="details_section">
- Please select a fence from the list.
+ <div id="details_header">
+ <div id="not_selected">
+ Select an item to view details
+ </div>
</div>
</py:when>
<py:otherwise>
- <!--! Details - Attributes section. -->
+
+ <!--! Details - Header. -->
+ <div id="details_header">
+ <h3 py:content="name">Fence A</h3>
+ <div id="details_header_buttons">
+ <a href="${tg.url('/fence_delete')}" id="dh_delete" title="delete"><span class="hide">delete</span></a>
+ <a href="${tg.url(request.path_qs)}" id="dh_update" title="refresh"><span class="hide">refresh</span></a>
+ </div>
+ type <span id="fence_details_type">${details[0]}</span>
+ </div>
+
+ <!--! Details - Hostname/IP/port section. -->
+ <div class="details_section">
+ <ul id="fence_details_list">
+ <li><span class="fence_details_label">Hostname</span>${details[1]}</li>
+ <li><span class="fence_details_label">IP Address</span>${details[2]}</li>
+ <li><span class="fence_details_label">Port</span>${details[3]}</li>
+ </ul>
+ </div>
+
+ <!--! Details - Nodes section. -->
+ <div class="details_section">
+ <a href="${tg.url('/fence_manage')}" class="float_button">Manage Nodes</a>
+ <h4>Nodes</h4>
+ <div class="details_inner">
+ <table id="fence_tnodes">
+ <thead>
+ <tr class="grid_row">
+ <th class="icon"></th>
+ <th class="fence_tnodes_name"></th>
+ <th class="fence_tnodes_status">status</th>
+ <th class="fence_tnodes_level">level</th>
+ <td class="table_space"></td>
+ </tr>
+ </thead>
+ <tbody>
+ <!--! List all the nodes connected with the current shared fence. -->
+ <tr py:for="node in details[4]" class="grid_row">
+ <td class="icon">
+ <img py:if="node[1] is not None" src="${tg.url('/images/exclamation.png')}" alt="Node has a problem." />
+ </td>
+ <td class="fence_tnodes_name"><span py:attrs="node[1] is None and {'class': 'entity_ok'} or {'class': 'entity_fail'}">${node[0]}</span></td>
+ <td class="fence_tnodes_status">${node[1] is not None and node[1] or 'OK'}</td>
+ <td class="fence_tnodes_level">${node[2]}</td>
+ <td class="table_space"></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
</py:otherwise>
14 years, 10 months
[luci] Initial part of shared fences, changes in CSS.
by Jan Pokorný
commit 9e51c41c2e479184e64fb9c8798fe0c6300c7df8
Author: Jan Pokorny <jpokorny(a)redhat.com>
Date: Thu Aug 6 17:52:55 2009 +0200
Initial part of shared fences, changes in CSS.
luci/controllers/root.py | 22 ++++
luci/public/css/fdom.css | 137 +---------------------
luci/public/css/fence.css | 32 +++++
luci/public/css/shared.css | 181 +++++++++++++++++++++++++++++
luci/public/css/style.css | 2 +
luci/public/images/global_11x11_black.png | Bin 0 -> 323 bytes
luci/public/images/reboot-grey.png | Bin 0 -> 373 bytes
luci/public/images/reboot-white.png | Bin 0 -> 310 bytes
luci/templates/fdom.html | 57 +++++----
luci/templates/fence.html | 80 +++++++++++++
10 files changed, 355 insertions(+), 156 deletions(-)
---
diff --git a/luci/controllers/root.py b/luci/controllers/root.py
index 86d2537..0f3df25 100644
--- a/luci/controllers/root.py
+++ b/luci/controllers/root.py
@@ -123,3 +123,25 @@ class RootController(BaseController):
if fdom_table.has_key(name):
details = fdom_table[name]
return dict(name=name, fdoms=fdoms, details=details)
+
+ @expose('luci.templates.fence')
+ def fence(self, name=""):
+ fence_table = { \
+ # Temporary data for fences, format:
+ # name: (type, address, port, [("node", status, level), ...])
+ "Fence A":("iLO", "123.123.78.90", 6666,
+ [("NodeDelta", "Something Terrible", 1)]),
+ "Fence B":("APC Power Device", "123.123.78.11", 6667,
+ [("NodeAlpha", None, 1), ("NodeDelta", "Something Terrible", 2)]),
+ "Fence C":("Virtual Machine", "123.123.78.156", 11023,
+ [("NodeAlpha", None, 1), ("NodeBeta", None, 2),
+ ("NodeGamma", None, 3),("NodeDelta", "Something Terrible")]),
+ "Fence D":("SCSI Reservation", "123.123.78.35", 5487,
+ [("NodeAlpha", None, 1), ("NodeBeta", None, 2),
+ ("NodeGamma", None, 3)])}
+ details = None
+ fences = [[k,v[0],len(v[3]),v[1]] for k,v in fence_table.iteritems()]
+ if name != "":
+ if fence_table.has_key(name):
+ details = fence_table[name]
+ return dict(name=name, fences=fences, details=details)
diff --git a/luci/public/css/fdom.css b/luci/public/css/fdom.css
index 521f7b6..799910f 100644
--- a/luci/public/css/fdom.css
+++ b/luci/public/css/fdom.css
@@ -1,146 +1,21 @@
-/*=============================== G E N E R A L ============================= */
-
-/* --------------------------------- toolbar -------------------------------- */
-
-.toolbar {
- padding-left: 1em;
- line-height: 23px;
- height: 31px;
- background: #484646 url(../images/toolbar_line.png) top left repeat-x;
-}
-
-.toolbar a {
- margin: 0 1em 0 1em;
- color: #ffffff;
- text-decoration: none;
-}
-
-.toolbar a:hover {
- color: #8e8e8e;
- text-decoration: none;
-}
-
-/* toolbar's buttons */
-
-#tb_add {
- background: url('../images/add-white.png') center left no-repeat;
- padding-left: 17px;
-}
-
-#tb_add:hover {
- background-image: url('../images/add-grey.png');
-}
-
-#tb_delete {
- background: url('../images/delete-white.png') center left no-repeat;
- padding-left: 17px;
-}
-
-#tb_delete:hover {
- background-image: url('../images/delete-grey.png');
-}
-
-
-/* --------------------------------- details -------------------------------- */
-
-.details {
-
-}
-
-
-/* ---------------------------------- misc. --------------------------------- */
-
-.table_space {
- width: 100%;
-}
-
-.float_right {
- float: right;
-}
-
-.float_left {
- float: left;
-}
-
-.input_priority {
- width: 3em;
-}
-
/*====================== F A I L O V E R D O M A I N S ==================== */
/* -------------------------------- overview -------------------------------- */
-/* list of failover domains */
-
-#fdom_tlist {
- border-collapse: collapse;
-}
-
-#fdom_tlist td,th {
- padding: 0 1em;
-}
-
-#fdom_tlist th {
- font-weight: normal;
-}
-
-.fdom_tlist_name {
- text-align: left;
-}
-
-th.fdom_tlist_name {
- min-width: 20%;
-}
-
-.fdom_tlist_attrs {
+.fdom_list_enabled {
text-align: center;
}
-#fdom_tlist_sep {
- border-top: black solid thin;
- height: .5em;
-}
-
-#fdom_tlist tr.chosen {
- background-color: #d2edaa;
-}
-
-#fdom_tlist tr:hover {
- background-color: #e8f6d4;
+.fdom_list_prioritizied {
+ text-align: center;
}
-#fdom_tlist tr#fdom_tlist_header:hover {
- background-color: inherit;
+.fdom_list_restricted {
+ text-align: center;
}
/* --------------------------------- details -------------------------------- */
-.details_section {
- margin-left: 1em;
-}
-
-.details_section h4 {
- margin: 1em 0 .5em 0;
-}
-
-.details_inner {
- margin-left: 1em;
-}
-
-.details_header {
- padding: 0;
- margin: 0;
- height: 44px;
- color: #ffffff;
- background: #5b5a5b url(../images/details_header_line.png) top left repeat-x;
-}
-
-.details_header h3 {
- margin-left: 1em;
- font-weight: bold;
-}
-
-
/* attributes setting */
#fdom_tattr {
@@ -190,7 +65,7 @@ fdom_tattr_hint {
}
#fdom_tmembers td,th {
- padding: 0 1em;
+ /*padding: 0 1em;*/
}
#fdom_tmembers th {
diff --git a/luci/public/css/fence.css b/luci/public/css/fence.css
new file mode 100644
index 0000000..77aad4b
--- /dev/null
+++ b/luci/public/css/fence.css
@@ -0,0 +1,32 @@
+/*=========================== S H A R E D F E N C E S ===================== */
+
+/* -------------------------------- overview -------------------------------- */
+
+.fence_list_type {
+ text-align: left;
+}
+
+th.fence_list_type {
+ min-width: 20%;
+}
+
+.fence_list_members {
+ text-align: left;
+}
+
+th.fence_list_members {
+ min-width: 20%;
+}
+
+td.fence_list_members {
+ margin: 0 0 0 11px;
+}
+
+.fence_list_ip {
+ text-align: left;
+}
+
+th.fence_list_ip {
+ min-width: 15%;
+}
+
diff --git a/luci/public/css/shared.css b/luci/public/css/shared.css
new file mode 100644
index 0000000..2b503c8
--- /dev/null
+++ b/luci/public/css/shared.css
@@ -0,0 +1,181 @@
+/*=============================== G E N E R A L ============================= */
+
+/* ---------------------------------- misc. --------------------------------- */
+
+.table_space {
+ width: 100%;
+}
+
+.float_right {
+ float: right;
+}
+
+.float_left {
+ float: left;
+}
+
+.input_priority {
+ width: 3em;
+}
+
+/* --------------------------------- toolbar -------------------------------- */
+
+.toolbar {
+ padding-left: 1em;
+ line-height: 23px;
+ height: 31px;
+ background: #484646 url(../images/toolbar_line.png) top left repeat-x;
+}
+
+.toolbar a {
+ margin: 0 1em 0 1em;
+ color: #ffffff;
+ text-decoration: none;
+}
+
+.toolbar a:hover {
+ color: #8e8e8e;
+ text-decoration: none;
+}
+
+/* toolbar's buttons */
+
+/* update */
+#tb_update {
+ background: url('../images/reboot-white.png') center left no-repeat;
+ padding-left: 17px;
+}
+
+#tb_update:hover {
+ background-image: url('../images/reboot-grey.png');
+}
+
+/* add */
+#tb_add {
+ background: url('../images/add-white.png') center left no-repeat;
+ padding-left: 17px;
+}
+
+#tb_add:hover {
+ background-image: url('../images/add-grey.png');
+}
+
+/* delete */
+#tb_delete {
+ background: url('../images/delete-white.png') center left no-repeat;
+ padding-left: 17px;
+}
+
+#tb_delete:hover {
+ background-image: url('../images/delete-grey.png');
+}
+
+
+/* -------------------------------- overview -------------------------------- */
+
+.overview {
+ margin: 0 0 5px 0;
+}
+
+.overview table {
+ border-collapse: collapse;
+}
+
+.overview table td,th {
+ padding: 1px 10px;
+}
+
+.overview table th {
+ font-weight: normal;
+}
+
+.overview table tr.even {
+ background-color: #f4f4f4;
+}
+
+.overview table tr.chosen {
+ background-color: #d2edaa;
+}
+
+.overview table tbody tr:hover {
+ background-color: #fafdf5;
+}
+
+.overview table tbody tr.chosen:hover {
+ background-color: #d2edaa;
+}
+
+.overview table tbody tr.even:hover {
+ background-color: #e8f6d4;
+}
+
+#table_sep {
+ padding: 0;
+}
+
+#table_sep hr {
+ padding: 0;
+ margin: 0 15px 5px 15px;
+}
+
+/* common columns */
+
+/* first checkbox */
+.overview table .checkbox {
+ padding: 0 0 0 5px;
+ text-align: center;
+}
+
+/* place for icon (icon itself not always used) */
+.overview table .icon {
+ padding: 0 2px;
+ width: 11px;
+ display: block;
+ text-align: center;
+}
+
+/* main identifier (with active link) */
+.overview table .main_id {
+ text-align: left;
+}
+
+.overview table th.main_id {
+ min-width: 20%;
+}
+
+.overview table td.main_id a {
+ text-decoration: none;
+ color: inherit;
+}
+
+.overview table td.main_id a:hover {
+ text-decoration: underline;
+}
+
+/* --------------------------------- details -------------------------------- */
+
+.details_section {
+ margin-left: 1em;
+}
+
+.details_section h4 {
+ margin: 1em 0 .5em 0;
+}
+
+.details_inner {
+ margin-left: 1em;
+}
+
+.details_header {
+ padding: 0;
+ margin: 0;
+ height: 44px;
+ color: #ffffff;
+ background: #5b5a5b url(../images/details_header_line.png) top left repeat-x;
+}
+
+.details_header h3 {
+ margin: 0 0 0 1em;
+ font-weight: bold;
+}
+
diff --git a/luci/public/css/style.css b/luci/public/css/style.css
index 8ca6f90..0aecce5 100644
--- a/luci/public/css/style.css
+++ b/luci/public/css/style.css
@@ -1,4 +1,6 @@
+@import url("shared.css");
@import url("fdom.css");
+@import url("fence.css");
html {
background: #555555 url('../images/pagebg.png') top left repeat-x;
diff --git a/luci/public/images/global_11x11_black.png b/luci/public/images/global_11x11_black.png
new file mode 100644
index 0000000..7ed0cf3
Binary files /dev/null and b/luci/public/images/global_11x11_black.png differ
diff --git a/luci/public/images/reboot-grey.png b/luci/public/images/reboot-grey.png
new file mode 100644
index 0000000..e836f3f
Binary files /dev/null and b/luci/public/images/reboot-grey.png differ
diff --git a/luci/public/images/reboot-white.png b/luci/public/images/reboot-white.png
new file mode 100644
index 0000000..bf45d85
Binary files /dev/null and b/luci/public/images/reboot-white.png differ
diff --git a/luci/templates/fdom.html b/luci/templates/fdom.html
index 3ff4041..72229a4 100644
--- a/luci/templates/fdom.html
+++ b/luci/templates/fdom.html
@@ -22,30 +22,37 @@
<!--! Overview section. -->
<div class="overview">
- <table id="fdom_tlist">
- <tr id="fdom_tlist_header">
- <th class="fdom_tlist_check"></th>
- <th class="fdom_tlist_name">name</th>
- <th class="fdom_tlist_attrs">enabled</th>
- <th class="fdom_tlist_attrs">prioritizied</th>
- <th class="fdom_tlist_attrs">restricted</th>
- <td class="table_space"></td>
- </tr>
- <tr id="fdom_tlist_sep">
- </tr>
-
- <!--! List all the failover domains. -->
- <tr py:for="fdom in fdoms" py:attrs="{'class': 'chosen'} if fdom[0]==name else None">
- <td class="fdom_tlist_check"><input type="checkbox"/></td>
- <td class="fdom_tlist_name"><a href="${tg.url('/fdom?name=' + fdom[0])}">${fdom[0]}</a></td>
- <td class="fdom_tlist_attrs" py:choose="False">
- <input py:when="True" type="checkbox" disabled="true" checked="checked"/>
- <input py:when="False" type="checkbox" disabled="true"/>
- </td>
- <td class="fdom_tlist_attrs"><img py:if="fdom[1]" src="${tg.url('/images/dot.png')}" alt="*" title="Attribute is active."/></td>
- <td class="fdom_tlist_attrs"><img py:if="fdom[2]" src="${tg.url('/images/dot.png')}" alt="*" title="Attribute is active."/></td>
- <td></td>
- </tr>
+ <table>
+ <thead>
+ <tr>
+ <th class="checkbox"></th>
+ <th class="icon"></th>
+ <th class="main_id">name</th>
+ <th class="fdom_list_enabled">enabled</th>
+ <th class="fdom_list_prioritizied">prioritizied</th>
+ <th class="fdom_list_restricted">restricted</th>
+ <td class="table_space"></td>
+ </tr>
+ <tr>
+ <td colspan="7" id="table_sep"><hr /></td>
+ </tr>
+ </thead>
+ <tbody>
+ <!--! List all the failover domains. -->
+ <tr py:for="i, fdom in enumerate(fdoms)"
+ py:attrs="fdom[0]==name and {'class': 'chosen'} or (not i%2 and {'class': 'even'} or None)">
+ <td class="checkbox"><input type="checkbox"/></td>
+ <td class="icon"></td>
+ <td class="main_id"><a href="${tg.url('/fdom?name=' + fdom[0])}">${fdom[0]}</a></td>
+ <td class="fdom_list_enabled" py:choose="False">
+ <input py:when="True" type="checkbox" disabled="true" checked="checked"/>
+ <input py:when="False" type="checkbox" disabled="true"/>
+ </td>
+ <td class="fdom_list_prioritizied"><img py:if="fdom[1]" src="${tg.url('/images/dot.png')}" alt="*" title="Attribute is active."/></td>
+ <td class="fdom_list_restricted"><img py:if="fdom[2]" src="${tg.url('/images/dot.png')}" alt="*" title="Attribute is active."/></td>
+ <td></td>
+ </tr>
+ </tbody>
</table>
</div>
</form>
@@ -54,7 +61,7 @@
<div class="details" py:choose="details">
<div class="details_header" py:if="details != None">
- <h3 py:replace="name">Failover1</h3>
+ <h3 py:content="name">Failover1</h3>
</div>
<py:when test="None">
diff --git a/luci/templates/fence.html b/luci/templates/fence.html
new file mode 100644
index 0000000..68003f2
--- /dev/null
+++ b/luci/templates/fence.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<xi:include href="master.html" />
+
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title>Fences</title>
+</head>
+
+<body>
+
+ <form>
+ <div class="toolbar">
+ <a href="./" id="tb_update">update</a>
+ <input type="submit" value="delete"/>
+ <!-- <a href="./" id="tb_delete">delete</a> -->
+ <a href="./" id="tb_add">add</a>
+ </div>
+
+ <!--! Overview section. -->
+ <div class="overview">
+ <table>
+ <thead>
+ <tr>
+ <th class="checkbox"></th>
+ <th class="icon"></th>
+ <th class="main_id">name</th>
+ <th class="fence_list_type">fence type</th>
+ <th class="fence_list_members">member nodes</th>
+ <th class="fence_list_ip">IP address</th>
+ <td class="table_space"></td>
+ </tr>
+ <tr>
+ <td colspan="7" id="table_sep"><hr /></td>
+ </tr>
+ </thead>
+ <tbody>
+ <!--! List all the shared fences. -->
+ <tr py:for="i, fence in enumerate(fences)"
+ py:attrs="fence[0]==name and {'class': 'chosen'} or (not i%2 and {'class': 'even'} or None)">
+ <td class="checkbox"><input type="checkbox"/></td>
+ <td class="icon"></td>
+ <td class="main_id"><a href="${tg.url('/fence?name=' + fence[0])}">${fence[0]}</a></td>
+ <td class="fence_list_type">${fence[1]}</td>
+ <td class="fence_list_members">${fence[2]}</td>
+ <td class="fence_list_ip">${fence[3]}</td>
+ <td></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </form>
+
+
+ <!--! Details section. -->
+ <div class="details" py:choose="details">
+
+ <div class="details_header" py:if="details != None">
+ <h3 py:content="name">Fence1</h3>
+ </div>
+
+ <py:when test="None">
+ <div class="details_section">
+ Please select a fence from the list.
+ </div>
+ </py:when>
+
+ <py:otherwise>
+ <!--! Details - Attributes section. -->
+
+ </py:otherwise>
+
+ </div>
+
+</body>
+</html>
14 years, 10 months