Commit ddd667f7 authored by Thomas Thrainer's avatar Thomas Thrainer

Merge branch 'stable-2.9' into stable-2.10

* stable-2.9
  Make the LUInstanceCreate return node names, not UUIDs
  Document new handling of degraded instances in NEWS
  Gracefully handle degraded instances in verification
  Be aware of the degraded case when cleaning up an instance
  Document changes to file-based disks in NEWS
  Preserve disk basename on instance rename
  Update NEWS file
  Modify test to reflect RAPI operation changes
  Add QA tests for RAPI multi-instance allocation
  Fix multi-allocation RAPI method
  Assign unique filenames to filebased disks

* stable-2.8
  Fix execution group of NodeD

Conflicts:
	NEWS (took both changes)
	lib/config.py (trivial)
	test/py/ganeti.cmdlib_unittest.py
          (manually reapplied changes to
           test/py/cmdlib/instance_unittest.py)
Signed-off-by: default avatarThomas Thrainer <thomasth@google.com>
Reviewed-by: default avatarHrvoje Ribicic <riba@google.com>
parents 7231ca26 12321d90
......@@ -1898,6 +1898,7 @@ $(REPLACE_VARS_SED): $(SHELL_ENV_INIT) Makefile stamp-directories
echo 's#@''GNTRAPIGROUP@#$(RAPI_GROUP)#g'; \
echo 's#@''GNTADMINGROUP@#$(ADMIN_GROUP)#g'; \
echo 's#@''GNTCONFDGROUP@#$(CONFD_GROUP)#g'; \
echo 's#@''GNTNODEDGROUP@#$(NODED_GROUP)#g'; \
echo 's#@''GNTLUXIDGROUP@#$(LUXID_GROUP)#g'; \
echo 's#@''GNTMASTERDGROUP@#$(MASTERD_GROUP)#g'; \
echo 's#@''GNTMONDGROUP@#$(MOND_GROUP)#g'; \
......
......@@ -166,6 +166,16 @@ before rc1.
- Issue 623: IPv6 Masterd <-> Luxid communication error
Version 2.9.4
-------------
*(unreleased)*
- Fix the RAPI instances-multi-alloc call
- assign unique filenames to file-based disks
- gracefully handle degraded non-diskless instances with 0 disks (issue 697)
Version 2.9.3
-------------
......
......@@ -91,7 +91,7 @@ _daemon_usergroup() {
echo "@GNTRAPIUSER@:@GNTRAPIGROUP@"
;;
noded)
echo "@GNTNODEDUSER@:@GNTDAEMONSGROUP@"
echo "@GNTNODEDUSER@:@GNTNODEDGROUP@"
;;
mond)
echo "@GNTMONDUSER@:@GNTMONDGROUP@"
......
......@@ -2724,6 +2724,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
node_disks = {}
node_disks_dev_inst_only = {}
diskless_instances = set()
nodisk_instances = set()
diskless = constants.DT_DISKLESS
for nuuid in node_uuids:
......@@ -2736,6 +2737,8 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
for disk in instanceinfo[inst_uuid].disks]
if not disks:
nodisk_instances.update(uuid for uuid in node_inst_uuids
if instanceinfo[uuid].disk_template != diskless)
# No need to collect data
continue
......@@ -2792,6 +2795,10 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
for inst_uuid in diskless_instances:
assert inst_uuid not in instdisk
instdisk[inst_uuid] = {}
# ...and disk-full instances that happen to have no disks
for inst_uuid in nodisk_instances:
assert inst_uuid not in instdisk
instdisk[inst_uuid] = {}
assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
len(nuuids) <= len(instanceinfo[inst].all_nodes) and
......
#
#
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 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
......@@ -1504,7 +1504,7 @@ class LUInstanceCreate(LogicalUnit):
False, self.op.reason)
result.Raise("Could not start instance")
return list(iobj.all_nodes)
return self.cfg.GetNodeNames(list(iobj.all_nodes))
class LUInstanceRename(LogicalUnit):
......
......@@ -52,6 +52,8 @@ _DISK_TEMPLATE_NAME_PREFIX = {
constants.DT_PLAIN: "",
constants.DT_RBD: ".rbd",
constants.DT_EXT: ".ext",
constants.DT_FILE: ".file",
constants.DT_SHARED_FILE: ".sharedfile",
}
......@@ -462,8 +464,8 @@ def GenerateDiskTemplate(
elif template_name in (constants.DT_FILE, constants.DT_SHARED_FILE):
logical_id_fn = \
lambda _, disk_index, disk: (file_driver,
"%s/disk%d" % (file_storage_dir,
disk_index))
"%s/%s" % (file_storage_dir,
names[idx]))
elif template_name == constants.DT_BLOCK:
logical_id_fn = \
lambda idx, disk_index, disk: (constants.BLOCKDEV_DRIVER_MANUAL,
......
......@@ -293,7 +293,15 @@ def RemoveDisks(lu, instance, target_node_uuid=None, ignore_failures=False):
CheckDiskTemplateEnabled(lu.cfg.GetClusterInfo(), instance.disk_template)
if instance.disk_template in constants.DTS_FILEBASED:
file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
if len(instance.disks) > 0:
file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
else:
if instance.disk_template == constants.DT_SHARED_FILE:
file_storage_dir = utils.PathJoin(lu.cfg.GetSharedFileStorageDir(),
instance.name)
else:
file_storage_dir = utils.PathJoin(lu.cfg.GetFileStorageDir(),
instance.name)
if target_node_uuid:
tgt = target_node_uuid
else:
......
......@@ -1533,13 +1533,13 @@ class ConfigWriter(object):
inst = self._config_data.instances[inst_uuid]
inst.name = new_name
for (idx, disk) in enumerate(inst.disks):
for (_, disk) in enumerate(inst.disks):
if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
# rename the file paths in logical and physical id
file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
disk.logical_id = (disk.logical_id[0],
utils.PathJoin(file_storage_dir, inst.name,
"disk%s" % idx))
os.path.basename(disk.logical_id[1])))
# Force update of ssconf files
self._config_data.cluster.serial_no += 1
......
......@@ -980,12 +980,19 @@ class R_2_instances_multi_alloc(baserlib.OpcodeResource):
raise http.HttpBadRequest("Request is missing required 'instances' field"
" in body")
op_id = {
"OP_ID": self.POST_OPCODE.OP_ID, # pylint: disable=E1101
}
# Unlike most other RAPI calls, this one is composed of individual opcodes,
# and we have to do the filling ourselves
OPCODE_RENAME = {
"os": "os_type",
"name": "instance_name",
}
body = objects.FillDict(self.request_body, {
"instances": [objects.FillDict(inst, op_id)
for inst in self.request_body["instances"]],
"instances": [
baserlib.FillOpcode(opcodes.OpInstanceCreate, inst, {},
rename=OPCODE_RENAME)
for inst in self.request_body["instances"]
],
})
return (body, {
......
......@@ -809,6 +809,7 @@ def RunQa():
if (qa_config.TestEnabled("instance-add-plain-disk")
and qa_config.IsTemplateSupported(constants.DT_PLAIN)):
# Normal instance allocation via RAPI
for use_client in [True, False]:
rapi_instance = RunTest(qa_rapi.TestRapiInstanceAdd, pnode,
use_client)
......@@ -820,6 +821,16 @@ def RunQa():
rapi_instance.Release()
del rapi_instance
# Multi-instance allocation
rapi_instance_one, rapi_instance_two = \
RunTest(qa_rapi.TestRapiInstanceMultiAlloc, pnode)
try:
RunTest(qa_rapi.TestRapiInstanceRemove, rapi_instance_one, True)
RunTest(qa_rapi.TestRapiInstanceRemove, rapi_instance_two, True)
finally:
rapi_instance_one.Release()
rapi_instance_two.Release()
finally:
pnode.Release()
......
......@@ -27,6 +27,7 @@ import tempfile
import random
import re
import itertools
import functools
from ganeti import utils
from ganeti import constants
......@@ -622,6 +623,76 @@ def TestRapiInstanceAdd(node, use_client):
raise
def _GenInstanceAllocationDict(node, instance):
"""Creates an instance allocation dict to be used with the RAPI"""
instance.SetDiskTemplate(constants.DT_PLAIN)
disks = [{"size": utils.ParseUnit(d.get("size")),
"name": str(d.get("name"))}
for d in qa_config.GetDiskOptions()]
nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
nics = [{
constants.INIC_MAC: nic0_mac,
}]
beparams = {
constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
}
return _rapi_client.InstanceAllocation(constants.INSTANCE_CREATE,
instance.name,
constants.DT_PLAIN,
disks, nics,
os=qa_config.get("os"),
pnode=node.primary,
beparams=beparams)
def TestRapiInstanceMultiAlloc(node):
"""Test adding two new instances via the RAPI instance-multi-alloc method"""
if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
return
JOBS_KEY = "jobs"
instance_one = qa_config.AcquireInstance()
instance_two = qa_config.AcquireInstance()
instance_list = [instance_one, instance_two]
try:
rapi_dicts = map(functools.partial(_GenInstanceAllocationDict, node),
instance_list)
job_id = _rapi_client.InstancesMultiAlloc(rapi_dicts)
results, = _WaitForRapiJob(job_id)
if JOBS_KEY not in results:
raise qa_error.Error("RAPI instance-multi-alloc did not deliver "
"information about created jobs")
if len(results[JOBS_KEY]) != len(instance_list):
raise qa_error.Error("RAPI instance-multi-alloc failed to return the "
"desired number of jobs!")
for success, job in results[JOBS_KEY]:
if success:
_WaitForRapiJob(job)
else:
raise qa_error.Error("Failed to create instance in "
"instance-multi-alloc call")
except:
# Note that although released, it may be that some of the instance creations
# have in fact succeeded. Handling this in a better way may be possible, but
# is not necessary as the QA has already failed at this point.
for instance in instance_list:
instance.Release()
raise
return (instance_one, instance_two)
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
def TestRapiInstanceRemove(instance, use_client):
"""Test removing instance via RAPI"""
......
......@@ -25,6 +25,7 @@
import copy
import itertools
import re
import unittest
import mock
import operator
......@@ -1106,11 +1107,12 @@ class TestGenerateDiskTemplate(CmdlibTestCase):
disk_template, disk_info, 2, disk_template,
file_storage_dir="/tmp", file_driver=constants.FD_BLKTAP)
self.assertEqual(map(operator.attrgetter("logical_id"), result), [
(constants.FD_BLKTAP, "/tmp/disk2"),
(constants.FD_BLKTAP, "/tmp/disk3"),
(constants.FD_BLKTAP, "/tmp/disk4"),
])
for (idx, disk) in enumerate(result):
(file_driver, file_storage_dir) = disk.logical_id
dir_fmt = r"^/tmp/.*\.%s\.disk%d$" % (disk_template, idx + 2)
self.assertEqual(file_driver, constants.FD_BLKTAP)
# FIXME: use assertIsNotNone when py 2.7 is minimum supported version
self.assertNotEqual(re.match(dir_fmt, file_storage_dir), None)
def testBlock(self):
disk_info = [{
......
......@@ -1763,18 +1763,27 @@ class TestInstancesMultiAlloc(unittest.TestCase):
clfactory = _FakeClientFactory(_FakeClient)
data = {
"instances": [{
"instance_name": "bar",
"name": "bar",
"mode": "create",
"disks": [{"size": 1024}],
"disk_template": "plain",
"nics": [{}],
}, {
"instance_name": "foo",
"name": "foo",
"mode": "create",
"disks": [{"size": 1024}],
"disk_template": "drbd",
"nics": [{}],
}],
}
handler = _CreateHandler(rlib2.R_2_instances_multi_alloc, [], {}, data,
clfactory)
(body, _) = handler.GetPostOpInput()
self.assertTrue(compat.all([inst["OP_ID"] == handler.POST_OPCODE.OP_ID
for inst in body["instances"]]))
self.assertTrue(compat.all(
[isinstance(inst, opcodes.OpInstanceCreate) for inst in body["instances"]]
))
class TestPermissions(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