Commit 86ea1af6 authored by Ayush Tiwari's avatar Ayush Tiwari Committed by Ayush Tiwari

CommitTool: Intoduction of CommitTool, BusinessCommit and BusinessSnapshot classes

parent d49d0c52
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2017 Nexedi SARL and Contributors. All Rights Reserved.
# Ayush-Tiwari <ayush.tiwari@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import gc
import os
import posixpath
import transaction
import imghdr
import tarfile
import time
import hashlib
import fnmatch
import re
import threading
import pprint
import uuid
from copy import deepcopy
from collections import defaultdict
from cStringIO import StringIO
from OFS.Image import Pdata
from lxml.etree import parse
from urllib import quote, unquote
from OFS import SimpleItem, XMLExportImport
from datetime import datetime
from itertools import chain
from operator import attrgetter
from persistent.list import PersistentList
from AccessControl import ClassSecurityInfo, Unauthorized, getSecurityManager
from Acquisition import Implicit, aq_base, aq_inner, aq_parent
from zLOG import LOG, INFO, WARNING
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type.Core.Folder import Folder
from Products.CMFCore.utils import getToolByName
from Products.PythonScripts.PythonScript import PythonScript
from Products.ERP5Type.dynamic.lazy_class import ERP5BaseBroken
from Products.ERP5Type.Globals import Persistent, PersistentMapping
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5Type.patches.ppml import importXML
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
class BusinessCommit(Folder):
meta_type = 'ERP5 Business Commit'
portal_type = 'Business Commit'
add_permission = Permissions.AddPortalContent
allowed_types = ('Business Item',
'Business Property Item',
'Business Patch item',)
template_format_version = 3
# Factory Type Information
factory_type_information = \
{ 'id' : portal_type
, 'meta_type' : meta_type
, 'icon' : 'file_icon.gif'
, 'product' : 'ERP5Type'
, 'factory' : ''
, 'type_class' : 'BusinessCommit'
, 'immediate_view' : 'BusinessCommit_view'
, 'allow_discussion' : 1
, 'allowed_content_types': ( 'Business Item',
'Business Property Item',
'Business Patch Item',
)
, 'filter_content_types' : 1
}
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = (
PropertySheet.Base,
PropertySheet.XMLObject,
PropertySheet.SimpleItem,
PropertySheet.CategoryCore,
PropertySheet.Version,
)
security.declarePublic('newContent')
def newContent(self, id=None, **kw):
"""
Override newContent so as to use 'id' generated like hash.
Also, copy the objects in the Business Commit after creating new object
"""
if id is None:
id = uuid.uuid1()
return super(BusinessCommit, self).newContent(id, **kw)
def createEquivalentSnapshot(self):
"""
This function uses the current commit to create a new snapshot
"""
portal_commit = self.aq_parent
# Create empty snapshot
snapshot = portal_commit.newContent(portal_type='Business Snapshot')
# Add the current commit as predecessor. This way we can have the BI
# BPI in that commit to the Business Snapshot also.
snapshot.setSimilarValue(self)
self.setSimilarValue(snapshot)
# Build the snapshot
snapshot.buildSnapshot()
return snapshot
def install(self):
"""
Installation:
- check if there is already an equivalent snapshot
- if not, create one and install it
"""
successor_list = self.getPredecessorRelatedValueList()
# Check if the successor list has a snapshot in it
try:
eqv_snapshot = [l for l
in successor_list
if l.getPortalType() == 'Business Snapshot'][0]
except IndexError:
# Create a new equivalent snapshot
eqv_snapshot = self.createEquivalentSnapshot()
for item in eqv_snapshot.objectValues():
item.install(self)
def getItemPathList(self):
return [l.getProperty('item_path') for l in self.objectValues()]
...@@ -719,11 +719,18 @@ class BusinessItem(XMLObject): ...@@ -719,11 +719,18 @@ class BusinessItem(XMLObject):
""" """
Overriden function so that we can update attributes for BusinessItem objects Overriden function so that we can update attributes for BusinessItem objects
""" """
return super(BusinessItem, self)._edit(item_path=item_path,
super(BusinessItem, self)._edit(item_path=item_path,
item_sign=item_sign, item_sign=item_sign,
item_layer=item_layer, item_layer=item_layer,
**kw) **kw)
# Build the object here, if the item_path has been added/updated
# XXX: We also need to add attribute to ensure that this doesn't happen
# while in tests or while creating them on the fly
if 'item_path' in self._v_modified_property_dict:
self.build(self.aq_parent)
def build(self, context, **kw): def build(self, context, **kw):
""" """
Extract value for the given path from the OFS Extract value for the given path from the OFS
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2017 Nexedi SARL and Contributors. All Rights Reserved.
# Ayush-Tiwari <ayush.tiwari@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import gc
import os
import posixpath
import transaction
import imghdr
import tarfile
import time
import hashlib
import fnmatch
import re
import threading
import pprint
from copy import deepcopy
from collections import defaultdict
from cStringIO import StringIO
from OFS.Image import Pdata
from lxml.etree import parse
from urllib import quote, unquote
from OFS import SimpleItem, XMLExportImport
from datetime import datetime
from itertools import chain
from operator import attrgetter
from persistent.list import PersistentList
from AccessControl import ClassSecurityInfo, Unauthorized, getSecurityManager
from Acquisition import Implicit, aq_base, aq_inner, aq_parent
from zLOG import LOG, INFO, WARNING
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type.Core.Folder import Folder
from Products.CMFCore.utils import getToolByName
from Products.PythonScripts.PythonScript import PythonScript
from Products.ERP5Type.dynamic.lazy_class import ERP5BaseBroken
from Products.ERP5Type.Globals import Persistent, PersistentMapping
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5Type.patches.ppml import importXML
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
class BusinessSnapshot(Folder):
meta_type = 'ERP5 Business Snashot'
portal_type = 'Business Snapshot'
add_permission = Permissions.AddPortalContent
allowed_types = ('Business Item',
'Business Property Item',
'Business Patch item',)
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
template_format_version = 3
# Factory Type Information
factory_type_information = \
{ 'id' : portal_type
, 'meta_type' : meta_type
, 'icon' : 'file_icon.gif'
, 'product' : 'ERP5Type'
, 'factory' : ''
, 'type_class' : 'BusinessSnapshot'
, 'immediate_view' : 'BusinessSnapshot_view'
, 'allow_discussion' : 1
, 'allowed_content_types': ( 'Business Item',
'Business Property Item',
'Business Patch Item',
)
, 'filter_content_types' : 1
}
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = (
PropertySheet.Base,
PropertySheet.XMLObject,
PropertySheet.SimpleItem,
PropertySheet.CategoryCore,
PropertySheet.Version,
)
def getLastSnapshot(self):
portal = self.getPortalObject()
commit_tool = portal.portal_commits
# Get the snapshot list except the current snapshot
snapshot_list = [l for l
in commit_tool.objectValues(portal_type='Business Snapshot')
if l != self]
if snapshot_list:
# Get the last created/installed snapshot comparing creation_date
return max(snapshot_list, key=(lambda x: x.getCreationDate()))
return None
def getItemList(self):
"""
Returns the collection of all Business Item, Business Property Item and
Business Patch item at the given snapshot.
"""
return self.objectValues()
def getItemPathList(self):
"""
Returns the path of all Business Item, Business Property Item and
Business Patch item at the given snapshot.
"""
return [l.getProperty('item_path') for l in self.getItemList()]
def buildSnapshot(self):
"""
Using equivalent commit, create a snapshot of ZODB state
"""
new_item_list = []
new_item_path_list = []
# Get the equivalent commit for the snapshot
eqv_commit = self.getSimilarValue()
# Get last created snapshot
last_snapshot = self.getLastSnapshot()
# Commit list of all commits between the last snapshot/first commit and the
# current snapshot
successor_commit_list = []
# If there is last snapshot, then combine all the commit afterwards to create
# new snapshot
if last_snapshot:
# [1]: Extend the item_list with list of items from last snapshot
new_item_list.extend(last_snapshot.getItemList())
new_item_path_list.extend(last_snapshot.getItemPathList())
# Get next predecessor commit for this snapshot using the equivalent commit
# Notice that we don't use the snapshot to get the next commit as the
# snapshot is mere a state which uses `predecessor` just for mentioning the
# equivalent commit.
# Here the first next commit should be the commit created after last snapshot
# which we can get using the equivalent commit of last snapshot and then
# finding the next commit for it
next_commit = last_snapshot.getSimilarValue().getPredecessorRelatedValue()
# If there is no last snapshot, create a new one by combining all the commits
else:
# Get the oldest commit and start from there to find the next commit
oldest_commit = min(
self.aq_parent.objectValues(portal_type='Business Commit'),
key=(lambda x: x.getCreationDate()))
new_item_list.extend(oldest_commit.objectValues())
new_item_path_list.extend(oldest_commit.getItemPathList())
next_commit = oldest_commit.getPredecessorRelatedValue()
# Fill sucessor commit list
while (next_commit.getId() != eqv_commit.getId()):
successor_commit_list.append(next_commit)
next_commit = next_commit.getPredecessorRelatedValue()
# Append the equivalent commit to successor commits
successor_commit_list.append(eqv_commit)
for commit in successor_commit_list:
for item in commit.objectValues():
item_path = item.getProperty('item_path')
if item_path in new_item_path_list:
# Replace the old item with same path with new item
new_item_list = [l if l.getProperty('item_path') != item_path else item for l in new_item_list]
else:
# Add the item to list if there is no existing item at that path
new_item_list.append(item)
new_item_path_list.append(item_path)
# Create hardlinks for the objects
for item in new_item_list:
self._setObject(item.id, item, suppress_events=True)
def install(self):
"""
Install the sub-objects in the commit
"""
for item in self.objectValues():
item.install(self)
...@@ -31,11 +31,13 @@ from webdav.client import Resource ...@@ -31,11 +31,13 @@ from webdav.client import Resource
from App.config import getConfiguration from App.config import getConfiguration
import os import os
import time
import shutil import shutil
import sys import sys
import hashlib import hashlib
import pprint import pprint
import transaction import transaction
import uuid
from Acquisition import Implicit, Explicit from Acquisition import Implicit, Explicit
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
...@@ -131,6 +133,26 @@ class CommitTool (BaseTool): ...@@ -131,6 +133,26 @@ class CommitTool (BaseTool):
- portal_commits/387897938794876-9 (Snapshort of erp5_trade) - portal_commits/387897938794876-9 (Snapshort of erp5_trade)
- portal_commits/387897938794876-10 (Snapshot of erp5_base) - portal_commits/387897938794876-10 (Snapshot of erp5_base)
Draft -> Commited -> Pushed (to repo) |Commit]
Draft -> Installed <-> Uninstalled |Snapshot]
Developer mode: make commits and push them (nothing else)
Developer mode: make snapshots and push them (nothing else)
Installation:
- create an empty snapshot that that's similar to a Commit
- fill it with hard links to commits and snapshots
- install it
Only Draft can be modified
3 types
- Commit - partial state (pushed)
- Save Point - complete state with copies of a single bt (only for
optimisation) (really needed ?) (pushed)
- Snapshort - complete state with hard link for all bt (installed)
We should try first with Commit and Snapshot
""" """
id = 'portal_commits' id = 'portal_commits'
title = 'Commit Tool' title = 'Commit Tool'
...@@ -148,6 +170,39 @@ class CommitTool (BaseTool): ...@@ -148,6 +170,39 @@ class CommitTool (BaseTool):
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareProtected(Permissions.ManagePortal, 'manage_overview') security.declareProtected(Permissions.ManagePortal, 'manage_overview')
manage_overview = DTMLFile('explainCommitTool', _dtmldir) #manage_overview = DTMLFile('explainCommitTool', _dtmldir)
def getCommitList(self):
return self.objectValues(portal_type='Business Commit')
def getSnapshotList(self):
return self.objectValues(portal_type='Business Snapshot')
security.declarePublic('newContent')
def newContent(self, id=None, **kw):
"""
Override newContent so as to use 'id' generated like hash
"""
if id is None:
id = uuid.uuid1()
new_obj = super(CommitTool, self).newContent(id, **kw)
# Add the last commit as its predecessor
commit_list = [l for l
in self.objectValues(portal_type='Business Commit')
if l != new_obj]
latest_commit = max(commit_list, key=(lambda x: x.getCreationDate()))
if new_obj.getPortalType() == 'Business Commit':
# TODO: Add check for no latest_commit. Usable especially for 1st BM
new_obj.setPredecessorValue(latest_commit)
else:
# If the new_obj is Business Snapshot, create a similar value for the
# latest commit
new_obj.setSimilarValue(latest_commit)
latest_commit.setSimilarValue(new_obj)
return new_obj
InitializeClass(CommitTool) InitializeClass(CommitTool)
...@@ -52,7 +52,7 @@ from Tool import CategoryTool, SimulationTool, RuleTool, IdTool, TemplateTool,\ ...@@ -52,7 +52,7 @@ from Tool import CategoryTool, SimulationTool, RuleTool, IdTool, TemplateTool,\
AcknowledgementTool, SolverTool, SolverProcessTool,\ AcknowledgementTool, SolverTool, SolverProcessTool,\
ConversionTool, RoundingTool, UrlRegistryTool, InterfaceTool,\ ConversionTool, RoundingTool, UrlRegistryTool, InterfaceTool,\
CertificateAuthorityTool, InotifyTool, TaskDistributionTool,\ CertificateAuthorityTool, InotifyTool, TaskDistributionTool,\
DiffTool DiffTool, CommitTool
import ERP5Site import ERP5Site
from Document import PythonScript, BusinessManager from Document import PythonScript, BusinessManager
object_classes = ( ERP5Site.ERP5Site, object_classes = ( ERP5Site.ERP5Site,
...@@ -88,7 +88,8 @@ portal_tools = ( CategoryTool.CategoryTool, ...@@ -88,7 +88,8 @@ portal_tools = ( CategoryTool.CategoryTool,
InotifyTool.InotifyTool, InotifyTool.InotifyTool,
TaskDistributionTool.TaskDistributionTool, TaskDistributionTool.TaskDistributionTool,
InterfaceTool.InterfaceTool, InterfaceTool.InterfaceTool,
DiffTool.DiffTool DiffTool.DiffTool,
CommitTool.CommitTool,
) )
content_classes = () content_classes = ()
content_constructors = () content_constructors = ()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment