+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)
# 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],
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
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
"""
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
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
"""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
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)
" '%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.
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$'):
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
<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>