On Thu, Jun 27, 2013 at 11:38:13AM +0300, Vladislav Bogdanov wrote:
> 26.06.2013 18:30, Dejan Muhamedagic wrote:
> > On Wed, Jun 26, 2013 at 06:13:33PM +0300, Vladislav Bogdanov wrote:
> >> 26.06.2013 15:57, Dejan Muhamedagic wrote:
> >>> On Thu, Jun 06, 2013 at 05:19:03PM +0200, Dejan Muhamedagic wrote:
> >>>> Hi,
> >>>>
> >>>> On Thu, Jun 06, 2013 at 03:11:16PM +0300, Vladislav Bogdanov wrote:
> >>>>> 06.06.2013 08:43, Vladislav Bogdanov wrote:
> >>>>> [...]
> >>>>>>>>>> I recall that LDAP has similar problem, which is easily worked 
> >>>>>>>>>> around
> >>>>>>>>>> with specifying two values, one is original, second is new.
> >>>>>>>>>> That way you tell LDAP server:
> >>>>>>>>>> Replace value Y in attribute X to value Z. And if value is not Y 
> >>>>>>>>>> at the
> >>>>>>>>>> moment of modification request, then command fails.
> >>>>>>>>>
> >>>>>>>>> "cibadmin --patch" works this way
> >>>>>>>>
> >>>>>>>> Who is baking new CIB in that case, cibadmin or cib?
> >>>>>>>
> >>>>>>> The patch is applied on the server - so "cib"
> >>>>>>
> >>>>>> Then that is safe way to go, assuming that cib daemon serializes
> >>>>>> modification requests.
> >>>>>>
> >>>>>
> >>>>> It would be great if crmsh use that trick.
> >>>>
> >>>> Hope to have something soon. Stay tuned.
> >>>
> >>> The patch for crmsh is attached and you'll need the very latest
> >>> pacemaker (because cibadmin needed some fixing). Unfortunately,
> >>> I cannot push this yet to the repository, as the current
> >>> pacemaker 1.1.10-rc still identifies itself as 1.1.9. I'd
> >>> appreciate if you could test it.
> >>
> >> Seems to work during preliminary testing (stop clone with crm configure
> >> edit and then start it with crm resource start).
> >> cib process on the DC reports it received the diff and handles that
> >> perfectly.
> >>
> >> Thank you!
> >>
> >> I'll build updated package with this patch tomorrow and try to put that
> >> into real work.
> >> I mean to try concurrent updates.
> >> What would be the best way to achieve them?
> >>
> >> Is starting editing with crm configure edit with some concurrent command
> >> during that editing is enough (and save after command is run)?
> > 
> 
> I meant to ask when does crmsh gets original epoch to construct diff, at
> the very beginning of editing, or right before commiting - there can be
> rather big timeframe between that points.
> 
> It would be nice to have an intelligent "patcher" which takes one CIB
> snapshot at the beginning of edit, than generates a diff and looks if it
> applies to a current CIB cleanly (all except epoch). Then it would be
> possible to use current epoch in a diff which goes to a cib daemon.
> I do not know does it make a sense.
> 
> May be there is a better way to not loose big edits due to some small
> unrelated changes were made meanwhile?
> 
> Or may be you can describe algorithm you use for those who do not know
> python?

The alogorithm is in cibconfig.py:_patch_cib(). But nothing
very interesting there.

If you want to test here's a new patch. It does work with
unrelated changes happening in the meantime. I didn't test yet
really concurrent updates.

Again: Please use this patch _only_ with the latest v1.1.10.

Cheers,

Dejan
# HG changeset patch
# User Dejan Muhamedagic <[email protected]>
# Date 1372430089 -7200
#      Fri Jun 28 16:34:49 2013 +0200
# Node ID 8b429e0a38b2419642fc36dc25cafff764bacf00
# Parent  b66f2014c2accc0d48d0e926baf82ba973209715
High: cibconfig: use cibadmin patch to update live CIB

