[Erp5-report] r16940 - in /erp5/trunk/products/CMFActivity: ./ dtml/

nobody at svn.erp5.org nobody at svn.erp5.org
Wed Oct 10 18:16:19 CEST 2007


Author: vincent
Date: Wed Oct 10 18:16:18 2007
New Revision: 16940

URL: http://svn.erp5.org?rev=16940&view=rev
Log:
Store existing nodes as a BTree:
 - limits conflict errors (the list is automaticaly modified, though rarely)
 - allows to store key, value pairs
Cache processingNode computation result.
Add accessors to node btree.
Remove manage_addNode as it's now automaticaly done.
Update iser interface and add new required methods.
Automaticaly register self as an available processing node when process_timer is called.
Use distributing node accessor instead of accessing the value directly.
Empty processing node list and no defined distributing node means now that the feature is disabled, not that every node distributes and process.
Display the ip:port value which identifies current node on the user interface.

Modified:
    erp5/trunk/products/CMFActivity/ActivityTool.py
    erp5/trunk/products/CMFActivity/dtml/manageLoadBalancing.dtml

Modified: erp5/trunk/products/CMFActivity/ActivityTool.py
URL: http://svn.erp5.org/erp5/trunk/products/CMFActivity/ActivityTool.py?rev=16940&r1=16939&r2=16940&view=diff
==============================================================================
--- erp5/trunk/products/CMFActivity/ActivityTool.py (original)
+++ erp5/trunk/products/CMFActivity/ActivityTool.py Wed Oct 10 18:16:18 2007
@@ -49,6 +49,7 @@
 from Products.CMFActivity.ActiveObject import DISTRIBUTABLE_STATE, INVOKE_ERROR_STATE, VALIDATE_ERROR_STATE
 from ActivityBuffer import ActivityBuffer
 from zExceptions import ExceptionFormatter
+from BTrees.OIBTree import OIBTree
 
 from ZODB.POSException import ConflictError
 from Products.MailHost.MailHost import MailHostError
@@ -72,6 +73,9 @@
 tic_lock = threading.Lock() # A RAM based lock to prevent too many concurrent tic() calls
 timerservice_lock = threading.Lock() # A RAM based lock to prevent TimerService spamming when busy
 first_run = 1
+currentNode = None
+ROLE_IDLE = 0
+ROLE_PROCESSING = 1
 
 # Activity Registration
 activity_dict = {}
@@ -301,9 +305,6 @@
     allowed_types = ( 'CMF Active Process', )
     security = ClassSecurityInfo()
 
