Commit 30198f04 authored by Iustin Pop's avatar Iustin Pop
Browse files

Merge remote branch 'devel-2.1'



* devel-2.1:
  Update import documentation for the recent changes
  Add a identify-defaults options for import
  Fix create/import verification of hvparams
  objects.Cluster: add method to get hv defaults
  Reuse NIC information from export
  Reuse backend parameters from export
  Reuse disk information from export
  Reuse hypervisor parameters in import
  Read disk template from export info
  CreateInstance: separate the reading of the export
  Move code from ExpandNames to CheckPrereq
  CreateInstance: Move some code to CheckArguments
  Export more instance parameters in instance export
  Export the nicparams too during instance export
  Handle errors better for wrong nic_count in export
  QA: Make sure RAPI credentials are setup on cluster init

Conflicts:
	lib/cmdlib.py (strange conflict, HEAD had no changes)
Signed-off-by: default avatarIustin Pop <iustin@google.com>
Reviewed-by: default avatarMichael Hanselmann <hansmi@google.com>
parents e7a25b08 33ea43b6
......@@ -320,11 +320,13 @@ them out of the Ganeti exports directory.
Importing an instance is similar to creating a new one, but additionally
one must specify the location of the snapshot. The command is::
gnt-backup import -n TARGET_NODE -t DISK_TEMPLATE \
gnt-backup import -n TARGET_NODE \
--src-node=NODE --src-dir=DIR INSTANCE_NAME
Most of the options available for the command :command:`gnt-instance
add` are supported here too.
By default, parameters will be read from the export information, but you
can of course pass them in via the command line - most of the options
available for the command :command:`gnt-instance add` are supported here
too.
Import of foreign instances
+++++++++++++++++++++++++++
......
......@@ -2091,6 +2091,7 @@ def FinalizeExport(instance, snap_disks):
config.set(constants.INISECT_INS, 'vcpus', '%d' %
instance.beparams[constants.BE_VCPUS])
config.set(constants.INISECT_INS, 'disk_template', instance.disk_template)
config.set(constants.INISECT_INS, 'hypervisor', instance.hypervisor)
nic_total = 0
for nic_count, nic in enumerate(instance.nics):
......@@ -2098,8 +2099,9 @@ def FinalizeExport(instance, snap_disks):
config.set(constants.INISECT_INS, 'nic%d_mac' %
nic_count, '%s' % nic.mac)
config.set(constants.INISECT_INS, 'nic%d_ip' % nic_count, '%s' % nic.ip)
config.set(constants.INISECT_INS, 'nic%d_bridge' % nic_count,
'%s' % nic.bridge)
for param in constants.NICS_PARAMETER_TYPES:
config.set(constants.INISECT_INS, 'nic%d_%s' % (nic_count, param),
'%s' % nic.nicparams.get(param, None))
# TODO: redundant: on load can read nics until it doesn't exist
config.set(constants.INISECT_INS, 'nic_count' , '%d' % nic_total)
......@@ -2116,6 +2118,17 @@ def FinalizeExport(instance, snap_disks):
config.set(constants.INISECT_INS, 'disk_count' , '%d' % disk_total)
# New-style hypervisor/backend parameters
config.add_section(constants.INISECT_HYP)
for name, value in instance.hvparams.items():
if name not in constants.HVC_GLOBALS:
config.set(constants.INISECT_HYP, name, str(value))
config.add_section(constants.INISECT_BEP)
for name, value in instance.beparams.items():
config.set(constants.INISECT_BEP, name, str(value))
utils.WriteFile(utils.PathJoin(destdir, constants.EXPORT_CONF_FILE),
data=config.Dumps())
shutil.rmtree(finaldestdir, ignore_errors=True)
......
......@@ -72,6 +72,7 @@ __all__ = [
"HVOPTS_OPT",
"HYPERVISOR_OPT",
"IALLOCATOR_OPT",
"IDENTIFY_DEFAULTS_OPT",
"IGNORE_CONSIST_OPT",
"IGNORE_FAILURES_OPT",
"IGNORE_REMOVE_FAILURES_OPT",
......@@ -948,6 +949,13 @@ MAINTAIN_NODE_HEALTH_OPT = \
" health, by shutting down unknown instances, shutting down"
" unknown DRBD devices, etc.")
IDENTIFY_DEFAULTS_OPT = \
cli_option("--identify-defaults", dest="identify_defaults",
default=False, action="store_true",
help="Identify which saved instance parameters are equal to"
" the current cluster defaults and set them as such, instead"
" of marking them as overridden")
def _ParseArgs(argv, commands, aliases):
"""Parser for the command line arguments.
......@@ -1563,9 +1571,12 @@ def GenericInstanceCreate(mode, opts, args):
elif opts.no_nics:
# no nics
nics = []
else:
elif mode == constants.INSTANCE_CREATE:
# default of one nic, all auto
nics = [{}]
else:
# mode == import
nics = []
if opts.disk_template == constants.DT_DISKLESS:
if opts.disks or opts.sd_size is not None:
......@@ -1573,18 +1584,23 @@ def GenericInstanceCreate(mode, opts, args):
" information passed")
disks = []
else:
if not opts.disks and not opts.sd_size:
if (not opts.disks and not opts.sd_size
and mode == constants.INSTANCE_CREATE):
raise errors.OpPrereqError("No disk information specified")
if opts.disks and opts.sd_size is not None:
raise errors.OpPrereqError("Please use either the '--disk' or"
" '-s' option")
if opts.sd_size is not None:
opts.disks = [(0, {"size": opts.sd_size})]
try:
disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
except ValueError, err:
raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
disks = [{}] * disk_max
if opts.disks:
try:
disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
except ValueError, err:
raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
disks = [{}] * disk_max
else:
disks = []
for didx, ddict in opts.disks:
didx = int(didx)
if not isinstance(ddict, dict):
......@@ -1618,12 +1634,14 @@ def GenericInstanceCreate(mode, opts, args):
src_node = None
src_path = None
no_install = opts.no_install
identify_defaults = False
elif mode == constants.INSTANCE_IMPORT:
start = False
os_type = None
src_node = opts.src_node
src_path = opts.src_dir
no_install = None
identify_defaults = opts.identify_defaults
else:
raise errors.ProgrammerError("Invalid creation mode %s" % mode)
......@@ -1646,7 +1664,8 @@ def GenericInstanceCreate(mode, opts, args):
os_type=os_type,
src_node=src_node,
src_path=src_path,
no_install=no_install)
no_install=no_install,
identify_defaults=identify_defaults)
SubmitOrSend(op, opts)
return 0
......
This diff is collapsed.
......@@ -319,6 +319,8 @@ FILE_DRIVER = frozenset([FD_LOOP, FD_BLKTAP])
# import/export config options
INISECT_EXP = "export"
INISECT_INS = "instance"
INISECT_HYP = "hypervisor"
INISECT_BEP = "backend"
# dynamic device modification
......
......@@ -934,6 +934,30 @@ class Cluster(TaggableObject):
obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
return obj
def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
"""Get the default hypervisor parameters for the cluster.
@param hypervisor: the hypervisor name
@param os_name: if specified, we'll also update the defaults for this OS
@param skip_keys: if passed, list of keys not to use
@return: the defaults dict
"""
if skip_keys is None:
skip_keys = []
fill_stack = [self.hvparams.get(hypervisor, {})]
if os_name is not None:
os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
fill_stack.append(os_hvp)
ret_dict = {}
for o_dict in fill_stack:
ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
return ret_dict
def FillHV(self, instance, skip_globals=False):
"""Fill an instance's hvparams dict.
......@@ -952,18 +976,9 @@ class Cluster(TaggableObject):
else:
skip_keys = []
# We fill the list from least to most important override
fill_stack = [
self.hvparams.get(instance.hypervisor, {}),
self.os_hvp.get(instance.os, {}).get(instance.hypervisor, {}),
instance.hvparams,
]
ret_dict = {}
for o_dict in fill_stack:
ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
return ret_dict
def_dict = self.GetHVDefaults(instance.hypervisor, instance.os,
skip_keys=skip_keys)
return FillDict(def_dict, instance.hvparams, skip_keys=skip_keys)
def FillBE(self, instance):
"""Fill an instance's beparams dict.
......
......@@ -465,7 +465,7 @@ class OpCreateInstance(OpCode):
"os_type", "force_variant", "no_install",
"pnode", "disk_template", "snode", "mode",
"disks", "nics",
"src_node", "src_path", "start",
"src_node", "src_path", "start", "identify_defaults",
"wait_for_sync", "ip_check", "name_check",
"file_storage_dir", "file_driver",
"iallocator",
......
......@@ -21,6 +21,7 @@
<year>2007</year>
<year>2008</year>
<year>2009</year>
<year>2010</year>
<holder>Google Inc.</holder>
</copyright>
&dhdate;
......@@ -29,7 +30,7 @@
&dhucpackage;
&dhsection;
<refmiscinfo>ganeti 2.0</refmiscinfo>
<refmiscinfo>ganeti 2.1</refmiscinfo>
</refmeta>
<refnamediv>
<refname>&dhpackage;</refname>
......@@ -143,7 +144,7 @@
<arg>--src-dir=<replaceable>source-dir</replaceable></arg>
<sbr>
<arg choice="req">-t<group>
<arg choice="opt">-t<group>
<arg>diskless</arg>
<arg>plain</arg>
<arg>drbd</arg>
......@@ -151,8 +152,12 @@
</group></arg>
<sbr>
<arg choice="opt">--identify-defaults</arg>
<sbr>
<arg choice="req"><replaceable>instance</replaceable></arg>
</cmdsynopsis>
<para>
Imports a new instance from an export residing on
<replaceable>source-node</replaceable> in
......@@ -167,11 +172,11 @@
<para>
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
zero. 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
<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.
......@@ -185,7 +190,13 @@
</para>
<para>
The minimum disk specification is therefore
If no disk information is passed, the disk configuration saved
at export time will be used.
</para>
<para>
The minimum disk specification is therefore empty (export
information will be used), a single disk can be specified as
<userinput>--disk 0:size=20G</userinput> (or <userinput>-s
20G</userinput> when using the <option>-s</option> option),
and a three-disk instance can be specified as
......@@ -195,10 +206,10 @@
<para>
The NICs of the instances can be specified via the
<option>--net</option> option. By default, one NIC is created
for the instance, with the MAC set to the original MAC of the
instance (as it was at export time). Each NIC can take up to
three parameters (all optional):
<option>--net</option> option. By default, the NIC
configuration of the original (exported) instance will be
reused. Each NIC can take up to three parameters (all
optional):
<variablelist>
<varlistentry>
<term>mac</term>
......@@ -240,15 +251,15 @@
</para>
<para>
Alternatively, if no network is desired for the instance, you
can prevent the default of one NIC with the
<option>--no-nics</option> option.
If no network is desired for the instance, you should create a
single empty NIC and delete it afterwards
via <command>gnt-instance modify --net delete</command>.
</para>
<para>
The <option>-B</option> option specifies the backend
parameters for the instance. If no such parameters are
specified, the values are inherited from the cluster. Possible
specified, the values are inherited from the export. Possible
parameters are:
<variablelist>
<varlistentry>
......@@ -278,8 +289,9 @@
</para>
<para>
The <option>-t</option> options specifies the disk layout type for
the instance. The available choices are:
The <option>-t</option> options specifies the disk layout type
for the instance. If not passed, the configuration of the
original instance is used. The available choices are:
<variablelist>
<varlistentry>
<term>diskless</term>
......@@ -334,13 +346,26 @@
</para>
<para>
If you do not want gnt-backup to wait for the disk mirror
to be synced, use the <option>--no-wait-for-sync</option>
option.
Since many of the parameters are by default read from the
exported instance information and used as such, the new
instance will have all parameters explicitly specified, the
opposite of a newly added instance which has most parameters
specified via cluster defaults. To change the import behaviour
to recognize parameters whose saved value matches the current
cluster default and mark it as such (default value), pass
the <option>--identify-defaults</option> option. This will
affect the hypervisor, backend and NIC parameters, both read
from the export file and passed in via the command line.
</para>
<para>
Example:
Example for identical instance import:
<screen>
# gnt-backup import -n node1.example.com instance3.example.com
</screen>
</para>
<para>
Explicit configuration example:
<screen>
# gnt-backup import -t plain --disk 0:size=1G -B memory=512 \
> -n node1.example.com \
......
......@@ -6,6 +6,9 @@
"os": "debian-etch",
"mem": "512M",
"rapi-user": "foobar",
"rapi-pass": "barfoo",
"# Lists of disk sizes": null,
"disk": ["1G", "512M"],
"disk-growth": ["2G", "768M"],
......
......@@ -78,6 +78,21 @@ def TestClusterInit():
AssertEqual(StartSSH(master['primary'],
utils.ShellQuoteArgs(cmd)).wait(), 0)
# Create RAPI credentials
rapi_user = qa_config.get("rapi-user", default=None)
rapi_pass = qa_config.get("rapi-pass", default=None)
if rapi_user and rapi_pass:
cmds = []
cred_string = "%s %s write" % (rapi_user, rapi_pass)
cmds.append(("echo %s >> %s" %
(utils.ShellQuote(cred_string),
utils.ShellQuote(constants.RAPI_USERS_FILE))))
cmds.append("%s stop-master" % constants.DAEMON_UTIL)
cmds.append("%s start-master" % constants.DAEMON_UTIL)
AssertEqual(StartSSH(master['primary'], ' && '.join(cmds)).wait(), 0)
def TestClusterRename():
"""gnt-cluster rename"""
......
......@@ -130,22 +130,23 @@ def RemoveExport(opts, args):
# this is defined separately due to readability only
import_opts = [
NODE_PLACEMENT_OPT,
BACKEND_OPT,
DISK_TEMPLATE_OPT,
DISK_OPT,
OS_SIZE_OPT,
DISK_TEMPLATE_OPT,
FILESTORE_DIR_OPT,
FILESTORE_DRIVER_OPT,
HYPERVISOR_OPT,
IALLOCATOR_OPT,
IDENTIFY_DEFAULTS_OPT,
NET_OPT,
NODE_PLACEMENT_OPT,
NOIPCHECK_OPT,
NONAMECHECK_OPT,
NONICS_OPT,
NWSYNC_OPT,
OS_SIZE_OPT,
SRC_DIR_OPT,
SRC_NODE_OPT,
NOIPCHECK_OPT,
NONAMECHECK_OPT,
IALLOCATOR_OPT,
FILESTORE_DIR_OPT,
FILESTORE_DRIVER_OPT,
HYPERVISOR_OPT,
SUBMIT_OPT,
]
......
......@@ -79,6 +79,16 @@ class TestClusterObject(unittest.TestCase):
self.fake_cl = objects.Cluster(hvparams=hvparams, os_hvp=os_hvp)
self.fake_cl.UpgradeConfig()
def testGetHVDefaults(self):
cl = self.fake_cl
self.failUnlessEqual(cl.GetHVDefaults(constants.HT_FAKE),
cl.hvparams[constants.HT_FAKE])
self.failUnlessEqual(cl.GetHVDefaults(None), {})
self.failUnlessEqual(cl.GetHVDefaults(constants.HT_XEN_PVM,
os_name="lenny-image"),
cl.os_hvp["lenny-image"][constants.HT_XEN_PVM])
def testFillHvFullMerge(self):
inst_hvparams = {
"blah": "blubb",
......
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