diff -r b66f2014c2ac -r 8b429e0a38b2 modules/cibconfig.py
--- a/modules/cibconfig.py	Fri Jun 28 14:54:09 2013 +0200
+++ b/modules/cibconfig.py	Fri Jun 28 16:34:49 2013 +0200
@@ -1928,7 +1928,6 @@ class CibFactory(Singleton):
         self._init_vars()
         self.regtest = options.regression_tests
         self.last_commit_time = 0
-        self.all_committed = True # has commit produced error
         self._no_constraint_rm_msg = False # internal (just not to produce silly messages)
         self.supported_cib_re = "^pacemaker-1[.][012]$"
     def is_cib_sane(self):
@@ -2044,26 +2043,24 @@ class CibFactory(Singleton):
         if not self.is_cib_supported():
             self.reset()
             return False
-        for attr in self.cib_elem.keys():
-            self.cib_attrs[attr] = self.cib_elem.get(attr)
+        self._get_cib_attributes(self.cib_elem)
         schema.init_schema(self.cib_elem)
         return True
     #
     # create a doc from the list of objects
     # (used by CibObjectSetRaw)
     #
-    def _regtest_filter(self, cib):
-        for attr in ("epoch", "admin_epoch"):
-            if cib.get(attr):
-                cib.set(attr, "0")
-        for attr in ("cib-last-written",):
-            if cib.get(attr):
-                del cib.attrib[attr]
+    def bump_epoch(self):
+        try:
+            self.cib_attrs["epoch"] = str(int(self.cib_attrs["epoch"])+1)
+        except:
+            self.cib_attrs["epoch"] = "1"
+    def _get_cib_attributes(self, cib):
+        for attr in cib.keys():
+            self.cib_attrs[attr] = cib.get(attr)
     def _set_cib_attributes(self, cib):
         for attr in self.cib_attrs:
             cib.set(attr, self.cib_attrs[attr])
