diff --git a/NEWS b/NEWS index 6709d3fa226d38a03e182369a338da5ffdecc222..cbbf76a78c842add206e4ed73dc8dd07847c047d 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,33 @@ +Version 2.0.2 + - Added experimental support for stripped logical volumes; this should + enhance performance but comes with a higher complexity in the block + device handling; stripping is only enabled when passing + --with-lvm-stripecount=N to configure, but codepaths are affected + even in the non-stripped mode + - Improved resiliency against transient failures at the end of DRBD + resyncs, and in general of DRBD resync checks + - Fixed a couple of issues with exports and snapshot errors + - Fixed a couple of issues in instance listing + - Added display of the disk size in βgnt-instance infoβ + - Fixed checking for valid OSes in instance creation + - Fixed handling of the βvcpusβ parameter in instance listing and in + general of invalid parameters + - Fixed http server library, and thus RAPI, to handle invalid + username/password combinations correctly; this means that now they + report unauthorized for queries too, not only for modifications, + allowing earlier detect of configuration problems + - Added a new βroleβ node list field, equivalent to the master/master + candidate/drained/offline flags combinations + - Fixed cluster modify and changes of candidate pool size + - Fixed cluster verify error messages for wrong files on regular nodes + - Fixed a couple of issues with node demotion from master candidate + role + - Fixed node readd issues + - Added non-interactive mode for βganeti-masterd --no-votingβ startup + - Added a new β--no-votingβ option for masterfailover to fix failover + on two-nodes clusters when the former master node is unreachable + - Added instance reinstall over RAPI + Version 2.0.1 - added -H/-B startup parameters to gnt-instance, which will allow re-adding the start in single-user option (regression from 1.2) diff --git a/configure.ac b/configure.ac index 76eeb36df835110bb86c12e4fd33e85e8a60282c..12ba06df08c9a98d679eb62c99f6bb6c5bb2166b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # Configure script for Ganeti m4_define([gnt_version_major], [2]) m4_define([gnt_version_minor], [0]) -m4_define([gnt_version_revision], [1]) +m4_define([gnt_version_revision], [2]) m4_define([gnt_version_suffix], []) m4_define([gnt_version_full], m4_format([%d.%d.%d%s], @@ -123,10 +123,10 @@ AC_SUBST(SOCAT_PATH, $socat_path) AC_ARG_WITH([lvm-stripecount], [AS_HELP_STRING([--with-lvm-stripecount=NUM], [the number of stripes to use for LVM volumes] - [ (default is 3)] + [ (default is 1)] )], [lvm_stripecount="$withval"], - [lvm_stripecount="3"]) + [lvm_stripecount="1"]) AC_SUBST(LVM_STRIPECOUNT, $lvm_stripecount) # Check common programs diff --git a/lib/bdev.py b/lib/bdev.py index 4971b53b1e33d1a9164c811f4aae5f62c2d0b0fe..9ac41e50e6db08d3b7d99c5bf0d2ed185f5825cc 100644 --- a/lib/bdev.py +++ b/lib/bdev.py @@ -299,7 +299,7 @@ class LogicalVolume(BlockDev): self._vg_name, self._lv_name = unique_id self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name) self._degraded = True - self.major = self.minor = None + self.major = self.minor = self.pe_size = self.stripe_count = None self.Attach() @classmethod @@ -411,19 +411,30 @@ class LogicalVolume(BlockDev): """ self.attached = False result = utils.RunCmd(["lvs", "--noheadings", "--separator=,", - "-olv_attr,lv_kernel_major,lv_kernel_minor", - self.dev_path]) + "--units=m", "--nosuffix", + "-olv_attr,lv_kernel_major,lv_kernel_minor," + "vg_extent_size,stripes", self.dev_path]) if result.failed: logging.error("Can't find LV %s: %s, %s", self.dev_path, result.fail_reason, result.output) return False - out = result.stdout.strip().rstrip(',') + # the output can (and will) have multiple lines for multi-segment + # LVs, as the 'stripes' parameter is a segment one, so we take + # only the last entry, which is the one we're interested in; note + # that with LVM2 anyway the 'stripes' value must be constant + # across segments, so this is a no-op actually + out = result.stdout.splitlines() + if not out: # totally empty result? splitlines() returns at least + # one line for any non-empty string + logging.error("Can't parse LVS output, no lines? Got '%s'", str(out)) + return False + out = out[-1].strip().rstrip(',') out = out.split(",") - if len(out) != 3: - logging.error("Can't parse LVS output, len(%s) != 3", str(out)) + if len(out) != 5: + logging.error("Can't parse LVS output, len(%s) != 5", str(out)) return False - status, major, minor = out[:3] + status, major, minor, pe_size, stripes = out if len(status) != 6: logging.error("lvs lv_attr is not 6 characters (%s)", status) return False @@ -434,8 +445,22 @@ class LogicalVolume(BlockDev): except ValueError, err: logging.error("lvs major/minor cannot be parsed: %s", str(err)) + try: + pe_size = int(float(pe_size)) + except (TypeError, ValueError), err: + logging.error("Can't parse vg extent size: %s", err) + return False + + try: + stripes = int(stripes) + except (TypeError, ValueError), err: + logging.error("Can't parse the number of stripes: %s", err) + return False + self.major = major self.minor = minor + self.pe_size = pe_size + self.stripe_count = stripes self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing # storage self.attached = True @@ -554,6 +579,13 @@ class LogicalVolume(BlockDev): """Grow the logical volume. """ + if self.pe_size is None or self.stripe_count is None: + if not self.Attach(): + _ThrowError("Can't attach to LV during Grow()") + full_stripe_size = self.pe_size * self.stripe_count + rest = amount % full_stripe_size + if rest != 0: + amount += full_stripe_size - rest # we try multiple algorithms since the 'best' ones might not have # space available in the right place, but later ones might (since # they have less constraints); also note that only recent LVM @@ -1609,7 +1641,8 @@ class DRBD8(BaseDRBD): if len(self._children) != 2 or None in self._children: _ThrowError("drbd%d: cannot grow diskless device", self.minor) self._children[0].Grow(amount) - result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"]) + result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s", + "%dm" % (self.size + amount)]) if result.failed: _ThrowError("drbd%d: resize failed: %s", self.minor, result.output) diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py index 270d61ab7ecd0ab06d5af40ff2dd16cacfc0ab3d..77a7e625d5c0f95e4b6076325c2a1db1cd18881f 100644 --- a/lib/rapi/baserlib.py +++ b/lib/rapi/baserlib.py @@ -250,6 +250,18 @@ class R_Generic(object): " '%s' parameter" % (name,)) return val + def _checkStringVariable(self, name, default=None): + """Return the parsed value of an int argument. + + """ + val = self.queryargs.get(name, default) + if isinstance(val, list): + if val: + val = val[0] + else: + val = default + return val + def getBodyParameter(self, name, *args): """Check and return the value for a given parameter. diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py index fcc4f2d4e115c0e902d0170a7f3d62d904c47295..c6270abed1b24a1656366e8cc9b7bef03b6cb184 100644 --- a/lib/rapi/connector.py +++ b/lib/rapi/connector.py @@ -163,6 +163,8 @@ CONNECTOR.update({ re.compile(r'^/2/instances/([\w\._-]+)/tags$'): rlib2.R_2_instances_name_tags, re.compile(r'^/2/instances/([\w\._-]+)/reboot$'): rlib2.R_2_instances_name_reboot, + re.compile(r'^/2/instances/([\w\._-]+)/reinstall$'): + rlib2.R_2_instances_name_reinstall, re.compile(r'^/2/instances/([\w\._-]+)/shutdown$'): rlib2.R_2_instances_name_shutdown, re.compile(r'^/2/instances/([\w\._-]+)/startup$'): diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index 738f86f029f1a34e9da41e2f1256529de0f7674c..45f649bea6611f3eda98395d9043a500095fdcd1 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -493,6 +493,36 @@ class R_2_instances_name_shutdown(baserlib.R_Generic): return baserlib.SubmitJob([op]) +class R_2_instances_name_reinstall(baserlib.R_Generic): + """/2/instances/[instance_name]/reinstall resource. + + Implements an instance reinstall. + + """ + + DOC_URI = "/2/instances/[instance_name]/reinstall" + + def POST(self): + """Reinstall an instance. + + The URI takes os=name and nostartup=[0|1] optional + parameters. By default, the instance will be started + automatically. + + """ + instance_name = self.items[0] + ostype = self._checkStringVariable('os') + nostartup = self._checkIntVariable('nostartup') + ops = [ + opcodes.OpShutdownInstance(instance_name=instance_name), + opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype), + ] + if not nostartup: + ops.append(opcodes.OpStartupInstance(instance_name=instance_name, + force=False)) + return baserlib.SubmitJob(ops) + + class _R_Tags(baserlib.R_Generic): """ Quasiclass for tagging resources diff --git a/man/gnt-node.sgml b/man/gnt-node.sgml index 5071e5a5e8ca3c2622b46b813f5fe1d7b55f131d..278a6335d80504938c7b22241500427dc0707e53 100644 --- a/man/gnt-node.sgml +++ b/man/gnt-node.sgml @@ -414,13 +414,18 @@ <varlistentry> <term>drained</term> <listitem> - <simpara>whether the node is drained or not</simpara> + <simpara>whether the node is drained or not; the cluster + still communicates with drained nodes but excludes them + from allocation operations</simpara> </listitem> </varlistentry> <varlistentry> <term>offline</term> <listitem> - <simpara>whether the node is offline or not</simpara> + <simpara>whether the node is offline or not; if offline, + the cluster does not communicate with offline nodes; + useful for nodes that are not reachable in order to + avoid delays</simpara> </listitem> </varlistentry> <varlistentry>