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