-        if self.regtest:
-            self._regtest_filter(cib)
     def objlist2doc(self, obj_list, obj_filter=None):
         '''
         Return document containing objects in obj_list.
@@ -2096,8 +2093,6 @@ class CibFactory(Singleton):
             cib_attr = None
         return c.get(a) == cib_attr
     def is_current_cib_equal(self, silent=False):
-        if self.overwrite:
-            return True
         cib_elem = read_cib(cibdump2elem)
         if cib_elem is None:
             return False
@@ -2122,9 +2117,10 @@ class CibFactory(Singleton):
         'Commit the configuration to the CIB.'
         if not self.is_cib_sane():
             return False
-        # all_committed is updated in the invoked object methods
-        self.all_committed = True
-        rc = self._commit_doc(force)
+        if cibadmin_can_patch():
+            rc = self._patch_cib(force)
+        else:
+            rc = self._replace_cib(force)
         if rc:
             # reload the cib!
             common_debug("CIB commit successful")
@@ -2132,8 +2128,8 @@ class CibFactory(Singleton):
                 self.last_commit_time = time.time()
             self.reset()
             self.initialize()
-        return self.all_committed
-    def _commit_schema(self):
+        return rc
+    def _update_schema(self):
         '''
         Set the validate-with, if the schema changed.
         '''
@@ -2144,13 +2140,13 @@ class CibFactory(Singleton):
             return False
         self.new_schema = False
         return True
-    def _commit_doc(self, force):
+    def _replace_cib(self, force):
         try:
             conf_el = self.cib_elem.findall("configuration")[0]
         except IndexErr:
             common_error("cannot find the configuration element")
             return False
-        if self.new_schema and not self._commit_schema():
+        if self.new_schema and not self._update_schema():
             return False
         cibadmin_opts = force and "-R --force" or "-R"
         rc = pipe_string("%s %s" % (cib_piped, cibadmin_opts), etree.tostring(conf_el))
@@ -2158,6 +2154,37 @@ class CibFactory(Singleton):
             update_err("cib", cibadmin_opts, etree.tostring(conf_el), rc)
             return False
         return True
+    def _patch_cib(self, force):
+        # copy the epoch from the current cib to both the target
+        # cib and the original one (otherwise cibadmin won't want
+        # to apply the patch)
+        current_cib = read_cib(cibdump2elem)
+        if current_cib is None:
+            return False
+        self._get_cib_attributes(current_cib)
+        self._set_cib_attributes(self.cib_orig)
+        current_cib = None # don't need that anymore
+        # now increase the epoch by 1
+        self.bump_epoch()
+        self._set_cib_attributes(self.cib_elem)
+        cib_s = etree.tostring(self.cib_orig, pretty_print=True)
+        tmpf = str2tmp(cib_s, suffix=".xml")
+        if not tmpf:
+            return False
+        vars.tmpfiles.append(tmpf)
+        cibadmin_opts = force and "-P --force" or "-P"
+        # produce a diff:
+        # dump_new_conf | crm_diff -o self.cib_orig -n -
+        rc, cib_diff = filter_string("crm_diff -o %s -n -" % \
+            tmpf, etree.tostring(self.cib_elem))
+        if not cib_diff:
+            common_err("crm_diff apparently failed to produce the diff (rc=%d)" % rc)
+            return False
+        rc = pipe_string("%s %s" % (cib_piped,cibadmin_opts), cib_diff)
+        if rc != 0:
+            update_err("cib", cibadmin_opts, cib_diff, rc)
+            return False
+        return True
     #
     # initialize cib_objects from CIB
     #
@@ -2203,21 +2230,24 @@ class CibFactory(Singleton):
         if not self._import_cib():
             return False
         sanitize_cib(self.cib_elem)
+        if cibadmin_can_patch():
+            self.cib_orig = copy.deepcopy(self.cib_elem)
         show_unrecognized_elems(self.cib_elem)
         self._populate()
         return self.check_structure()
     def _init_vars(self):
         self.cib_elem = None  # the cib
+        self.cib_orig = None  # the file holding the CIB which we loaded
         self.cib_attrs = {} # cib version dictionary
         self.cib_objects = [] # a list of cib objects
         self.remove_queue = [] # a list of cib objects to be removed
         self.id_refs = {} # dict of id-refs
-        self.overwrite = False # update cib unconditionally
         self.new_schema = False # schema changed
     def reset(self):
         if self.cib_elem is None:
             return
         self.cib_elem = None
+        self.cib_orig = None
         self._init_vars()
         id_store.clear()
     def find_object(self, obj_id):
diff -r b66f2014c2ac -r 8b429e0a38b2 modules/cibstatus.py
--- a/modules/cibstatus.py	Fri Jun 28 14:54:09 2013 +0200
+++ b/modules/cibstatus.py	Fri Jun 28 16:34:49 2013 +0200
@@ -104,7 +104,7 @@ class CibStatus(Singleton):
     def _load_cib(self, source):
         if source == "live":
             if not self.backing_file:
-                self.backing_file = cib2tmp()
+                self.backing_file = cibdump2tmp()
                 if not self.backing_file:
                     return None
                 vars.tmpfiles.append(self.backing_file)
diff -r b66f2014c2ac -r 8b429e0a38b2 modules/msg.py
--- a/modules/msg.py	Fri Jun 28 14:54:09 2013 +0200
+++ b/modules/msg.py	Fri Jun 28 16:34:49 2013 +0200
@@ -153,11 +153,15 @@ def update_err(obj_id, cibadm_opt, xml, 
         task = "update"
     elif cibadm_opt == '-D':
         task = "delete"
+    elif cibadm_opt == '-P':
+        task = "patch"
     else:
         task = "replace"
-    err_buf.error("could not %s %s"%(task, obj_id))
+    err_buf.error("could not %s %s (rc=%d)" % (task, obj_id, rc))
     if rc == 54:
         err_buf.info("Permission denied.")
+    elif task == "patch":
+        err_buf.info("offending xml diff: %s" % xml)
     else:
         err_buf.info("offending xml: %s" % xml)
 
diff -r b66f2014c2ac -r 8b429e0a38b2 modules/ui.py.in
--- a/modules/ui.py.in	Fri Jun 28 14:54:09 2013 +0200
+++ b/modules/ui.py.in	Fri Jun 28 16:34:49 2013 +0200
@@ -325,7 +325,7 @@ class CibShadow(UserInterface):
         if not is_filename_sane(name):
             return False
         if ext_cmd("%s -C '%s' --force" % (self.extcmd, name)) == 0:
-            common_info("commited '%s' shadow CIB to the cluster"%name)
+            common_info("committed '%s' shadow CIB to the cluster"%name)
         else:
             common_err("failed to commit the %s shadow CIB"%name)
             return False
@@ -1763,16 +1763,18 @@ class CibConfig(UserInterface):
             common_info("apparently there is nothing to commit")
             common_info("try changing something first")
             return
-        rc1 = cib_factory.is_current_cib_equal()
+        rc1 = True
+        if not (force or cibadmin_can_patch()):
+            rc1 = cib_factory.is_current_cib_equal()
         rc2 = cib_factory.is_cib_empty() or \
             self._verify(mkset_obj("xml", "changed"), mkset_obj("xml"))
         if rc1 and rc2:
             return cib_factory.commit()
         if force or user_prefs.get_force():
             common_info("commit forced")
-            return cib_factory.commit(True)
+            return cib_factory.commit(force=True)
         if ask("Do you still want to commit?"):
-            return cib_factory.commit(True)
+            return cib_factory.commit(force=True)
         return False
     def upgrade(self, cmd, force=None):
         "usage: upgrade [force]"
diff -r b66f2014c2ac -r 8b429e0a38b2 modules/utils.py
--- a/modules/utils.py	Fri Jun 28 14:54:09 2013 +0200
+++ b/modules/utils.py	Fri Jun 28 16:34:49 2013 +0200
@@ -183,12 +183,12 @@ def filter_string(cmd, s, stderr_on=True
         common_info("from: %s" % cmd)
     return rc, outp
 
-def str2tmp(s):
+def str2tmp(s, suffix=".pcmk"):
     '''
     Write the given string to a temporary file. Return the name
     of the file.
     '''
-    fd, tmp = mkstemp(suffix=".pcmk")
+    fd, tmp = mkstemp(suffix=suffix)
     try:
         f = os.fdopen(fd,"w")
     except IOError, msg:
@@ -873,7 +873,7 @@ def get_cib_attributes(cib_f, tag, attr_
     f.close()
     return val_l
 
-def is_pcmk_118(cib_f=None):
+def is_min_pcmk_ver(min_ver, cib_f=None):
     if not vars.pcmk_version:
         if cib_f:
             vars.pcmk_version = get_cib_property(cib_f, "dc-version", "1.1.1")
@@ -882,7 +882,12 @@ def is_pcmk_118(cib_f=None):
         else:
             vars.pcmk_version = get_pcmk_version("1.1.1")
     from distutils.version import LooseVersion
-    return LooseVersion(vars.pcmk_version) >= LooseVersion("1.1.8")
+    return LooseVersion(vars.pcmk_version) >= LooseVersion(min_ver)
+def is_pcmk_118(cib_f=None):
+    return is_min_pcmk_ver("1.1.8", cib_f=cib_f)
+def cibadmin_can_patch():
+    return True
+    #return is_min_pcmk_ver("1.1.10")
 
 user_prefs = UserPrefs.getInstance()
 options = Options.getInstance()
diff -r b66f2014c2ac -r 8b429e0a38b2 modules/xmlutil.py
--- a/modules/xmlutil.py	Fri Jun 28 14:54:09 2013 +0200
+++ b/modules/xmlutil.py	Fri Jun 28 16:34:49 2013 +0200
@@ -55,7 +55,7 @@ def cibdump2file(fname):
         common_err(msg)
         return None
     return str2file(s, fname)
-def cib2tmp():
+def cibdump2tmp():
     cmd = add_sudo(cib_dump)
     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
     try:
_______________________________________________
Linux-HA mailing list
[email protected]
http://lists.linux-ha.org/mailman/listinfo/linux-ha
See also: http://linux-ha.org/ReportingProblems

Reply via email to