-    _distributingNode = ''
-    _nodes = ()
-
     manage_options = tuple(
                      [ { 'label' : 'Overview', 'action' : 'manage_overview' }
                      , { 'label' : 'Activities', 'action' : 'manageActivities' }
@@ -402,17 +403,19 @@
        
     def getCurrentNode(self):
         """ Return current node in form ip:port """
-        port = ''
-        from asyncore import socket_map
-        for k, v in socket_map.items():
-            if hasattr(v, 'port'):
-                # see Zope/lib/python/App/ApplicationManager.py: def getServers(self)
-                type = str(getattr(v, '__class__', 'unknown'))
-                if type == 'ZServer.HTTPServer.zhttp_server':
-                    port = v.port
-                    break
-        ip = socket.gethostbyname(socket.gethostname())
-        currentNode = '%s:%s' %(ip, port)
+        global currentNode
+        if currentNode is None:
+          port = ''
+          from asyncore import socket_map
+          for k, v in socket_map.items():
+              if hasattr(v, 'port'):
+                  # see Zope/lib/python/App/ApplicationManager.py: def getServers(self)
+                  type = str(getattr(v, '__class__', 'unknown'))
+                  if type == 'ZServer.HTTPServer.zhttp_server':
+                      port = v.port
+                      break
+          ip = socket.gethostbyname(socket.gethostname())
+          currentNode = '%s:%s' %(ip, port)
         return currentNode
         
     security.declarePublic('getDistributingNode')
@@ -420,11 +423,46 @@
         """ Return the distributingNode """
         return self.distributingNode
 
-    security.declarePublic('getNodeList getNodes')
-    def getNodes(self):
-        """ Return all nodes """
-        return self._nodes
-    getNodeList = getNodes
+    def getNodeList(self, role=None):
+      node_dict = self.getNodeDict()
+      if role is None:
+        result = [x for x in node_dict.keys()]
+      else:
+        result = [node_id for node_id, node_role in node_dict.items() if node_role == role]
+      result.sort()
+      return result
+
+    def getNodeDict(self):
+      nodes = self._nodes
+      if isinstance(nodes, tuple):
+        new_nodes = OIBTree()
+        new_nodes.update([(x, ROLE_PROCESSING) for x in self._nodes])
+        self._nodes = nodes = new_nodes
+      return nodes
+
+    def registerNode(self, node):
+      node_dict = self.getNodeDict()
+      if not node_dict.has_key(node):
+        if len(node_dict) == 0: # If we are registering the first node, make
+                                # it both the distributing node and a processing
+                                # node.
+          role = ROLE_PROCESSING
+          self.distributingNode = node
+        else:
+          role = ROLE_IDLE
+        self.updateNode(node, role)
+
+    def updateNode(self, node, role):
+      node_dict = self.getNodeDict()
+      node_dict[node] = role
+
+    security.declareProtected(CMFCorePermissions.ManagePortal, 'getProcessingNodeList')
+    def getProcessingNodeList(self):
+      return self.getNodeList(role=ROLE_PROCESSING)
+
+    security.declareProtected(CMFCorePermissions.ManagePortal, 'getUnusedNodeList')
+    def getIdleNodeList(self):
+      return self.getNodeList(role=ROLE_IDLE)
 
     def _isValidNodeName(self, node_name) :
       """Check we have been provided a good node name"""
@@ -447,46 +485,64 @@
                   '/manageLoadBalancing?manage_tabs_message=' +
                   urllib.quote("Malformed Distributing Node."))
 
-    security.declarePublic('manage_addNode')
-    def manage_addNode(self, node, REQUEST=None):
-        """ add a node """
-        if not self._isValidNodeName(node) :
-            if REQUEST is not None:
-                REQUEST.RESPONSE.redirect(
-                    REQUEST.URL1 +
-                    '/manageLoadBalancing?manage_tabs_message=' +
-                    urllib.quote("Malformed node."))
-            return
-        
-        if node in self._nodes:
-            if REQUEST is not None:
-                REQUEST.RESPONSE.redirect(
-                    REQUEST.URL1 +
-                    '/manageLoadBalancing?manage_tabs_message=' +
-                    urllib.quote("Node exists already."))
-            return
-            
-        self._nodes = self._nodes + (node,)
-        
-        if REQUEST is not None:
-            REQUEST.RESPONSE.redirect(
-                REQUEST.URL1 +
-                '/manageLoadBalancing?manage_tabs_message=' +
-                urllib.quote("Node successfully added."))
-                        
-    security.declarePublic('manage_delNode')
-    def manage_delNode(self, deleteNodes, REQUEST=None):
-        """ delete nodes """
-        nodeList = list(self._nodes)
-        for node in deleteNodes:
-            if node in self._nodes:
-                nodeList.remove(node)
-        self._nodes = tuple(nodeList)
-        if REQUEST is not None:
-            REQUEST.RESPONSE.redirect(
-                REQUEST.URL1 +
-                '/manageLoadBalancing?manage_tabs_message=' +
-                urllib.quote("Node(s) successfully deleted."))
+    security.declareProtected(CMFCorePermissions.ManagePortal, 'manage_delNode')
+    def manage_delNode(self, unused_node_list=None, REQUEST=None):
+      """ delete selected unused nodes """
+      processing_node = self.getDistributingNode()
+      updated_processing_node = False
+      if unused_node_list is not None:
+        node_dict = self.getNodeDict()
+        for node in unused_node_list:
+          if node in node_dict:
+            del node_dict[node]
+          if node == processing_node:
+            self.processing_node = ''
+            updated_processing_node = True
+      if REQUEST is not None:
+        if unused_node_list is None:
+          message = "No unused node selected, nothing deleted."
+        else:
+          message = "Deleted nodes %r." % (unused_node_list, )
+        if updated_processing_node:
+          message += "Disabled distributing node because it was deleted."
+        REQUEST.RESPONSE.redirect(
+          REQUEST.URL1 +
+          '/manageLoadBalancing?manage_tabs_message=' +
+          urllib.quote(message))
+
+    security.declareProtected(CMFCorePermissions.ManagePortal, 'manage_addToProcessingList')
+    def manage_addToProcessingList(self, unused_node_list=None, REQUEST=None):
+      """ Change one or more idle nodes into processing nodes """
+      if unused_node_list is not None:
+        node_dict = self.getNodeDict()
+        for node in unused_node_list:
+          self.updateNode(node, ROLE_PROCESSING)
+      if REQUEST is not None:
+        if unused_node_list is None:
+          message = "No unused node selected, nothing done."
+        else:
+          message = "Nodes now procesing: %r." % (unused_node_list, )
+        REQUEST.RESPONSE.redirect(
+          REQUEST.URL1 +
+          '/manageLoadBalancing?manage_tabs_message=' +
+          urllib.quote(message))
+
+    security.declareProtected(CMFCorePermissions.ManagePortal, 'manage_removeFromProcessingList')
+    def manage_removeFromProcessingList(self, processing_node_list=None, REQUEST=None):
+      """ Change one or more procesing nodes into idle nodes """
+      if processing_node_list is not None:
+        node_dict = self.getNodeDict()
+        for node in processing_node_list:
+          self.updateNode(node, ROLE_IDLE)
+      if REQUEST is not None:
+        if processing_node_list is None:
+          message = "No used node selected, nothing done."
+        else:
+          message = "Nodes now unused %r." % (processing_node_list, )
+        REQUEST.RESPONSE.redirect(
+          REQUEST.URL1 +
+          '/manageLoadBalancing?manage_tabs_message=' +
+          urllib.quote(message))
 
     def process_timer(self, tick, interval, prev="", next=""):
         """
@@ -508,13 +564,12 @@
           newSecurityManager(self.REQUEST, user)
 
           currentNode = self.getCurrentNode()
+          self.registerNode(currentNode)
+          processing_node_list = self.getNodeList(role=ROLE_PROCESSING)
 
           # only distribute when we are the distributingNode or if it's empty
-          if (self.distributingNode == currentNode):
-            self.distribute(len(self._nodes))
-
-          elif not self.distributingNode:
-            self.distribute(1)
+          if (self.getDistributingNode() == currentNode):
+            self.distribute(len(processing_node_list))
 
           # SkinsTool uses a REQUEST cache to store skin objects, as
           # with TimerService we have the same REQUEST over multiple
@@ -527,11 +582,8 @@
           # call tic for the current processing_node
           # the processing_node numbers are the indices of the elements in the node tuple +1
           # because processing_node starts form 1
-          if currentNode in self._nodes:
-            self.tic(list(self._nodes).index(currentNode)+1)
-
-          elif len(self._nodes) == 0:
-            self.tic(1)
+          if currentNode in processing_node_list:
+            self.tic(processing_node_list.index(currentNode)+1)
 
         finally:
           timerservice_lock.release()

Modified: erp5/trunk/products/CMFActivity/dtml/manageLoadBalancing.dtml
URL: http://svn.erp5.org/erp5/trunk/products/CMFActivity/dtml/manageLoadBalancing.dtml?rev=16940&r1=16939&r2=16940&view=diff
==============================================================================
--- erp5/trunk/products/CMFActivity/dtml/manageLoadBalancing.dtml (original)
+++ erp5/trunk/products/CMFActivity/dtml/manageLoadBalancing.dtml Wed Oct 10 18:16:18 2007
@@ -44,16 +44,22 @@
   Zope Configuration File <i>zope.conf</i>.
 </p>
 
-<p class="form-help">
-    The <i>Distributing node</i> is responsible for the Load Distribution. The default value is empty
-    which means that every existing node will try to do the distribution, which is fine when there
-    is only one ZServer running.
-    To change the Distributing Node, edit the value and click &quot;Change&quot;.
-</p>
-
 <form action="&dtml-URL1;">
 
 <table cellspacing="0" cellpadding="2" border="0" width="100%">
+  <tr class="list-header">
+    <td align="left" valign="top" colspan=2>
+      <div class="form-label">This node</div>
+    </td>
+  </tr>
+  <tr>
+    <td colspan=2>Current node is <b><dtml-var getCurrentNode></b></td>
+  </tr>
+  <tr>
+    <td>&nbsp;</td>
+    <td>&nbsp;</td>
+  </tr>
+  </tr>
   <tr class="list-header">
     <td align="left" valign="top" colspan=2>
       <div class="form-label">Distributing Node</div>
@@ -61,113 +67,114 @@
   </tr>
   <tr>
     <td>
-      <div class="form-label">IP:Port</div>
+      <select name="distributingNode">
+        <option value="">(disabled)</option>
+        <dtml-in getNodeList prefix="node">
+          <dtml-if expr="node_item == getDistributingNode()">
+            <option selected="selected" value="<dtml-var sequence-item>">
+              <dtml-var sequence-item>
+            </option>
+          <dtml-else>
+            <option value="<dtml-var sequence-item>">
+              <dtml-var sequence-item>
+            </option>
+          </dtml-if>
+        </dtml-in>
+      </select>
+      <input type="submit" class="form-element" name="manage_setDistributingNode:method" value=" Change ">
     </td>
-    <td align="left">
-      <div class="form-item">
-        <input type="text" name="distributingNode" value="&dtml-getDistributingNode;" size="19" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>&nbsp;</td>
-    <td align="left">
-      <div class="form-element">
-        <input type="submit" class="form-element" name="manage_setDistributingNode:method" value=" Change ">
-     </div>
+    <td>
+      <p class="form-help">
+        The <i>Distributing node</i> is responsible for the Load Distribution.
+        Only one node can be <i>Distributing node</i> at any given time.
+        It is also possible to interupt activity distribution by selecting the
+        <i>(disabled)</i> value. Activity nodes will receive no more activity
+        when <i>Distributing node</i> is disabled.
+      </p>
     </td>
   </tr>
   <tr>
     <td>&nbsp;</td>
     <td>&nbsp;</td>
   </tr>
-  <tr>
-    <td align="left" colspan=2>
-      <p class="form-help">
-        The following list defines the nodes the Load is currently distributed to. Per default this list is empty
-        which means that every existing node will try to process all activities, which is fine, when ther is only one
-        ZServer running.
-        To delete a node from the list, activate the checkbox on the left of that node and click the &quot;Delete&quot; button.
-      </p>
-    </td>
-  </tr>
   <tr class="list-header">
     <td align="left" valign="top" colspan=2>
       <div class="form-label">Existing Nodes</div>
     </td>
-  <dtml-in getNodes>
-  <dtml-let domain=sequence-key
-          node=sequence-item
-          index=sequence-index>
-  <tr class="row-normal">
-    <td align="left" valign="top">
-      <input type="checkbox" name="deleteNodes:list" value="&dtml-node;">
+  </tr>
+  <tr>
+    <td>
+      <table>
+        <tr>
+          <td rowspan="2">
+            <div class="form-label">Idle&nbsp;nodes</div>
+            <select name="unused_node_list:list" size="10" multiple="multiple" style="width:100%">
+              <dtml-in getIdleNodeList>
+                <option value="<dtml-var sequence-item>"><dtml-var sequence-item></option>
+              </dtml-in>
+            </select>
+          </td>
+          <td>
+            <input type="submit" name="manage_addToProcessingList:method" value="&gt;"/>
+          </td>
+          <td rowspan="2">
+            <div class="form-label">Processing&nbsp;nodes</div>
+            <select name="processing_node_list:list" size="10" multiple="multiple" style="width:100%">
+              <dtml-in getProcessingNodeList prefix="node">
+                <option value="<dtml-var node_item>"><dtml-var node_item> (#<dtml-var expr="node_index + 1">)</option>
+              </dtml-in>
+            </select>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <input type="submit" name="manage_removeFromProcessingList:method" value="&lt;"/>
+          </td>
+        </tr>
+        <tr>
+          <td align="left" colspan="2">
+            <div class="form-element">
+              <input type="submit" class="form-element" name="manage_delNode:method" value=" Delete " />
+           </div>
+          </td>
+        </tr>
+      </table>
     </td>
-    <td align="left" valign="top">
-      <dtml-if "domain == _.None">
-        <p>Default</p>
-      <dtml-else>
-         <p><dtml-var node></p>
-      </dtml-if>
-    </td>
-  </tr>
-  </dtml-let>
-  </dtml-in>
-  <tr>
-    <td align="left" colspan="2">
-      <div class="form-element">
-        <input type="submit" class="form-element" name="manage_delNode:method" value=" Delete " />
-     </div>
+    <td>
+      <p class="form-help">
+        Every node sharing the same ZODB will register itself to the idle
+        node list - except the first one which will be automaticaly declared
+        both <i>Processing node</i> and <i>Distributing node</i>. Registered
+        nodes can then be made <i>Processing nodes</i> or <i>Idle nodes</i>.
+      </p>
+      <p class="form-help">
+       <b>Important note:</b> Nodes can register themselves, but can not
+       unregister themselves (for example, a node which IP has changed will
+       be present twice in the list). It is up to the user to manualy prune
+       obsolete nodes. If non-existant nodes are present in the
+       <i>Processing node</i> list, activities will get balanced to those
+       nodes and never be executed.
+      </p>
     </td>
   </tr>
   <tr>
     <td>&nbsp;</td>
-    <td>&nbsp;</td>
+     <td>&nbsp;</td>
   </tr>
-  <tr>
+  <tr class="list-header">
     <td align="left" valign="top" colspan=2>
-        <p class="form-help">
-          To add a new node, enter the Ip-Address and Port-Number for the new node
-          and click the &quot;Add&quot; button. 
-        </p>
+      <div class="form-label">Subscribe/Unsubscribe from Timer Service</div>
     </td>
   </tr>
   <tr>
-    <td>
-      <div class="form-label">IP:Port</div>
-    </td>
-    <td align="left">
-      <div class="form-item"><input type="text" name="node" size="19" /></div>
-    </td>
-  </tr>
-  <tr>
-    <td>&nbsp;</td>
-    <td align="left">
-      <div class="form-element">
-        <input type="submit" class="form-element" name="manage_addNode:method" value=" Add ">
-     </div>
-    </td>
-  </tr>
-  <tr>
-    <td>&nbsp;</td>
-    <td>&nbsp;</td>
-  </tr>
-  <tr>
-    <td align="left" colspan=2>
-      <p class="form-help">
-        Subscribe/Unsubscribe from Timer Service
-      </p>
-    </td>
-  </tr>
-  <tr class="list-header">
-    <td align="left" valign="top" colspan=2>
+    <td colspan=2>
       <div class="form-label">
         Status:
         <dtml-if isSubscribed>
           Subscribed
         <dtml-else>
           Not Subscribed
-       </dtml-if>
+        </dtml-if>
       </div>
     </td>
   </tr>




More information about the Erp5-report mailing list