Commit a91143f1 authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Merge branch 'devel-2.1'



* devel-2.1:
  SSH: do not check IPs
  Add separate module for backported language functionality
  Add make commit-check target
  burnin: skip migration based on hypervisor support
  Add a hypervisor constant for migration support
  LUSetClusterParams: initialize needed parameters
  hv_chroot: move unmount to CleanupInstance()
  Fix indentation error
  utils: Add function for partial application of function arguments
  gnt-instance info: sort the hv parameters
  Only use boot=on on non-ide disks only (KVM)
  Add -usbdevice tablet to KVM when using vnc
  KVM: fix a bug in _TryReadUidFile
  Fix RAPI client methods return values
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarMichael Hanselmann <hansmi@google.com>
parents debed9ae b427788e
......@@ -95,6 +95,7 @@ pkgpython_PYTHON = \
lib/bootstrap.py \
lib/cli.py \
lib/cmdlib.py \
lib/compat.py \
lib/config.py \
lib/constants.py \
lib/daemon.py \
......@@ -335,6 +336,7 @@ python_tests = \
test/ganeti.bdev_unittest.py \
test/ganeti.cli_unittest.py \
test/ganeti.cmdlib_unittest.py \
test/ganeti.compat_unittest.py \
test/ganeti.confd.client_unittest.py \
test/ganeti.config_unittest.py \
test/ganeti.constants_unittest.py \
......@@ -582,4 +584,6 @@ coverage: $(BUILT_SOURCES) $(python_tests)
$(PLAIN_TESTS_ENVIRONMENT) $(abs_top_srcdir)/autotools/gen-coverage \
$(python_tests)
commit-check: distcheck lint apidoc
# vim: set noet :
......@@ -32,6 +32,7 @@ from ganeti import utils
from ganeti import errors
from ganeti import constants
from ganeti import objects
from ganeti import compat
# Size of reads in _CanReadDevice
......@@ -388,7 +389,7 @@ class LogicalVolume(BlockDev):
pvs_info.reverse()
pvlist = [ pv[1] for pv in pvs_info ]
if utils.any(pvlist, lambda v: ":" in v):
if compat.any(pvlist, lambda v: ":" in v):
_ThrowError("Some of your PVs have the invalid character ':' in their"
" name, this is not supported - please filter them out"
" in lvm.conf using either 'filter' or 'preferred_names'")
......@@ -462,7 +463,7 @@ class LogicalVolume(BlockDev):
"""
if (not cls._VALID_NAME_RE.match(name) or
name in cls._INVALID_NAMES or
utils.any(cls._INVALID_SUBSTRINGS, lambda x: x in name)):
compat.any(cls._INVALID_SUBSTRINGS, lambda x: x in name)):
_ThrowError("Invalid LVM name '%s'", name)
def Remove(self):
......
......@@ -37,6 +37,7 @@ from ganeti import luxi
from ganeti import ssconf
from ganeti import rpc
from ganeti import ssh
from ganeti import compat
from optparse import (OptionParser, TitledHelpFormatter,
Option, OptionValueError)
......@@ -2143,7 +2144,7 @@ class JobExecutor(object):
ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
# first, remove any non-submitted jobs
self.jobs, failures = utils.partition(self.jobs, lambda x: x[1])
self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
for idx, _, jid, name in failures:
ToStderr("Failed to submit job for %s: %s", name, jid)
results.append((idx, False, jid))
......
......@@ -2255,8 +2255,11 @@ class LUSetClusterParams(LogicalUnit):
"""Check parameters
"""
if not hasattr(self.op, "candidate_pool_size"):
self.op.candidate_pool_size = None
for attr in ["candidate_pool_size",
"uid_pool", "add_uids", "remove_uids"]:
if not hasattr(self.op, attr):
setattr(self.op, attr, None)
if self.op.candidate_pool_size is not None:
try:
self.op.candidate_pool_size = int(self.op.candidate_pool_size)
......
#
#
# Copyright (C) 2010 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Module containing backported language/library functionality.
"""
import itertools
try:
import functools
except ImportError:
functools = None
def all(seq, pred=bool): # pylint: disable-msg=W0622
"""Returns True if pred(x) is True for every element in the iterable.
Please note that this function provides a C{pred} parameter which isn't
available in the version included in Python 2.5 and above.
"""
for _ in itertools.ifilterfalse(pred, seq):
return False
return True
def any(seq, pred=bool): # pylint: disable-msg=W0622
"""Returns True if pred(x) is True for at least one element in the iterable.
Please note that this function provides a C{pred} parameter which isn't
available in the version included in Python 2.5 and above.
"""
for _ in itertools.ifilter(pred, seq):
return True
return False
def partition(seq, pred=bool): # pylint: disable-msg=W0622
"""Partition a list in two, based on the given predicate.
"""
return (list(itertools.ifilter(pred, seq)),
list(itertools.ifilterfalse(pred, seq)))
# Even though we're using Python's built-in "partial" function if available,
# this one is always defined for testing.
def _partial(func, *args, **keywords): # pylint: disable-msg=W0622
"""Decorator with partial application of arguments and keywords.
This function was copied from Python's documentation.
"""
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords) # pylint: disable-msg=W0142
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
if functools is None:
partial = _partial
else:
partial = functools.partial
......@@ -61,6 +61,7 @@ from ganeti import daemon # contains AsyncUDPSocket
from ganeti import errors
from ganeti import confd
from ganeti import ssconf
from ganeti import compat
class ConfdAsyncUDPClient(daemon.AsyncUDPSocket):
......@@ -581,7 +582,7 @@ class ConfdCountingCallback:
"""Have all the registered queries received at least an answer?
"""
return utils.all(self._answers.values())
return compat.all(self._answers.values())
def _HandleExpire(self, up):
# if we have no answer we have received none, before the expiration.
......
......@@ -101,10 +101,14 @@ class BaseHypervisor(object):
- a function to check for parameter validity on the remote node,
in the L{ValidateParameters} function
- an error message for the above function
@type CAN_MIGRATE: boolean
@cvar CAN_MIGRATE: whether this hypervisor can do migration (either
live or non-live)
"""
PARAMETERS = {}
ANCILLARY_FILES = []
CAN_MIGRATE = False
def __init__(self):
pass
......
......@@ -212,6 +212,13 @@ class ChrootManager(hv_base.BaseHypervisor):
raise HypervisorError("Can't stop the processes using the chroot")
return
def CleanupInstance(self, instance_name):
"""Cleanup after a stopped instance
"""
if self._IsDirLive(root_dir):
raise HypervisorError("Processes are still using the chroot")
for mpath in self._GetMountSubdirs(root_dir):
utils.RunCmd(["umount", mpath])
......
......@@ -40,6 +40,8 @@ class FakeHypervisor(hv_base.BaseHypervisor):
a real virtualisation software installed.
"""
CAN_MIGRATE = True
_ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
def __init__(self):
......
......@@ -44,6 +44,7 @@ from ganeti.hypervisor import hv_base
class KVMHypervisor(hv_base.BaseHypervisor):
"""KVM hypervisor interface"""
CAN_MIGRATE = True
_ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
_PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
......@@ -235,13 +236,12 @@ class KVMHypervisor(hv_base.BaseHypervisor):
if os.path.exists(uid_file):
try:
uid = int(utils.ReadFile(uid_file))
return uid
except EnvironmentError:
logging.warning("Can't read uid file", exc_info=True)
return None
except (TypeError, ValueError):
logging.warning("Can't parse uid file contents", exc_info=True)
return None
return uid
return None
@classmethod
def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
......@@ -425,7 +425,10 @@ class KVMHypervisor(hv_base.BaseHypervisor):
# TODO: handle FD_LOOP and FD_BLKTAP (?)
if boot_disk:
kvm_cmd.extend(['-boot', 'c'])
boot_val = ',boot=on'
if disk_type != constants.HT_DISK_IDE:
boot_val = ',boot=on'
else:
boot_val = ''
# We only boot from the first disk
boot_disk = False
else:
......@@ -440,9 +443,14 @@ class KVMHypervisor(hv_base.BaseHypervisor):
options = ',format=raw,media=cdrom'
if boot_cdrom:
kvm_cmd.extend(['-boot', 'd'])
options = '%s,boot=on' % options
if disk_type != constants.HT_DISK_IDE:
options = '%s,boot=on' % options
else:
options = '%s,if=virtio' % options
if disk_type == constants.HT_DISK_PARAVIRTUAL:
if_val = ',if=virtio'
else:
if_val = ',if=%s' % disk_type
options = '%s%s' % (options, if_val)
drive_val = 'file=%s%s' % (iso_image, options)
kvm_cmd.extend(['-drive', drive_val])
......@@ -498,6 +506,10 @@ class KVMHypervisor(hv_base.BaseHypervisor):
vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
kvm_cmd.extend(['-vnc', vnc_arg])
# Also add a tablet USB device to act as a mouse
# This solves various mouse alignment issues
kvm_cmd.extend(['-usbdevice', 'tablet'])
else:
kvm_cmd.extend(['-nographic'])
......
......@@ -39,6 +39,7 @@ class XenHypervisor(hv_base.BaseHypervisor):
all the functionality that is identical for both.
"""
CAN_MIGRATE = True
REBOOT_RETRY_COUNT = 60
REBOOT_RETRY_INTERVAL = 10
......
......@@ -33,6 +33,7 @@ import errno
from ganeti import errors
from ganeti import utils
from ganeti import compat
def ssynchronized(lock, shared=0):
......@@ -1216,7 +1217,7 @@ class GanetiLockManager:
"""
# This way of checking only works if LEVELS[i] = i, which we check for in
# the test cases.
return utils.any((self._is_owned(l) for l in LEVELS[level + 1:]))
return compat.any((self._is_owned(l) for l in LEVELS[level + 1:]))
def _BGL_owned(self): # pylint: disable-msg=C0103
"""Check if the current thread owns the BGL.
......
......@@ -137,7 +137,7 @@ class GanetiRapiClient(object):
@param query: query arguments to pass to urllib.urlencode
@type prepend_version: bool
@param prepend_version: whether to automatically fetch and prepend the
Ganeti version to the URL path
Ganeti RAPI version to the URL path
@rtype: str
@return: URL path
......@@ -171,7 +171,7 @@ class GanetiRapiClient(object):
@param content: HTTP body content
@type prepend_version: bool
@param prepend_version: whether to automatically fetch and prepend the
Ganeti version to the URL path
Ganeti RAPI version to the URL path
@rtype: str
@return: JSON-Decoded response
......@@ -213,7 +213,7 @@ class GanetiRapiClient(object):
return self._version
def GetVersion(self):
"""Gets the ganeti version running on the cluster.
"""Gets the Remote API version running on the cluster.
@rtype: int
@return: Ganeti version
......@@ -266,7 +266,7 @@ class GanetiRapiClient(object):
if dry_run:
query.append(("dry-run", 1))
self._SendRequest(HTTP_PUT, "/tags", query)
return self._SendRequest(HTTP_PUT, "/tags", query)
def DeleteClusterTags(self, tags, dry_run=False):
"""Deletes tags from the cluster.
......@@ -339,12 +339,15 @@ class GanetiRapiClient(object):
@type instance: str
@param instance: the instance to delete
@rtype: int
@return: job id
"""
query = []
if dry_run:
query.append(("dry-run", 1))
self._SendRequest(HTTP_DELETE, "/instances/%s" % instance, query)
return self._SendRequest(HTTP_DELETE, "/instances/%s" % instance, query)
def GetInstanceTags(self, instance):
"""Gets tags for an instance.
......@@ -376,7 +379,7 @@ class GanetiRapiClient(object):
if dry_run:
query.append(("dry-run", 1))
self._SendRequest(HTTP_PUT, "/instances/%s/tags" % instance, query)
return self._SendRequest(HTTP_PUT, "/instances/%s/tags" % instance, query)
def DeleteInstanceTags(self, instance, tags, dry_run=False):
"""Deletes tags from an instance.
......
......@@ -95,6 +95,7 @@ class SshRunner:
"-oHashKnownHosts=no",
"-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE,
"-oUserKnownHostsFile=/dev/null",
"-oCheckHostIp=no",
]
if use_cluster_key:
......
......@@ -1781,20 +1781,6 @@ def FirstFree(seq, base=0):
return None
def all(seq, pred=bool): # pylint: disable-msg=W0622
"Returns True if pred(x) is True for every element in the iterable"
for _ in itertools.ifilterfalse(pred, seq):
return False
return True
def any(seq, pred=bool): # pylint: disable-msg=W0622
"Returns True if pred(x) is True for at least one element in the iterable"
for _ in itertools.ifilter(pred, seq):
return True
return False
def SingleWaitForFdCondition(fdobj, event, timeout):
"""Waits for a condition to occur on the socket.
......@@ -1886,12 +1872,6 @@ def WaitForFdCondition(fdobj, event, timeout):
return result
def partition(seq, pred=bool): # # pylint: disable-msg=W0622
"Partition a list in two, based on the given predicate"
return (list(itertools.ifilter(pred, seq)),
list(itertools.ifilterfalse(pred, seq)))
def UniqueSequence(seq):
"""Returns a list with unique elements.
......
......@@ -40,6 +40,7 @@ from ganeti import bootstrap
from ganeti import ssh
from ganeti import objects
from ganeti import uidpool
from ganeti import compat
@UsesRPC
......@@ -423,7 +424,7 @@ def VerifyDisks(opts, args):
if missing:
for iname, ival in missing.iteritems():
all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
all_missing = compat.all(ival, lambda x: x[0] in bad_nodes)
if all_missing:
ToStdout("Instance %s cannot be verified as it lives on"
" broken nodes", iname)
......
......@@ -1175,7 +1175,7 @@ def ShowInstanceConfig(opts, args):
vnc_bind_address)
buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
for key in instance["hv_actual"]:
for key in sorted(instance["hv_actual"]):
if key in instance["hv_instance"]:
val = instance["hv_instance"][key]
else:
......
#!/usr/bin/python
#
# Copyright (C) 2010 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Script for unittesting the compat module"""
import unittest
from ganeti import compat
import testutils
class TestPartial(testutils.GanetiTestCase):
def test(self):
# Test standard version
self._Test(compat.partial)
# Test our version
self._Test(compat._partial)
def _Test(self, fn):
def _TestFunc1(x, power=2):
return x ** power
cubic = fn(_TestFunc1, power=3)
self.assertEqual(cubic(1), 1)
self.assertEqual(cubic(3), 27)
self.assertEqual(cubic(4), 64)
def _TestFunc2(*args, **kwargs):
return (args, kwargs)
self.assertEqualValues(fn(_TestFunc2, "Hello", "World")("Foo"),
(("Hello", "World", "Foo"), {}))
self.assertEqualValues(fn(_TestFunc2, "Hello", xyz=123)("Foo"),
(("Hello", "Foo"), {"xyz": 123}))
self.assertEqualValues(fn(_TestFunc2, xyz=123)("Foo", xyz=999),
(("Foo", ), {"xyz": 999,}))
if __name__ == "__main__":
testutils.GanetiTestProgram()
......@@ -192,7 +192,9 @@ class GanetiRapiClientTests(unittest.TestCase):
self.assertHandler(rlib2.R_2_tags)
def testAddClusterTags(self):
self.client.AddClusterTags(["awesome"], dry_run=True)
self.rapi.AddResponse("1234")
self.assertEqual(1234,
self.client.AddClusterTags(["awesome"], dry_run=True))
self.assertHandler(rlib2.R_2_tags)
self.assertDryRun()
self.assertQuery("tag", ["awesome"])
......@@ -227,7 +229,8 @@ class GanetiRapiClientTests(unittest.TestCase):
self.assertDryRun()
def testDeleteInstance(self):
self.client.DeleteInstance("instance", dry_run=True)
self.rapi.AddResponse("1234")
self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
self.assertHandler(rlib2.R_2_instances_name)
self.assertItems(["instance"])
self.assertDryRun()
......@@ -239,7 +242,9 @@ class GanetiRapiClientTests(unittest.TestCase):
self.assertItems(["fooinstance"])
def testAddInstanceTags(self):
self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True)
self.rapi.AddResponse("1234")
self.assertEqual(1234,
self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
self.assertHandler(rlib2.R_2_instances_name_tags)
self.assertItems(["fooinstance"])
self.assertDryRun()
......@@ -425,7 +430,9 @@ class GanetiRapiClientTests(unittest.TestCase):
self.assertItems(["node-k"])
def testAddNodeTags(self):
self.client.AddNodeTags("node-v", ["awesome"], dry_run=True)
self.rapi.AddResponse("1234")
self.assertEqual(1234,
self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
self.assertHandler(rlib2.R_2_nodes_name_tags)
self.assertItems(["node-v"])
self.assertDryRun()
......
......@@ -36,6 +36,8 @@ from ganeti import constants
from ganeti import cli
from ganeti import errors
from ganeti import utils
from ganeti import hypervisor
from ganeti import compat
from ganeti.confd import client as confd_client
......@@ -111,6 +113,7 @@ OPTIONS = [
help="OS to use during burnin",
metavar="<OS>",
completion_suggest=cli.OPT_COMPL_ONE_OS),
cli.HYPERVISOR_OPT,
cli.cli_option("--disk-size", dest="disk_size",
help="Disk size (determines disk count)",
default="128m", type="string", metavar="<size,size,...>",
......@@ -473,7 +476,11 @@ class Burner(object):
constants.BE_MEMORY: options.mem_size,
constants.BE_VCPUS: 1,
}
self.hypervisor = None
self.hvp = {}
if options.hypervisor:
self.hypervisor, self.hvp = options.hypervisor
socket.setdefaulttimeout(options.net_timeout)
......@@ -515,6 +522,9 @@ class Burner(object):
default_nic_params = self.cluster_info["nicparams"][constants.PP_DEFAULT]
self.cluster_default_nicparams = default_nic_params
if self.hypervisor is None:
self.hypervisor = self.cluster_info["default_hypervisor"]
self.hv_class = hypervisor.GetHypervisorClass(self.hypervisor)
@_DoCheckInstances
@_DoBatch(False)
......@@ -559,6 +569,7 @@ class Burner(object):
iallocator=self.opts.iallocator,
beparams=self.bep,
hvparams=self.hvp,
hypervisor=self.hypervisor,
)
remove_instance = lambda name: lambda: self.to_rem.append(name)
self.ExecOrQueue(instance, [op], post_process=remove_instance(instance))
......@@ -972,14 +983,20 @@ class Burner(object):
self.BurnReplaceDisks2()
if (opts.disk_template in constants.DTS_GROWABLE and
utils.any(self.disk_growth, lambda n: n > 0)):
compat.any(self.disk_growth, lambda n: n > 0)):
self.BurnGrowDisks()
if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
self.BurnFailover()
if opts.do_migrate and opts.disk_template == constants.DT_DRBD8:
self.BurnMigrate()
if opts.do_migrate:
if opts.disk_template != constants.DT_DRBD8:
Log("Skipping migration (disk template not DRBD8)")
elif not self.hv_class.CAN_MIGRATE:
Log("Skipping migration (hypervisor %s does not support it)",
self.hypervisor)
else:
self.BurnMigrate()
if (opts.do_move and len(self.nodes) > 1 and
opts.disk_template in [constants.DT_PLAIN, constants.DT_FILE]):
......
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