Commit b701a5df authored by Iustin Pop's avatar Iustin Pop
Browse files

Merge remote branch 'origin/devel-2.1'



* origin/devel-2.1:
  Implement QA tests for disk template changes
  Update instance modify documentation
  Implement conversion from drbd to plain
  Implement conversion from plain to drbd
  Abstract check that an instance is down
  Abstract node free disk space check
  Abstract disk template verification
  Update documentation for disk adoption
  Implement disk adoption mode in gnt-instance
  LUCreateInstance: implement disk adoption mode
  LUCreateInstance: Move parameter init earlier
  ConfigWriter: add an LV reservation manager
  Fix two issues related to check-man
  utils.RunCmd: Test case with reset_env set and setting variables
Signed-off-by: default avatarIustin Pop <iustin@google.com>
Reviewed-by: default avatarMichael Hanselmann <hansmi@google.com>
parents bb3776b4 7f69aabb
......@@ -417,6 +417,7 @@ man/%.7.in man/%.8.in: man/%.sgml man/footer.sgml $(DOCBOOK_WRAPPER)
@test -n "$(DOCBOOK2MAN)" || \
{ echo 'docbook2man' not found during configure; exit 1; }
$(DOCBOOK_WRAPPER) "$(DOCBOOK2MAN)" $< $(notdir $(@:.in=)) $@
$(CHECK_MAN) $@
man/%.html.in: man/%.sgml man/footer.sgml $(DOCBOOK_WRAPPER)
@test -n "$(DOCBOOK2HTML)" || \
......@@ -425,7 +426,6 @@ man/%.html.in: man/%.sgml man/footer.sgml $(DOCBOOK_WRAPPER)
man/%.7: man/%.7.in $(REPLACE_VARS_SED)
sed -f $(REPLACE_VARS_SED) < $< > $@
$(CHECK_MAN) $@
man/%.8: man/%.8.in $(REPLACE_VARS_SED)
sed -f $(REPLACE_VARS_SED) < $< > $@
......
......@@ -20,7 +20,7 @@
set -e
! LC_ALL=C MANWIDTH=80 \
! MANWIDTH=80 \
man --warnings --encoding=utf8 --local-file "$1" 2>&1 >/dev/null | \
grep -v -e "cannot adjust line" -e "can't break line" | \
grep .
......@@ -326,6 +326,27 @@ one must specify the location of the snapshot. The command is::
Most of the options available for the command :command:`gnt-instance
add` are supported here too.
Import of foreign instances
+++++++++++++++++++++++++++
There is a possibility to import a foreign instance whose disk data is
already stored as LVM volumes without going through copying it: the disk
adoption mode.
For this, ensure that the original, non-managed instance is stopped,
then create a Ganeti instance in the usual way, except that instead of
passing the disk information you specify the current volumes::
gnt-instance add -t plain -n HOME_NODE ... \
--disk 0:adopt=lv_name INSTANCE_NAME
This will take over the given logical volumes, rename them to the Ganeti
standard (UUID-based), and without installing the OS on them start
directly the instance. If you configure the hypervisor similar to the
non-managed configuration that the instance had, the transition should
be seamless for the instance. For more than one disk, just pass another
disk parameter (e.g. ``--disk 1:adopt=...``).
Instance HA features
--------------------
......@@ -491,6 +512,33 @@ command::
Note that this will fail if the disks already exists.
Conversion of an instance's disk type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is possible to convert between a non-redundant instance of type
``plain`` (LVM storage) and redundant ``drbd`` via the ``gnt-instance
modify`` command::
# start with a non-redundant instance
gnt-instance add -t plain ... INSTANCE
# later convert it to redundant
gnt-instance stop INSTANCE
gnt-instance modify -t drbd INSTANCE
gnt-instance start INSTANCE
# and convert it back
gnt-instance stop INSTANCE
gnt-instance modify -t plain INSTANCE
gnt-instance start INSTANCE
The conversion must be done while the instance is stopped, and
converting from plain to drbd template presents a small risk, especially
if the instance has multiple disks and/or if one node fails during the
conversion procedure). As such, it's recommended (as always) to make
sure that downtime for manual recovery is acceptable and that the
instance has up-to-date backups.
Debugging instances
+++++++++++++++++++
......
......@@ -1538,13 +1538,23 @@ def GenericInstanceCreate(mode, opts, args):
if not isinstance(ddict, dict):
msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
raise errors.OpPrereqError(msg)
elif "size" not in ddict:
raise errors.OpPrereqError("Missing size for disk %d" % didx)
try:
ddict["size"] = utils.ParseUnit(ddict["size"])
except ValueError, err:
raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
(didx, err))
elif "size" in ddict:
if "adopt" in ddict:
raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
" (disk %d)" % didx)
try:
ddict["size"] = utils.ParseUnit(ddict["size"])
except ValueError, err:
raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
(didx, err))
elif "adopt" in ddict:
if mode == constants.INSTANCE_IMPORT:
raise errors.OpPrereqError("Disk adoption not allowed for instance"
" import")
ddict["size"] = 0
else:
raise errors.OpPrereqError("Missing size or adoption source for"
" disk %d" % didx)
disks[didx] = ddict
utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
......
This diff is collapsed.
......@@ -125,6 +125,9 @@ class TemporaryReservationManager:
class ConfigWriter:
"""The interface to the cluster configuration.
@ivar _temporary_lvs: reservation manager for temporary LVs
@ivar _all_rms: a list of all temporary reservation managers
"""
def __init__(self, cfg_file=None, offline=False):
self.write_count = 0
......@@ -139,6 +142,9 @@ class ConfigWriter:
self._temporary_drbds = {}
self._temporary_macs = TemporaryReservationManager()
self._temporary_secrets = TemporaryReservationManager()
self._temporary_lvs = TemporaryReservationManager()
self._all_rms = [self._temporary_ids, self._temporary_macs,
self._temporary_secrets, self._temporary_lvs]
# Note: in order to prevent errors when resolving our name in
# _DistributeConfig, we compute it here once and reuse it; it's
# better to raise an error before starting to modify the config
......@@ -190,6 +196,20 @@ class ConfigWriter:
else:
self._temporary_macs.Reserve(mac, ec_id)
@locking.ssynchronized(_config_lock, shared=1)
def ReserveLV(self, lv_name, ec_id):
"""Reserve an VG/LV pair for an instance.
@type lv_name: string
@param lv_name: the logical volume name to reserve
"""
all_lvs = self._AllLVs()
if lv_name in all_lvs:
raise errors.ReservationError("LV already in use")
else:
self._temporary_lvs.Reserve(lv_name, ec_id)
@locking.ssynchronized(_config_lock, shared=1)
def GenerateDRBDSecret(self, ec_id):
"""Generate a DRBD secret.
......@@ -1451,6 +1471,5 @@ class ConfigWriter:
"""Drop per-execution-context reservations
"""
self._temporary_ids.DropECReservations(ec_id)
self._temporary_macs.DropECReservations(ec_id)
self._temporary_secrets.DropECReservations(ec_id)
for rm in self._all_rms:
rm.DropECReservations(ec_id)
......@@ -620,7 +620,8 @@ class OpSetInstanceParams(OpCode):
__slots__ = [
"instance_name",
"hvparams", "beparams", "force",
"nics", "disks",
"nics", "disks", "disk_template",
"remote_node",
]
......
......@@ -75,7 +75,10 @@
<sbr>
<group choice="req">
<arg rep="repeat">--disk=<replaceable>N</replaceable>:size=<replaceable>VAL</replaceable><arg>,mode=<replaceable>ro|rw</replaceable></arg></arg>
<arg rep="repeat">--disk=<replaceable>N</replaceable>:<group choice="req">
<arg>size=<replaceable>VAL</replaceable></arg>
<arg>adopt=<replaceable>LV</replaceable></arg>
</group>,mode=<replaceable>ro|rw</replaceable></arg>
<arg>-s <replaceable>SIZE</replaceable></arg>
</group>
<sbr>
......@@ -126,15 +129,28 @@
The <option>disk</option> option specifies the parameters
for the disks of the instance. The numbering of disks starts
at zero, and at least one disk needs to be passed. For each
disk, at least the size needs to be given, and optionally
the access mode (read-only or the default of read-write) can
also be specified. The size is interpreted (when no unit is
given) in mebibytes. You can also use one of the suffixes
disk, either the size or the adoption source needs to be
given, and optionally the access mode (read-only or the
default of read-write) can also be specified. The size is
interpreted (when no unit is given) in mebibytes. You can
also use one of the suffixes
<literal>m</literal>, <literal>g</literal> or
<literal>t</literal> to specificy the exact the units used;
these suffixes map to mebibytes, gibibytes and tebibytes.
</para>
<para>
When using the <option>adopt</option> key in the disk
definition, Ganeti will reuse those volumes (instead of
creating new ones) as the instance's disks. Ganeti will
rename these volumes to the standard format, and (without
installing the OS) will use them as-is for the
instance. This allows migrating instances from non-managed
mode (e.q. plain KVM with LVM) to being managed via
Ganeti. Note that this works only for the `plain' disk
template (see below for template details).
</para>
<para>
Alternatively, a single-disk instance can be created via the
<option>-s</option> option which takes a single argument,
......@@ -1381,6 +1397,13 @@ instance5: 11225
<arg>--disk <replaceable>N</replaceable>:mode=<replaceable>MODE</replaceable></arg>
</group>
<sbr>
<arg>-t<group choice="req">
<arg>plain</arg>
<arg>drbd</arg>
</group></arg>
<sbr>
<arg>--submit</arg>
<sbr>
......@@ -1400,6 +1423,13 @@ instance5: 11225
in the form of <userinput>name=value[,...]</userinput>. For details which options can be specified, see the <command>add</command> command.
</para>
<para>
The <option>-t</option> option will change the disk template
of the instance. Currently only conversions between the
plain and drbd disk templates are supported, and the
instance must be stopped before attempting the conversion.
</para>
<para>
The <option>--disk
add:size=<replaceable>SIZE</replaceable></option> option
......
......@@ -317,6 +317,8 @@ def main():
try:
instance = RunTest(func, pnode, snode)
RunCommonInstanceTests(instance)
if qa_config.TestEnabled('instance-convert-disk'):
RunTest(qa_instance.TestInstanceConvertDisk, instance, snode)
RunExportImportTests(instance, pnode)
RunHardwareFailureTests(instance, pnode, snode)
RunTest(qa_instance.TestInstanceRemove, instance)
......
......@@ -62,6 +62,7 @@
"instance-add-plain-disk": true,
"instance-add-drbd-disk": true,
"instance-convert-disk": true,
"instance-automatic-restart": false,
"instance-consecutive-failures": false,
......
......@@ -209,6 +209,17 @@ def TestInstanceModify(instance):
utils.ShellQuoteArgs(cmd)).wait(), 0)
def TestInstanceConvertDisk(instance, snode):
"""gnt-instance modify -t"""
master = qa_config.GetMasterNode()
cmd = ['gnt-instance', 'modify', '-t', 'plain', instance['name']]
AssertEqual(StartSSH(master['primary'],
utils.ShellQuoteArgs(cmd)).wait(), 0)
cmd = ['gnt-instance', 'modify', '-t', 'drbd', '-n', snode, instance['name']]
AssertEqual(StartSSH(master['primary'],
utils.ShellQuoteArgs(cmd)).wait(), 0)
def TestInstanceList():
"""gnt-instance list"""
master = qa_config.GetMasterNode()
......
......@@ -1207,7 +1207,7 @@ def SetInstanceParams(opts, args):
@return: the desired exit code
"""
if not (opts.nics or opts.disks or
if not (opts.nics or opts.disks or opts.disk_template or
opts.hvparams or opts.beparams):
ToStderr("Please give at least one of the parameters.")
return 1
......@@ -1247,9 +1247,18 @@ def SetInstanceParams(opts, args):
errors.ECODE_INVAL)
disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
if (opts.disk_template and
opts.disk_template in constants.DTS_NET_MIRROR and
not opts.node):
ToStderr("Changing the disk template to a mirrored one requires"
" specifying a secondary node")
return 1
op = opcodes.OpSetInstanceParams(instance_name=args[0],
nics=opts.nics,
disks=opts.disks,
disk_template=opts.disk_template,
remote_node=opts.node,
hvparams=opts.hvparams,
beparams=opts.beparams,
force=opts.force)
......@@ -1261,7 +1270,7 @@ def SetInstanceParams(opts, args):
ToStdout("Modified instance %s", args[0])
for param, data in result:
ToStdout(" - %-5s -> %s", param, data)
ToStdout("Please don't forget that these parameters take effect"
ToStdout("Please don't forget that most parameters take effect"
" only at the next start of the instance.")
return 0
......@@ -1407,7 +1416,8 @@ commands = {
"Replaces all disks for the instance"),
'modify': (
SetInstanceParams, ARGS_ONE_INSTANCE,
[BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT],
[BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
DISK_TEMPLATE_OPT, SINGLE_NODE_OPT],
"<instance>", "Alters the parameters of an instance"),
'shutdown': (
GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
......
......@@ -240,6 +240,8 @@ class TestRunCmd(testutils.GanetiTestCase):
def testResetEnv(self):
"""Test environment reset functionality"""
self.failUnlessEqual(RunCmd(["env"], reset_env=True).stdout.strip(), "")
self.failUnlessEqual(RunCmd(["env"], reset_env=True,
env={"FOO": "bar",}).stdout.strip(), "FOO=bar")
class TestRunParts(unittest.TestCase):
......
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