From nobody Fri May 3 21:27:33 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1574359184; cv=none; d=zoho.com; s=zohoarc; b=UwZqpJWcYOorFDDIkEv1B8oZr6AAwJCcVyprpNIERrCz9mo6olLVIiDNrNV5jkgfD3c8xivj58TUsHeHN/Ssanb5LfpKok/i6RSWqi1P5fIBMb0BJGOJgz7HlMItA/AtuHlPVEq2G0P+P7YaeiIXSgLvhOuo/7dmDcTGNnRizCA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1574359184; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=uaC9LGw/wHEIBLw0vkf5ikCrI2wUQmaVOtovmjW9txQ=; b=R+EtMeOI1uLuu7f7lDGJsNS6rlr0kMsOjglTg7L4jwXdQ4unzg+tZ+WykSETpZchMUKRyeA6oVBtz1JSPayDkjVNIIn4Q3kxvBS7xkGcu7sjrhl2YSWKiSvN1mtXstEgF8yWHL41/OTuGLvGJxbOWFGusZjWb6CRYGZObsmo7bc= ARC-Authentication-Results: i=1; mx.zoho.com; dkim=fail; spf=pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1574359184180555.5968037826304; Thu, 21 Nov 2019 09:59:44 -0800 (PST) Received: from localhost ([::1]:43318 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iXqk0-000544-Jm for importer@patchew.org; Thu, 21 Nov 2019 12:59:40 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:42917) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iXqae-0000Es-VU for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:01 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iXqae-00073e-46 for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:00 -0500 Received: from mout.web.de ([212.227.15.3]:38935) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1iXqad-000728-R9 for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:00 -0500 Received: from luklap ([89.247.255.160]) by smtp.web.de (mrweb004 [213.165.67.108]) with ESMTPSA (Nemesis) id 0MP2Sl-1iS5IE1mMB-006QdI; Thu, 21 Nov 2019 18:49:29 +0100 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=web.de; s=dbaedf251592; t=1574358569; bh=/tWQRuIu4SR7FaDWRPHY2EoqGlYDYuB7/4qetBCJls0=; h=X-UI-Sender-Class:Date:From:To:Cc:Subject:In-Reply-To:References; b=nTWHOfm+2sSk+XdI2d0ISmtpBkEhGngKui5Ir3sWpJX39x8SKt4dEBPMidcwavEEI 8SMVzXbukXUwkIRW2O/A6Bh9kMtPT+qF7jLQaP3amO+/Q2Fh2Mj3kxmecWoQvqcyHv vl1kab2vYuLBhnROlG7sP+aYy/MmnxrSimPIqs2I= X-UI-Sender-Class: c548c8c5-30a9-4db5-a2e7-cb6cb037b8f9 Date: Thu, 21 Nov 2019 18:49:24 +0100 From: Lukas Straub To: qemu-devel Subject: [PATCH 1/4] block/quorum.c: stable children names Message-ID: In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Provags-ID: V03:K1:+QrQyax3xz6u5ZUK7gqFU8+jrbwvqLzy/3g+LfT4UXkF+aAouY9 4XaTokmPEaiwucyueROhWtkLxWC6QGp69R2AoC83JlThb0n+f0exXZEOd5gylghQPkzrnYx megDTVBEEatB8WGd9GE8Qktmmgly5ec9XWgHfmY+XEBMATXT6uN2mXrt9P2r21Msq53K1mk /1PpvnuqOsCRfn1zY6XOA== X-UI-Out-Filterresults: notjunk:1;V03:K0:DzyybryLu1U=:wOwLHO64xJ/H5IOJSZbUIp xdU8C/3I/UckyQl21rGB6ojsLUBN33vYtR4FdnQZacLq860RM47b+LBiC72jtQw6uTYho5jNU +V3TibSUOH5HugF9yOw9zBFkNhLHbhTTSClJrORvKZz2cFVk302frynx8AfOxqAhqSyrEHUJO qUr4dnPFni2APZ2S6CG+42XKSRU48yrCl60c5LxJ2uV0bBBKn6zi0aV5PRdW/RT3Tq1FL5TBj UR9t5xzSNnxWJqN6UKlYqG8akDoLj41AnH7xcfSLmwc8pPfU1ZfiYCGzG/bOqMn7/TIp9jExx bSCTQLyA9DAc6q5omlXazxSUCz0mQK8n2GHzQZDkIo0tR7KipeNA6JwJ46Aa+o8UQaPu4S7Rx h4ygqjU/X1owkS91TXPV8xUebcCKgOO4KYe2Zkcvz0Cp3Yo8ikHDXLtLV46nGpx86GZyb66wD 7g4IV9mox2hayx/WB/M4n0M5bUwSsyAvVeZV9XjEW86vd+n1cWjaJVYD/1/9iq4tN5ilQAY1U WvhsBg1I2pE1c5pAhEiCZyoMTYu1OezmgjZo1y2KaGqpEyt+OXgTcP390WzaZsre+DB7aXj9n GiV6eVttHt2+UxeRnzA/yphNp13hqSK2cE+K94bf1+3j2gFyStzTaslr1oxBDNt2pxj//0KxU g+cKvXldkmE6Fng7DkJnanBZiLPZ9yf3VotEEokFc3ZcDqw7jeoDIP8lhFm9OFJ6YxJORmxRU /817fuPZSar6cxThBQi8K7uNuoolzRoFu6syp9fZp7pTdx2GNhGJN51qiL0nm3NFvO/YlXMiZ AJOnl3o+zjkEWt4b57iDzPWWF+ppUtahBFMB4jMUc8MQll/keHP+5DPbGWzKkL45wbEjjPe1n EiaZC05tUH2yxegGtjPzPtBoPdqaTR2EE1VgYASbDoIn+rbhLpegUwQ94PsmShkdi4ws1fRrc pLh7pkJYdlaYctNL5V8ukhyGEIqN2pH8YB6hTzUrYVx0cyIp+lW91IipZlIkQ3P6nu37GISnC xDN65A7s6L8/ceKLOZi8bUjHvAfSxsanOWIaUQkn67OxLaEXywMuWEizwIsOQeabSUGUqedHd qvaZGyffJPze3+mQKm8KL9/tdxxhv8khf867eaL1ju3hfunRxc+Dsg4zd7kCf2IV4NFXApAlW 2L7gbcMxxdhEs46gX8/xJWuL9PnqCqm6ReKVmAdmO7EgUKqOy6rcM23i2mGKZTyP9Dt/WddXN D/NkhvOH/daL2NnALQnczNpV0wRXIzqxVCt14u+o9L990LHOZia5fJ+WKUPs= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 212.227.15.3 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: "Zhang, Chen" , Jason Wang , Alberto Garcia , "Dr. David Alan Gilbert" Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) Content-Type: text/plain; charset="utf-8" If we remove the child with the highest index from the quorum, decrement s->next_child_index. This way we get stable children names as long as we only remove the last child. Signed-off-by: Lukas Straub --- block/quorum.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/quorum.c b/block/quorum.c index df68adcfaa..6100d4108a 100644 --- a/block/quorum.c +++ b/block/quorum.c @@ -1054,6 +1054,12 @@ static void quorum_del_child(BlockDriverState *bs, B= drvChild *child, /* We know now that num_children > threshold, so blkverify must be fal= se */ assert(!s->is_blkverify); + unsigned child_id; + sscanf(child->name, "children.%u", &child_id); + if (child_id =3D=3D s->next_child_index - 1) { + s->next_child_index--; + } + bdrv_drained_begin(bs); /* We can safely remove this child now */ -- 2.20.1 From nobody Fri May 3 21:27:33 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1574358875; cv=none; d=zoho.com; s=zohoarc; b=nHkG9ggZLb8VuO+tZG0rLofGRICv/najtVJMesQOqW27RZR4rCphJ4FiUAKuKDfRRQ2/mEYA/r4+DWPhlYPkC8Xf5lZzBZ0+zdqFL20ulkL7rixN8u7PNTXb+OuJD7FHQev6Pv7znZoOm47adD4pT/sz6SHN4T/AOMEavhzIqdg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1574358875; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=mqwItGT2/xevjcyR52mJMNbv8UdqiRqhgNs1vnJdTN0=; b=lug9RqBFla10xWL7ZovDapHoLjkc+i9M01RH+s2dZ2Z2gZw57agf4TAbA/bYTuLdoVstFYYVIn46poCCYnsett1BRkay24RRBRTDHCn9EXq/UrIX2gwnw76PBw/b84P+m+RFq0P7PZoxs3VUrb/IHSikMVaPJK8YhIrb2zfo5F0= ARC-Authentication-Results: i=1; mx.zoho.com; dkim=fail; spf=pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1574358875162381.88584490269193; Thu, 21 Nov 2019 09:54:35 -0800 (PST) Received: from localhost ([::1]:43286 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iXqf1-00028i-QZ for importer@patchew.org; Thu, 21 Nov 2019 12:54:32 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:42989) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iXqaq-0000He-3W for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:16 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iXqal-0007Bm-Vk for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:12 -0500 Received: from mout.web.de ([217.72.192.78]:36019) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1iXqal-00078P-Eq for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:07 -0500 Received: from luklap ([89.247.255.160]) by smtp.web.de (mrweb103 [213.165.67.124]) with ESMTPSA (Nemesis) id 0M6DyQ-1he6Mu0tb8-00yCpe; Thu, 21 Nov 2019 18:49:34 +0100 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=web.de; s=dbaedf251592; t=1574358574; bh=mUHwoNP69oYZm/UPIrC3/73YAFFXYW+U5IJQHfNVy7c=; h=X-UI-Sender-Class:Date:From:To:Cc:Subject:In-Reply-To:References; b=SjtpMD1HazrfXz81cnNyQXbZePGqDYK/2pMaEeT6+TZWgXKgyLQVgbbvC3LL0EDOw xsNvMCNJGZNiKn7QVFfsin8cJx2o031I/RkvMIOx+gk4EVvIxZ7odCd4eMRDS2i8xa vAtYqASk3UNu5UJTxXD81bI58uUmhfson3+7mT4M= X-UI-Sender-Class: c548c8c5-30a9-4db5-a2e7-cb6cb037b8f9 Date: Thu, 21 Nov 2019 18:49:33 +0100 From: Lukas Straub To: qemu-devel Subject: [PATCH 2/4] colo: Introduce resource agent Message-ID: <4b51cd192df72d880841794199a10d7a834688d4.1574356137.git.lukasstraub2@web.de> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Provags-ID: V03:K1:+Rw/WD3LMNj64X7tuHCO+rg8luc5pY+cClCe3ABrnKrgnzmU+HE ItiSDIA+oUyerx1CdAudnoAGdoc3v7MhiOddEHfZdJHaeECTio5SZr2oWio9qMLFmo6MDgC hdHBHSwR1GlbawrSu3vaCcqOLmj3sItg13+weWYSn7OSO6iWls/rRtRbyPnsjR49FdEAAlt YK+ZnuKRs0CktrUm+7cAg== X-UI-Out-Filterresults: notjunk:1;V03:K0:w+zOmB6bx4U=:XHcNMDC59/4IGWyErUZLM5 KynbZh8XqKIbHRCQUSQqFUF1ftmul06Dt5dA/a6Nho+gLL1sMduKPuclNWaivk9uO59xKaNEd 7hoKuuYUt9LYRYAjiQqUgg8mSk/6Qrj6Vb4fnbZxvHMpDojL8EmU9j6CwiKjbjoJ1cmJXX9yx OSFIozS3np5zJOU0SErIDRrl5+rFb+u54RQ1L39nlX/SI9aJVWChT+cEKX+CSuca6yck54V4d SqhOHqd24+AcTmAbSCDdUNMBp6OAeZWKw1kTvPP5mHMo3ZMxxoRDu62tuMZ8jPqzgW8DaCyxD ID68y+6+wP8sjLj/sXlBchTW864eeufG+3i8Q6BF61usuXpqKTD6LAmOCbNoZ2wBg2JSgVW66 8ZxzwCuSZ8HBE809Q+clKJCCodrNztsqhKkBTPtMMATvcklrCBGgxOj4ROYIf8JfYqVd8gvFb w4H+QjvvuaONE1f9aN+Q6I2h625V/4PAPqFHCkkoYxUalgMEAbQrUesL7A9s89KQdvHupBHbY JH4jSk03DfLeByrPyag2NdDMqDw1v0fO0LO0A6YAya8pZBL4GLV2/NYHGCVYABt4uzTOpbSxo G8FxEx6G8f7MnoMqAePhpW2IiCc8yuSNTo2BAPaAbnUnzx1QzdwijmyMfDVWtZwW+9SumnKx4 yWf53y3XhJeO5QQ+rY6wlf7y8Lckq32ruc/6ALAV8zlmk9S9D9ufIi1+Rfor2tA7dEYU+WKf4 W4AeY0G95yYs/nDy/RsTJTLJUUHRHA/Zp/oslFOZRjlVSkmxvXKYE8TfTFcvPSngQ1LAMw1vY PZuDvMbJeR0+0YifFiOUp9QluAgG/sUja1W4LVSE2AoKbmtT3HlX0FiYDifkwMuGIromE2h8v D/w9ehw5KbyGWo46wTJBKXRs6wjsgghqqJUG8i8DBNfOlPRQ0dbqvQ9XaYWtb0Bge5w6/DTbj JX3H78Jd7kMrra1VQ6p/sW4OygJcS4WhrW3ZBbjaHn98mbAKyoOoBXqcBpYsfeBiMJBR6662l Kf3bsPWOfqaXEGQlFLEICF0RyccD3c7anutjkXmayXZDzuZ4vHjpIeSwLb1VxgAQV+Bqm/IjE m35QkkUJL/bUjERrjETkG3RRk9ts43XhXv0AdKUp+5Fj8KN2LOsAPGKaG81hPIhL4l/5m5sB/ vAA7OaVAdp47HM9+wDrRaW9csYaAZQyKWNVQFyf8LAd/1Drg3CSig5Nf3X2vIiHt+AQ20qf9y m8iwR5+ubpHCN2kVB7Zd0KKuGrqqGEB8l4NeiYBLsmvJL5s62XVHlVUI/dVI= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 217.72.192.78 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: "Zhang, Chen" , Jason Wang , Alberto Garcia , "Dr. David Alan Gilbert" Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) Content-Type: text/plain; charset="utf-8" Introduce a resource agent which can be used in a Pacemaker cluster to manage qemu COLO. Signed-off-by: Lukas Straub --- scripts/colo-resource-agent/colo | 1026 ++++++++++++++++++++++++++++++ 1 file changed, 1026 insertions(+) create mode 100755 scripts/colo-resource-agent/colo diff --git a/scripts/colo-resource-agent/colo b/scripts/colo-resource-agent= /colo new file mode 100755 index 0000000000..5fd9cfc0b5 --- /dev/null +++ b/scripts/colo-resource-agent/colo @@ -0,0 +1,1026 @@ +#!/usr/bin/env python + +# Resource agent for qemu COLO for use with Pacemaker CRM +# +# Copyright (c) Lukas Straub +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. + +from __future__ import print_function +import subprocess +import sys +import os +import os.path +import signal +import socket +import json +import re +import time +import logging +import logging.handlers + +# Constants +OCF_SUCCESS =3D 0 +OCF_ERR_GENERIC =3D 1 +OCF_ERR_ARGS =3D 2 +OCF_ERR_UNIMPLEMENTED =3D 3 +OCF_ERR_PERM =3D 4 +OCF_ERR_INSTALLED =3D 5 +OCF_ERR_CONFIGURED =3D 6 +OCF_NOT_RUNNING =3D 7 +OCF_RUNNING_MASTER =3D 8 +OCF_FAILED_MASTER =3D 9 + +# Get environment variables +OCF_RESKEY_CRM_meta_notify_type \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify_type") +OCF_RESKEY_CRM_meta_notify_operation \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify_operation") +OCF_RESKEY_CRM_meta_notify_start_uname \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify_start_uname", "") +OCF_RESKEY_CRM_meta_notify_stop_uname \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify_stop_uname", "") +OCF_RESKEY_CRM_meta_notify_active_uname \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify_active_uname", "") +OCF_RESKEY_CRM_meta_notify_promote_uname \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify_promote_uname", "") +OCF_RESKEY_CRM_meta_notify_demote_uname \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify_demote_uname", "") +OCF_RESKEY_CRM_meta_notify_master_uname \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify_master_uname", "") +OCF_RESKEY_CRM_meta_notify_slave_uname \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify_slave_uname", "") + +HA_RSCTMP =3D os.getenv("HA_RSCTMP", "/run/resource-agents") +HA_LOGFACILITY =3D os.getenv("HA_LOGFACILITY") +HA_LOGFILE =3D os.getenv("HA_LOGFILE") +HA_DEBUGLOG =3D os.getenv("HA_DEBUGLOG") +OCF_RESOURCE_INSTANCE =3D os.getenv("OCF_RESOURCE_INSTANCE", "default-inst= ance") +OCF_RESKEY_CRM_meta_timeout \ + =3D int(os.getenv("OCF_RESKEY_CRM_meta_timeout", "60000")) +OCF_RESKEY_CRM_meta_interval \ + =3D int(os.getenv("OCF_RESKEY_CRM_meta_interval", "1")) +OCF_RESKEY_CRM_meta_clone_max \ + =3D int(os.getenv("OCF_RESKEY_CRM_meta_clone_max", "1")) +OCF_RESKEY_CRM_meta_clone_node_max \ + =3D int(os.getenv("OCF_RESKEY_CRM_meta_clone_node_max", "1")) +OCF_RESKEY_CRM_meta_master_max \ + =3D int(os.getenv("OCF_RESKEY_CRM_meta_master_max", "1")) +OCF_RESKEY_CRM_meta_master_node_max \ + =3D int(os.getenv("OCF_RESKEY_CRM_meta_master_node_max", "1")) +OCF_RESKEY_CRM_meta_notify \ + =3D os.getenv("OCF_RESKEY_CRM_meta_notify") +OCF_RESKEY_CRM_meta_globally_unique \ + =3D os.getenv("OCF_RESKEY_CRM_meta_globally_unique") + +HOSTNAME =3D os.getenv("OCF_RESKEY_CRM_meta_on_node", socket.gethostname()) + +OCF_ACTION =3D os.getenv("__OCF_ACTION") +if not OCF_ACTION and len(sys.argv) =3D=3D 2: + OCF_ACTION =3D sys.argv[1] + +# Resource parameters +OCF_RESKEY_binary_default =3D "qemu-system-x86_64" +OCF_RESKEY_log_dir_default =3D HA_RSCTMP +OCF_RESKEY_options_default =3D "" +OCF_RESKEY_disk_size_default =3D "" +OCF_RESKEY_active_hidden_dir_default =3D "" +OCF_RESKEY_listen_address_default =3D "0.0.0.0" +OCF_RESKEY_base_port_default =3D "9000" +OCF_RESKEY_checkpoint_interval_default =3D "20000" +OCF_RESKEY_debug_default =3D "false" + +OCF_RESKEY_binary =3D os.getenv("OCF_RESKEY_binary", OCF_RESKEY_binary_def= ault) +OCF_RESKEY_log_dir =3D os.getenv("OCF_RESKEY_log_dir", OCF_RESKEY_log_dir_= default) +OCF_RESKEY_options =3D os.getenv("OCF_RESKEY_options", OCF_RESKEY_options_= default) +OCF_RESKEY_disk_size \ + =3D os.getenv("OCF_RESKEY_disk_size", OCF_RESKEY_disk_size_default) +OCF_RESKEY_active_hidden_dir \ + =3D os.getenv("OCF_RESKEY_active_hidden_dir", \ + OCF_RESKEY_active_hidden_dir_default) +OCF_RESKEY_listen_address \ + =3D os.getenv("OCF_RESKEY_listen_address", OCF_RESKEY_listen_address_d= efault) +OCF_RESKEY_base_port \ + =3D os.getenv("OCF_RESKEY_base_port", OCF_RESKEY_base_port_default) +OCF_RESKEY_checkpoint_interval \ + =3D os.getenv("OCF_RESKEY_checkpoint_interval", \ + OCF_RESKEY_checkpoint_interval_default) +OCF_RESKEY_debug =3D os.getenv("OCF_RESKEY_debug", OCF_RESKEY_debug_defaul= t) + +ACTIVE_IMAGE =3D os.path.join(OCF_RESKEY_active_hidden_dir, \ + OCF_RESOURCE_INSTANCE + "-active.qcow2") +HIDDEN_IMAGE =3D os.path.join(OCF_RESKEY_active_hidden_dir, \ + OCF_RESOURCE_INSTANCE + "-hidden.qcow2") + +QMP_SOCK =3D os.path.join(HA_RSCTMP, OCF_RESOURCE_INSTANCE + "-qmp.sock") +COMP_SOCK =3D os.path.join(HA_RSCTMP, OCF_RESOURCE_INSTANCE + "-compare.so= ck") +COMP_OUT_SOCK =3D os.path.join(HA_RSCTMP, OCF_RESOURCE_INSTANCE \ + + "-comp_out.sock") + +PID_FILE =3D os.path.join(HA_RSCTMP, OCF_RESOURCE_INSTANCE + "-qemu.pid") + +QMP_LOG =3D os.path.join(OCF_RESKEY_log_dir, OCF_RESOURCE_INSTANCE + "-qmp= .log") +QEMU_LOG =3D os.path.join(OCF_RESKEY_log_dir, OCF_RESOURCE_INSTANCE + "-qe= mu.log") + +# Exception only raised by ourself +class Error(Exception): + pass + +def setup_constants(): + # This function is called after the parameters where validated + global MIGRATE_PORT, MIROR_PORT, COMPARE_IN_PORT, NBD_PORT + MIGRATE_PORT =3D int(OCF_RESKEY_base_port) + MIROR_PORT =3D int(OCF_RESKEY_base_port) + 1 + COMPARE_IN_PORT =3D int(OCF_RESKEY_base_port) + 2 + NBD_PORT =3D int(OCF_RESKEY_base_port) + 3 + + global QEMU_PRIMARY_CMDLINE + QEMU_PRIMARY_CMDLINE =3D ("'%(OCF_RESKEY_binary)s' %(OCF_RESKEY_option= s)s" + " -chardev socket,id=3Dcomp0,path=3D'%(COMP_SOCK)s',server,nowait" + " -chardev socket,id=3Dcomp0-0,path=3D'%(COMP_SOCK)s'" + " -chardev socket,id=3Dcomp_out,path=3D'%(COMP_OUT_SOCK)s',server,= nowait" + " -chardev socket,id=3Dcomp_out0,path=3D'%(COMP_OUT_SOCK)s'" + " -drive if=3Dnone,node-name=3Dcolo-disk0,driver=3Dquorum,read-pat= tern=3Dfifo," + "vote-threshold=3D1,children.0=3Dparent0" + " -qmp unix:'%(QMP_SOCK)s',server,nowait" + " -daemonize -D '%(QEMU_LOG)s' -pidfile '%(PID_FILE)s'") % globals= () + + global QEMU_SECONDARY_CMDLINE + QEMU_SECONDARY_CMDLINE =3D ("'%(OCF_RESKEY_binary)s' %(OCF_RESKEY_opti= ons)s" + " -chardev socket,id=3Dred0,host=3D'%(OCF_RESKEY_listen_address)s'= ," + "port=3D%(MIROR_PORT)s,server,nowait" + " -chardev socket,id=3Dred1,host=3D'%(OCF_RESKEY_listen_address)s'= ," + "port=3D%(COMPARE_IN_PORT)s,server,nowait" + " -object filter-redirector,id=3Df1,netdev=3Dhn0,queue=3Dtx,indev= =3Dred0" + " -object filter-redirector,id=3Df2,netdev=3Dhn0,queue=3Drx,outdev= =3Dred1" + " -object filter-rewriter,id=3Drew0,netdev=3Dhn0,queue=3Dall" + " -drive if=3Dnone,node-name=3Dchilds0,top-id=3Dcolo-disk0," + "driver=3Dreplication,mode=3Dsecondary,file.driver=3Dqcow2," + "file.file.filename=3D'%(ACTIVE_IMAGE)s',file.backing.driver=3Dqco= w2," + "file.backing.file.filename=3D'%(HIDDEN_IMAGE)s'," + "file.backing.backing=3Dparent0" + " -drive if=3Dnone,node-name=3Dcolo-disk0,driver=3Dquorum,read-pat= tern=3Dfifo," + "vote-threshold=3D1,children.0=3Dchilds0" + " -incoming tcp:'%(OCF_RESKEY_listen_address)s':%(MIGRATE_PORT)s" + " -qmp unix:'%(QMP_SOCK)s',server,nowait" + " -daemonize -D '%(QEMU_LOG)s' -pidfile '%(PID_FILE)s'") % globals= () + +def qemu_colo_meta_data(): + print("""\ + + + + + 0.1 + +Resource agent for qemu COLO. (https://wiki.qemu.org/Features/COLO) + +After defining the master/slave instance, the master score has to be +manually set to show which node has up-to-date data. So you copy your +image to one host (and create empty images the other host(s)) and then +run "crm_master -r name_of_your_primitive -v 10" on that host. + +Also, you have to set 'notify=3Dtrue' in the metadata attributes when +defining the master/slave instance. + + Qemu COLO + + + + + Qemu binary to use + Qemu binary + + + + + Directory to place logs in + Log directory + + + + + +Options to pass to qemu. These will be passed alongside COLO specific +options, so you need to follow these conventions: The netdev should have +id=3Dhn0 and the disk controller drive=3Dcolo-disk0. The image node should +have id/node-name=3Dparent0, but should not be connected to the guest. +Example: +-vnc :0 -enable-kvm -cpu qemu64,+kvmclock -m 512 -netdev bridge,id=3Dhn0 +-device virtio-net,netdev=3Dhn0 -device virtio-blk,drive=3Dcolo-disk0 +-drive if=3Dnone,id=3Dparent0,format=3Dqcow2,file=3D/mnt/vms/vm01.qcow2 + + Options to pass to qemu. + + + + Disk size of the image + Disk size of the image + + + + + +Directory where the active and hidden images will be stored. It is +recommended to put this on a ramdisk. + + Path to active and hidden images + + + + + Address to listen on. + Listen address + + + + + +4 tcp ports that are unique for each instance. (base_port to base_port + 3) + + Ports to use + + + + + +Interval for regular checkpoints in milliseconds. + + Interval for regular checkpoints + + + + + Enable debuging + Enable debuging + + + + + + + + + + + + + + + + + + + +""") + +def logs_open(): + global log + log =3D logging.getLogger(OCF_RESOURCE_INSTANCE) + log.setLevel(logging.DEBUG) + formater =3D logging.Formatter("(%(name)s) %(levelname)s: %(message)s") + + if sys.stdout.isatty(): + handler =3D logging.StreamHandler(stream=3Dsys.stderr) + handler.setFormatter(formater) + log.addHandler(handler) + + if HA_LOGFACILITY: + handler =3D logging.handlers.SysLogHandler("/dev/log") + handler.setFormatter(formater) + log.addHandler(handler) + + if HA_LOGFILE: + handler =3D logging.FileHandler(HA_LOGFILE) + handler.setFormatter(formater) + log.addHandler(handler) + + if HA_DEBUGLOG and HA_DEBUGLOG !=3D HA_LOGFILE: + handler =3D logging.FileHandler(HA_DEBUGLOG) + handler.setFormatter(formater) + log.addHandler(handler) + + global qmp_log + qmp_log =3D logging.getLogger("qmp_log") + qmp_log.setLevel(logging.DEBUG) + formater =3D logging.Formatter("%(message)s") + + if is_true(OCF_RESKEY_debug): + handler =3D logging.handlers.WatchedFileHandler(QMP_LOG) + handler.setFormatter(formater) + qmp_log.addHandler(handler) + else: + handler =3D logging.NullHandler() + qmp_log.addHandler(handler) + +def rotate_logfile(logfile, numlogs): + numlogs -=3D 1 + for n in range(numlogs, -1, -1): + file =3D logfile + if n !=3D 0: + file =3D "%s.%s" % (file, n) + if os.path.exists(file): + if n =3D=3D numlogs: + os.remove(file) + else: + newname =3D "%s.%s" % (logfile, n + 1) + os.rename(file, newname) + +def is_writable(file): + return os.access(file, os.W_OK) + +def is_executable_file(file): + return os.path.isfile(file) and os.access(file, os.X_OK) + +def is_true(var): + return re.match("yes|true|1|YES|TRUE|True|ja|on|ON", str(var)) !=3D No= ne + +# Check if the binary exists and is executable +def check_binary(binary): + if is_executable_file(binary): + return True + PATH =3D os.getenv("PATH", os.defpath) + for dir in PATH.split(os.pathsep): + if is_executable_file(os.path.join(dir, binary)): + return True + log.error("binary \"%s\" doesn't exist or not executable" % binary) + return False + +def run_command(commandline): + proc =3D subprocess.Popen(commandline, shell=3DTrue, stdout=3Dsubproce= ss.PIPE,\ + stderr=3Dsubprocess.STDOUT, universal_newlines= =3DTrue) + stdout, stderr =3D proc.communicate() + if proc.returncode !=3D 0: + log.error("command \"%s\" failed with code %s:\n%s" \ + % (commandline, proc.returncode, stdout)) + raise Error() + +# Functions for setting and getting the master score to tell Pacemaker whi= ch +# host has the most recent data +def set_master_score(score): + if score <=3D 0: + os.system("crm_master -q -l forever -D") + else: + os.system("crm_master -q -l forever -v %s" % score) + +def set_remote_master_score(remote, score): + if score <=3D 0: + os.system("crm_master -q -l forever -N '%s' -D" % remote) + else: + os.system("crm_master -q -l forever -N '%s' -v %s" % (remote, scor= e)) + +def get_master_score(): + proc =3D subprocess.Popen("crm_master -q -G", shell=3DTrue, \ + stdout=3Dsubprocess.PIPE, universal_newlines= =3DTrue) + stdout, stderr =3D proc.communicate() + if proc.returncode !=3D 0: + return 0 + else: + return int(str.strip(stdout)) + +def get_remote_master_score(remote): + proc =3D subprocess.Popen("crm_master -q -N '%s' -G" % remote, shell= =3DTrue, \ + stdout=3Dsubprocess.PIPE, universal_newlines= =3DTrue) + stdout, stderr =3D proc.communicate() + if proc.returncode !=3D 0: + return 0 + else: + return int(str.strip(stdout)) + +def recv_line(fd): + line =3D "" + while True: + tmp =3D fd.recv(1) + if tmp =3D=3D "\n": + break + line +=3D tmp + return line + +# Filter out events +def read_answer(fd): + while True: + line =3D recv_line(fd) + qmp_log.debug(line) + + answer =3D json.loads(line) + # Ignore everything else + if "return" in answer or "error" in answer: + break + return answer + +# Execute one or more qmp commands +def qmp_execute(fd, commands, ignore_error =3D False): + for command in commands: + if not command: + continue + + try: + to_send =3D json.dumps(command) + fd.sendall(to_send + "\n") + qmp_log.debug(to_send) + + answer =3D read_answer(fd) + except Exception as e: + log.error("while executing qmp command: %s\n%s" \ + % (json.dumps(command), e)) + raise Error() + + if not ignore_error and ("error" in answer): + log.error("qmp command returned error:\n%s\n%s" \ + % (json.dumps(command), json.dumps(answer))) + raise Error() + + return answer + +# Open qemu qmp connection +def qmp_open(fail_fast =3D False): + # Timeout for commands =3D our timeout minus 10s + timeout =3D max(1, (OCF_RESKEY_CRM_meta_timeout/1000)-10) + + try: + fd =3D socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + if fail_fast: + fd.settimeout(min(10, (OCF_RESKEY_CRM_meta_timeout/1000))) + else: + fd.settimeout(timeout) + fd.connect(QMP_SOCK) + + answer =3D qmp_execute(fd, [{"execute": "qmp_capabilities"}], True) + except Exception as e: + log.error("while connecting to qmp socket: %s" % e) + raise Error() + + if "error" in answer: + log.error("while connecting to qmp socket: %s" % json.dumps(answer= )) + raise Error() + + fd.settimeout(timeout) + return fd + +# Check qemu health and colo role +def qmp_check_state(fd): + answer =3D qmp_execute(fd, [{"execute": "query-status"}]) + vm_status =3D answer["return"] + + answer =3D qmp_execute(fd, [{"execute": "query-colo-status"}]) + colo_status =3D answer["return"] + + if vm_status["status"] =3D=3D "inmigrate": + role =3D OCF_SUCCESS + replication =3D OCF_NOT_RUNNING + + elif (vm_status["status"] =3D=3D "running" \ + or vm_status["status"] =3D=3D "colo" \ + or vm_status["status"] =3D=3D "finish-migrate") \ + and colo_status["mode"] =3D=3D "none" \ + and (colo_status["reason"] =3D=3D "request" \ + or colo_status["reason"] =3D=3D "none"): + role =3D OCF_RUNNING_MASTER + replication =3D OCF_NOT_RUNNING + + elif (vm_status["status"] =3D=3D "running" \ + or vm_status["status"] =3D=3D "colo" \ + or vm_status["status"] =3D=3D "finish-migrate") \ + and colo_status["mode"] =3D=3D "secondary": + role =3D OCF_SUCCESS + replication =3D OCF_SUCCESS + + elif (vm_status["status"] =3D=3D "running" \ + or vm_status["status"] =3D=3D "colo" \ + or vm_status["status"] =3D=3D "finish-migrate") \ + and colo_status["mode"] =3D=3D "primary": + role =3D OCF_RUNNING_MASTER + replication =3D OCF_SUCCESS + + else: + log.error("unknown qemu status: vm status: %s colo mode: %s" \ + % (vm_status["status"], colo_status["mode"])) + role =3D OCF_ERR_GENERIC + replication =3D OCF_ERR_GENERIC + + return role, replication + +# Get the host of the nbd node +def qmp_get_nbd_remote(fd): + block_nodes =3D qmp_execute(fd, [{"execute": "query-named-block-nodes"= }]) + for node in block_nodes["return"]: + if node["node-name"] =3D=3D "nbd0": + url =3D str(node["image"]["filename"]) + return str.split(url, "//")[1].split("/")[0].split(":")[0] + return None + +# Check if we are currently resyncing +def qmp_check_resync(fd): + answer =3D qmp_execute(fd, [{"execute": "query-block-jobs"}]) + for job in answer["return"]: + if job["device"] =3D=3D "resync": + return job + return None + +def qmp_start_resync(fd, remote): + qmp_execute(fd, [ + {"execute": "blockdev-add", "arguments": {"driver": "nbd", "node-n= ame": "nbd0", "server": {"type": "inet", "host": str(remote), "port": str(N= BD_PORT)}, "export": "parent0"}}, + {"execute": "blockdev-mirror", "arguments": {"device": "colo-disk0= ", "job-id": "resync", "target": "nbd0", "sync": "full", "auto-dismiss": Fa= lse}} + ]) + +def qmp_cancel_resync(fd): + if qmp_check_resync(fd)["status"] !=3D "concluded": + qmp_execute(fd, [{"execute": "block-job-cancel", "arguments": {"de= vice": "resync", "force": True}}], True) + # Wait for the block-job to finish + while qmp_check_resync(fd)["status"] !=3D "concluded": + time.sleep(1) + + qmp_execute(fd, [ + {"execute": "block-job-dismiss", "arguments": {"id": "resync"}}, + {"execute": "blockdev-del", "arguments": {"node-name": "nbd0"}} + ]) + +def qmp_start_colo(fd, remote): + # Check if we have a filter-rewriter + answer =3D qmp_execute(fd, [{"execute": "qom-list", "arguments": {"pat= h": "/objects/rew0"}}], True) + if "error" in answer: + if answer["error"]["class"] =3D=3D "DeviceNotFound": + have_filter_rewriter =3D False + else: + log.error("while checking for filter-rewriter:\n%s" \ + % json.dumps(answer)) + raise Error() + else: + have_filter_rewriter =3D True + + # Pause VM and cancel resync + qmp_execute(fd, [ + {"execute": "stop"}, + {"execute": "block-job-cancel", "arguments": {"device": "resync"}} + ]) + + # Wait for the block-job to finish + while qmp_check_resync(fd)["status"] !=3D "concluded": + time.sleep(1) + + # Add the replication node + qmp_execute(fd, [ + {"execute": "block-job-dismiss", "arguments": {"id": "resync"}}, + {"execute": "blockdev-add", "arguments": {"driver": "replication",= "node-name": "replication0", "mode": "primary", "file": "nbd0"}}, + {"execute": "x-blockdev-change", "arguments": {"parent": "colo-dis= k0", "node": "replication0"}} + ]) + + # Connect mirror and compare_in to secondary + qmp_execute(fd, [ + {"execute": "chardev-add", "arguments": {"id": "mirror0", "backend= ": {"type": "socket", "data": {"addr": {"type": "inet", "data": {"host": st= r(remote), "port": str(MIROR_PORT)}}, "server": False, "reconnect": 1}}}}, + {"execute": "chardev-add", "arguments": {"id": "compare1", "backen= d": {"type": "socket", "data": {"addr": {"type": "inet", "data": {"host": s= tr(remote), "port": str(COMPARE_IN_PORT)}}, "server": False, "reconnect": 1= }}}} + ]) + + # Add the COLO filters + if have_filter_rewriter: + qmp_execute(fd, [ + {"execute": "object-add", "arguments": {"qom-type": "filter-mi= rror", "id": "m0", "props": {"insert": "before", "position": "id=3Drew0", "= netdev": "hn0", "queue": "tx", "outdev": "mirror0"}}}, + {"execute": "object-add", "arguments": {"qom-type": "filter-re= director", "id": "redire0", "props": {"insert": "before", "position": "id= =3Drew0", "netdev": "hn0", "queue": "rx", "indev": "comp_out"}}}, + {"execute": "object-add", "arguments": {"qom-type": "filter-re= director", "id": "redire1", "props": {"insert": "before", "position": "id= =3Drew0", "netdev": "hn0", "queue": "rx", "outdev": "comp0"}}}, + {"execute": "object-add", "arguments": {"qom-type": "iothread"= , "id": "iothread1"}}, + {"execute": "object-add", "arguments": {"qom-type": "colo-comp= are", "id": "comp0", "props": {"primary_in": "comp0-0", "secondary_in": "co= mpare1", "outdev": "comp_out0", "iothread": "iothread1"}}} + ]) + else: + qmp_execute(fd, [ + {"execute": "object-add", "arguments": {"qom-type": "filter-mi= rror", "id": "m0", "props": {"netdev": "hn0", "queue": "tx", "outdev": "mir= ror0"}}}, + {"execute": "object-add", "arguments": {"qom-type": "filter-re= director", "id": "redire0", "props": {"netdev": "hn0", "queue": "rx", "inde= v": "comp_out"}}}, + {"execute": "object-add", "arguments": {"qom-type": "filter-re= director", "id": "redire1", "props": {"netdev": "hn0", "queue": "rx", "outd= ev": "comp0"}}}, + {"execute": "object-add", "arguments": {"qom-type": "iothread"= , "id": "iothread1"}}, + {"execute": "object-add", "arguments": {"qom-type": "colo-comp= are", "id": "comp0", "props": {"primary_in": "comp0-0", "secondary_in": "co= mpare1", "outdev": "comp_out0", "iothread": "iothread1"}}} + ]) + + # Start COLO + qmp_execute(fd, [ + {"execute": "migrate-set-capabilities", "arguments": {"capabilitie= s": [{"capability": "x-colo", "state": True }] }}, + {"execute": "migrate-set-parameters" , "arguments": {"x-checkpoint= -delay": int(OCF_RESKEY_checkpoint_interval)}}, + {"execute": "migrate", "arguments": {"uri": "tcp:%s:%s" % (remote,= MIGRATE_PORT)}} + ]) + + # Wait for COLO to start + while qmp_execute(fd, [{"execute": "query-status"}])["return"]["status= "] \ + =3D=3D "paused": + time.sleep(1) + +def qmp_primary_failover(fd): + qmp_execute(fd, [ + {"execute": "x-blockdev-change", "arguments": {"parent": "colo-dis= k0", "child": "children.1"}}, + {"execute": "blockdev-del", "arguments": {"node-name": "replicatio= n0"}}, + {"execute": "blockdev-del", "arguments": {"node-name": "nbd0"}}, + {"execute": "object-del", "arguments": {"id": "comp0"}}, + {"execute": "object-del", "arguments": {"id": "iothread1"}}, + {"execute": "object-del", "arguments": {"id": "m0"}}, + {"execute": "object-del", "arguments": {"id": "redire0"}}, + {"execute": "object-del", "arguments": {"id": "redire1"}}, + {"execute": "chardev-remove", "arguments": {"id": "mirror0"}}, + {"execute": "chardev-remove", "arguments": {"id": "compare1"}}, + {"execute": "x-colo-lost-heartbeat"} + ]) + +def qmp_secondary_failover(fd): + # Stop the NBD server, and resume + qmp_execute(fd, [ + {"execute": "nbd-server-stop"}, + {"execute": "x-colo-lost-heartbeat"} + ]) + + # Prepare for continuing replication when we have a new secondary + qmp_execute(fd, [ + {"execute": "object-del", "arguments": {"id": "f2"}}, + {"execute": "object-del", "arguments": {"id": "f1"}}, + {"execute": "chardev-remove", "arguments": {"id": "red1"}}, + {"execute": "chardev-remove", "arguments": {"id": "red0"}}, + {"execute": "chardev-add", "arguments": {"id": "comp0", "backend":= {"type": "socket", "data": {"addr": {"type": "unix", "data": {"path": str(= COMP_SOCK)}}, "server": True}}}}, + {"execute": "chardev-add", "arguments": {"id": "comp0-0", "backend= ": {"type": "socket", "data": {"addr": {"type": "unix", "data": {"path": st= r(COMP_SOCK)}}, "server": False}}}}, + {"execute": "chardev-add", "arguments": {"id": "comp_out", "backen= d": {"type": "socket", "data": {"addr": {"type": "unix", "data": {"path": s= tr(COMP_OUT_SOCK)}}, "server": True}}}}, + {"execute": "chardev-add", "arguments": {"id": "comp_out0", "backe= nd": {"type": "socket", "data": {"addr": {"type": "unix", "data": {"path": = str(COMP_OUT_SOCK)}}, "server": False}}}} + ]) + +# Sanity checks: check parameters, files, binaries, etc. +def qemu_colo_validate_all(): + # Check resource parameters + if not str.isdigit(OCF_RESKEY_base_port): + log.error("base_port needs to be a number") + return OCF_ERR_CONFIGURED + + if not str.isdigit(OCF_RESKEY_checkpoint_interval): + log.error("checkpoint_interval needs to be a number") + return OCF_ERR_CONFIGURED + + if not OCF_RESKEY_active_hidden_dir: + log.error("active_hidden_dir needs to be specified") + return OCF_ERR_CONFIGURED + + if not OCF_RESKEY_disk_size: + log.error("disk_size needs to be specified") + return OCF_ERR_CONFIGURED + + # Check resource meta configuration + if OCF_ACTION !=3D "stop": + if OCF_RESKEY_CRM_meta_master_max !=3D 1: + log.error("only one master allowed") + return OCF_ERR_CONFIGURED + + if OCF_RESKEY_CRM_meta_clone_max > 2: + log.error("maximum 2 clones allowed") + return OCF_ERR_CONFIGURED + + if OCF_RESKEY_CRM_meta_master_node_max !=3D 1: + log.error("only one master per node allowed") + return OCF_ERR_CONFIGURED + + if OCF_RESKEY_CRM_meta_clone_node_max !=3D 1: + log.error("only one clone per node allowed") + return OCF_ERR_CONFIGURED + + # Check if notify is enabled + if OCF_ACTION !=3D "stop" and OCF_ACTION !=3D "monitor": + if not is_true(OCF_RESKEY_CRM_meta_notify) \ + and not OCF_RESKEY_CRM_meta_notify_start_uname: + log.error("notify needs to be enabled") + return OCF_ERR_CONFIGURED + + # Check that globally-unique is disabled + if is_true(OCF_RESKEY_CRM_meta_globally_unique): + log.error("globally-unique needs to be disabled") + return OCF_ERR_CONFIGURED + + # Check binaries + if not check_binary(OCF_RESKEY_binary): + return OCF_ERR_INSTALLED + + if not check_binary("qemu-img"): + return OCF_ERR_INSTALLED + + # Check paths and files + if not is_writable(OCF_RESKEY_active_hidden_dir) \ + or not os.path.isdir(OCF_RESKEY_active_hidden_dir): + log.error("active and hidden image directory missing or not writab= le") + return OCF_ERR_PERM + + return OCF_SUCCESS + +# Check if qemu is running +def check_pid(): + if not os.path.exists(PID_FILE): + return OCF_NOT_RUNNING, None + + fd =3D open(PID_FILE, "r") + pid =3D int(str.strip(fd.readline())) + fd.close() + try: + os.kill(pid, 0) + except OSError: + log.info("qemu is not running") + return OCF_NOT_RUNNING, pid + else: + return OCF_SUCCESS, pid + +def qemu_colo_monitor(fail_fast =3D False): + status, pid =3D check_pid() + if status !=3D OCF_SUCCESS: + return status, OCF_ERR_GENERIC + + fd =3D qmp_open(fail_fast) + + role, replication =3D qmp_check_state(fd) + if role !=3D OCF_SUCCESS and role !=3D OCF_RUNNING_MASTER: + return role, replication + + if not fail_fast and OCF_RESKEY_CRM_meta_interval !=3D 0: + # This isn't a probe monitor + block_job =3D qmp_check_resync(fd) + if block_job !=3D None: + if "error" in block_job: + log.error("resync error: %s" % block_job["error"]) + qmp_cancel_resync(fd) + # TODO: notify pacemaker about peer failure + elif block_job["ready"] =3D=3D True: + log.info("resync done, starting colo") + peer =3D qmp_get_nbd_remote(fd) + qmp_start_colo(fd, peer) + # COLO started, our secondary now can be promoted if the + # primary fails + set_remote_master_score(peer, 100) + else: + pct_done =3D (float(block_job["offset"]) \ + / float(block_job["len"])) * 100 + log.info("resync %.2f%% done" % pct_done) + + fd.close() + + return role, replication + +def qemu_colo_start(): + if check_pid()[0] =3D=3D OCF_SUCCESS: + log.info("qemu is already running") + return OCF_SUCCESS + + run_command("qemu-img create -q -f qcow2 %s %s" \ + % (ACTIVE_IMAGE, OCF_RESKEY_disk_size)) + run_command("qemu-img create -q -f qcow2 %s %s" \ + % (HIDDEN_IMAGE, OCF_RESKEY_disk_size)) + + rotate_logfile(QMP_LOG, 4) + rotate_logfile(QEMU_LOG, 4) + run_command(QEMU_SECONDARY_CMDLINE) + + fd =3D qmp_open() + qmp_execute(fd, [ + {"execute": "nbd-server-start", "arguments": {"addr": {"type": "in= et", "data": {"host": str(OCF_RESKEY_listen_address), "port": str(NBD_PORT)= }}}}, + {"execute": "nbd-server-add", "arguments": {"device": "parent0", "= writable": True}} + ]) + fd.close() + + return OCF_SUCCESS + +def env_do_shutdown_guest(): + return OCF_RESKEY_CRM_meta_notify_active_uname \ + and OCF_RESKEY_CRM_meta_notify_stop_uname \ + and str.strip(OCF_RESKEY_CRM_meta_notify_active_uname) \ + =3D=3D str.strip(OCF_RESKEY_CRM_meta_notify_stop_uname) + +def env_find_secondary(): + # slave(s) =3D + # OCF_RESKEY_CRM_meta_notify_slave_uname + # - OCF_RESKEY_CRM_meta_notify_stop_uname + # + OCF_RESKEY_CRM_meta_notify_start_uname + # Filter out hosts that are stopping and ourselves + for host in str.split(OCF_RESKEY_CRM_meta_notify_slave_uname, " "): + if host: + for stopping_host \ + in str.split(OCF_RESKEY_CRM_meta_notify_stop_uname, " "): + if host =3D=3D stopping_host: + break + else: + if host !=3D HOSTNAME: + # we found a valid secondary + return host + + for host in str.split(OCF_RESKEY_CRM_meta_notify_start_uname, " "): + if host !=3D HOSTNAME: + # we found a valid secondary + return host + + # we found no secondary + return None + +def _qemu_colo_stop(monstatus, shutdown_guest): + # stop action must do everything possible to stop the resource + try: + now =3D time.time() + timeout =3D now + (OCF_RESKEY_CRM_meta_timeout/1000)-10 + force_stop =3D False + + if monstatus =3D=3D OCF_NOT_RUNNING: + log.info("resource is already stopped") + return OCF_SUCCESS + elif monstatus =3D=3D OCF_RUNNING_MASTER or monstatus =3D=3D OCF_S= UCCESS: + force_stop =3D False + else: + force_stop =3D True + + if not force_stop: + fd =3D qmp_open(True) + if shutdown_guest: + if monstatus =3D=3D OCF_RUNNING_MASTER: + qmp_execute(fd, [{"execute": "system_powerdown"}]) + else: + qmp_execute(fd, [{"execute": "quit"}]) + fd.close() + + # wait for qemu to stop + while time.time() < timeout: + status, pid =3D check_pid() + if status =3D=3D OCF_NOT_RUNNING: + # qemu stopped + return OCF_SUCCESS + elif status =3D=3D OCF_SUCCESS: + # wait + time.sleep(1) + else: + # something went wrong, force stop instead + break + + log.warning("clean stop timeout reached") + except Exception as e: + log.warning("error while stopping: \"%s\"" % e.message) + + log.info("force stopping qemu") + + status, pid =3D check_pid() + if status =3D=3D OCF_NOT_RUNNING: + return OCF_SUCCESS + try: + os.kill(pid, signal.SIGTERM) + time.sleep(2) + os.kill(pid, signal.SIGKILL) + except Exception: + pass + + while check_pid()[0] !=3D OCF_NOT_RUNNING: + time.sleep(1) + + return OCF_SUCCESS + +def qemu_colo_stop(): + shutdown_guest =3D env_do_shutdown_guest() + try: + role, replication =3D qemu_colo_monitor(True) + except Exception: + role, replication =3D OCF_ERR_GENERIC, OCF_ERR_GENERIC + + status =3D _qemu_colo_stop(role, shutdown_guest) + + if HOSTNAME =3D=3D str.strip(OCF_RESKEY_CRM_meta_notify_master_uname): + peer =3D env_find_secondary() + if peer and (get_remote_master_score(peer) > 10) \ + and not shutdown_guest: + # We where a healthy primary and had a healthy secondary. We w= here + # stopped so outdate ourselves, as the secondary will take ove= r. + set_master_score(0) + else: + if role =3D=3D OCF_RUNNING_MASTER: + # We where a healthy primary but had no healty secondary o= r it + # was stopped as well. So we have up-to-date data. + set_master_score(10) + else: + # We where a unhealthy primary but also had no healty seco= ndary. + # So we still should have up-to-date data. + set_master_score(5) + else: + if get_master_score() > 10: + if role =3D=3D OCF_SUCCESS: + if shutdown_guest: + # We where a healthy secondary and (probably) had a he= althy + # primary and both where stopped. So we have up-to-dat= e data + # too. + set_master_score(10) + else: + # We where a healthy secondary and (probably) had a he= althy + # primary still running. So we are now out of date. + set_master_score(0) + else: + # We where a unhealthy secondary. So we are now out of dat= e. + set_master_score(0) + + return status + +def qemu_colo_notify(): + action =3D "%s-%s" % (OCF_RESKEY_CRM_meta_notify_type, \ + OCF_RESKEY_CRM_meta_notify_operation) + + if action =3D=3D "post-start": + if HOSTNAME =3D=3D str.strip(OCF_RESKEY_CRM_meta_notify_master_una= me): + peer =3D str.strip(OCF_RESKEY_CRM_meta_notify_start_uname) + fd =3D qmp_open() + qmp_start_resync(fd, peer) + fd.close() + # The secondary has inconsistent data until resync is finished + set_remote_master_score(peer, 0) + + elif action =3D=3D "pre-stop": + if not env_do_shutdown_guest() \ + and HOSTNAME =3D=3D str.strip(OCF_RESKEY_CRM_meta_notify_master= _uname): + fd =3D qmp_open() + peer =3D qmp_get_nbd_remote(fd) + if peer =3D=3D str.strip(OCF_RESKEY_CRM_meta_notify_stop_uname= ): + if qmp_check_resync(fd) !=3D None: + qmp_cancel_resync(fd) + elif peer and get_remote_master_score(peer) > 10: + qmp_primary_failover(fd) + fd.close() + + return OCF_SUCCESS + +def qemu_colo_promote(): + role, replication =3D qemu_colo_monitor() + + if role =3D=3D OCF_SUCCESS and replication =3D=3D OCF_NOT_RUNNING: + status =3D _qemu_colo_stop(role, False) + if status !=3D OCF_SUCCESS: + return status + + rotate_logfile(QMP_LOG, 4) + rotate_logfile(QEMU_LOG, 4) + run_command(QEMU_PRIMARY_CMDLINE) + set_master_score(101) + + peer =3D env_find_secondary() + if peer: + fd =3D qmp_open() + qmp_start_resync(fd, peer) + # The secondary has inconsistent data until resync is finished + set_remote_master_score(peer, 0) + fd.close() + return OCF_SUCCESS + elif role =3D=3D OCF_SUCCESS and replication =3D=3D OCF_SUCCESS: + fd =3D qmp_open() + qmp_secondary_failover(fd) + set_master_score(101) + + peer =3D env_find_secondary() + if peer: + qmp_start_resync(fd, peer) + # The secondary has inconsistent data until resync is finished + set_remote_master_score(peer, 0) + fd.close() + return OCF_SUCCESS + else: + return OCF_ERR_GENERIC + +def qemu_colo_demote(): + status =3D qemu_colo_stop() + if status !=3D OCF_SUCCESS: + return status + return qemu_colo_start() + + +if OCF_ACTION =3D=3D "meta-data": + qemu_colo_meta_data() + exit(OCF_SUCCESS) + +logs_open() + +status =3D qemu_colo_validate_all() +# Exit here if our sanity checks fail, but try to continue if we need to s= top +if status !=3D OCF_SUCCESS and OCF_ACTION !=3D "stop": + exit(status) + +setup_constants() + +os.system("echo 'Action: %s' >> /tmp/ra-env.log" % OCF_ACTION) +os.system("env >> /tmp/ra-env.log") + +try: + if OCF_ACTION =3D=3D "start": + status =3D qemu_colo_start() + elif OCF_ACTION =3D=3D "stop": + status =3D qemu_colo_stop() + elif OCF_ACTION =3D=3D "monitor": + status =3D qemu_colo_monitor()[0] + elif OCF_ACTION =3D=3D "notify": + status =3D qemu_colo_notify() + elif OCF_ACTION =3D=3D "promote": + status =3D qemu_colo_promote() + elif OCF_ACTION =3D=3D "demote": + status =3D qemu_colo_demote() + elif OCF_ACTION =3D=3D "validate-all": + status =3D qemu_colo_validate_all() + else: + status =3D OCF_ERR_UNIMPLEMENTED +except Error: + exit(OCF_ERR_GENERIC) +else: + exit(status) -- 2.20.1 From nobody Fri May 3 21:27:33 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1574359588; cv=none; d=zoho.com; s=zohoarc; b=LnT3fCV9RhIupOS9e3fCsOUzG0qzpsdMxfKadHMZengbRzNF5tjB/wY42kz0hf2vbP6Harj9pE/VxYNmTNIXSLvhQDwBiJF24q98o76JJub/HZBNOrydHgk0aCTivz6Avj4TpMOsw+mXag531y2A5v8kpOCcXohVzDx25cCLh5U= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1574359588; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=UYbOEbRY3jM+S3MoTsVUul1zz2HDRxLP+EW4jffFhnI=; b=Lk3FZI/jIk7k3O8JpuQihZQ9E7vRkU/qycUJlAH8qbWNBu3KU8CAhqmRAIBwsPIrM6H2m2+iIl8AfHlpIVZbWjRyNyQMNWgEbT0d71NnwZYLdoVP/cfqc1D0UgtsuKzJD7Yz3xBz2oddaHQVLuyVI4ewRPfC49+RBXNjVqjsacM= ARC-Authentication-Results: i=1; mx.zoho.com; dkim=fail; spf=pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1574359588089628.7116627595997; Thu, 21 Nov 2019 10:06:28 -0800 (PST) Received: from localhost ([::1]:43364 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iXqqX-0007dF-O1 for importer@patchew.org; Thu, 21 Nov 2019 13:06:26 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:42939) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iXqak-0000GC-Mh for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:09 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iXqai-00076X-8M for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:06 -0500 Received: from mout.web.de ([212.227.15.3]:47873) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1iXqah-000766-Rk for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:04 -0500 Received: from luklap ([89.247.255.160]) by smtp.web.de (mrweb004 [213.165.67.108]) with ESMTPSA (Nemesis) id 0MZAp2-1iHGbn0EP6-00KxTH; Thu, 21 Nov 2019 18:49:39 +0100 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=web.de; s=dbaedf251592; t=1574358579; bh=yXu8lwW3nSXeCgvYXToXRi5ZquVyneVbYn6+LKIl2/s=; h=X-UI-Sender-Class:Date:From:To:Cc:Subject:In-Reply-To:References; b=RGtOqTr4mEtz0Uy3gIqCIDoHToNJi79fk/2vax9NeMVNC64ZJhfsJhkbwKSYt1ua+ fyvYyo9O0ga+/lHewaEnneHdUiQWasqMsq2soG6wVuLcHzRolepUmNHy7AxCGXuID7 oMso6epzOofwgB0G5Y/EtoeF+O3n7C9UJu2iZbOk= X-UI-Sender-Class: c548c8c5-30a9-4db5-a2e7-cb6cb037b8f9 Date: Thu, 21 Nov 2019 18:49:38 +0100 From: Lukas Straub To: qemu-devel Subject: [PATCH 3/4] colo: Introduce high-level test Message-ID: <8415b472eb0b5ed9e787850d70f7952a29b93934.1574356137.git.lukasstraub2@web.de> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Provags-ID: V03:K1:GddmsFf24tQM+2bWN7uo92zhAOkad+mt+q2fjAYhNXNA8W8bu9N laWwne7a3jIIt6OIDcr5FhXECDd3HgrjVXZKEt89yfZ/oLaUm31gGon3WJM5pGayMxiSG9Q T4m6uJA8WfXrcCA2pixTdRn3pq4YdLQc6sXsPcKQmZfnuVf5RQDbaB7y5HQnOoOR7/nPgyS 3jk3yFy3WypqsUR4qq81A== X-UI-Out-Filterresults: notjunk:1;V03:K0:z+aRLUIZEjg=:kVwx94ADFL0GTNHMfdc3YH 5H3K1Gv3VKxHhMEAsUtL2A8iEbzWxGcF2AdW4Bf3lIhb9eh7sBSlnR+V6q1ODAWYN7GGIuVFa K0zGIU9wnuT53GtvN2kIUAcnsxt0NIUYYrpe9tOSnj9ByKKl92EElKMn9JObEhtdY62Tohl7S 3k4NNeI/UC5ILDc9ocKMFPELl2sBYTFwPEr4lBn37Lt636djZdFacBrSoEI71EplmwkykPDs+ wFYPbzjlWQ0nwjovDUpKoLPpgFWXwhXxbFQ8L3BCSAeVEiVm5WHnU3B78h7CL9e4u9oSVOjcG VqWTvyPmhj8kJIoeynryF5bNG/Dm2jqepNP0YFBpxsBvlDs76ar/jjJYKzPogCR8hyh2r02np karBbnXi+NcgJ/tEHxE4+POauPeveDL/63DZ07zVFXYafUuvN6uW6YA1HPDskXoo+sg11prbk AMAH3Spmo7hzCDb2P09CJilorQH91nAtkHHTd0Qvr5rUfaieJQGM2dkEAyxXQthu9ovXMo0aw eOVBs3qTBk2NGodvVbI/8sxTYPPd9bYjCD1M+HQBlT0T0OTs4SqbyW8o7Z5EgdzeV6KIpxcRn 22onMp9x0kPSjn/oAr0UqsEjZMhBpOB3jOZV01uqJVDdQO21pVllQlU+NE4H+QMDwuSR/thS/ k7A/MurlTDw1EnmX1/kta84FDMrhqsCwwlK8qvuVg7O7KtG3WbgkkmWUe5MPZEwuarHJTg5ck 3axTHmHYUUrPcIsaNIi7s+X9IFQeF48fLfR7ytRMm77wcgYC8rU/RWUF5KEA3Dx3RYUhfIkYG /n3aIjV8CK4B+lQp9BcTXikEwTZHpKfUzeoVss6sVhBon2VEhDX4IgYK+N1GdpsOtyMoEsN6l J48kpUZK+wu6Do2l3hFtRx4YRLuh7uulTV+Gfn1gMK2zzlsk5WGwRi/05liovBMGmDuj93VHR JsoFZuJHvtCV2ByBdjv2pmWfBdNSyfeM3WZrqlcd2gnD464qXzDmLKU44y8Nvc7VgL9J7LrY2 Qq+l07uUmtbDQpgo/YMnXLaplmWjIYKYRczb/koGcZpjiqORvBvgsmuWNGpwObJje52aQhndR LPXO+aqLFZLGgljJ6HKuhRCXveKJvekytQbzFxlR/vVTgzegkl1Lbq0FL1CupFoLEZCIOieVs qqMP9cPO4ZPV8LCISwRCXc/Bsknh9orknAdJXY94HMj72cf83VqkD+4oSRh6Gjka8IAF/S+gz LY11GtMY72By3zGBO5ruiy09IC0PmnirKTH387mwvBS83MNNFeXRX7cb9T8o= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 212.227.15.3 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: "Zhang, Chen" , Jason Wang , Alberto Garcia , "Dr. David Alan Gilbert" Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) Content-Type: text/plain; charset="utf-8" Add high-level test relying on the colo resource-agent to test all failover cases while checking guest network connectivity Signed-off-by: Lukas Straub --- scripts/colo-resource-agent/crm_master | 44 +++ tests/acceptance/colo.py | 444 +++++++++++++++++++++++++ 2 files changed, 488 insertions(+) create mode 100755 scripts/colo-resource-agent/crm_master create mode 100644 tests/acceptance/colo.py diff --git a/scripts/colo-resource-agent/crm_master b/scripts/colo-resource= -agent/crm_master new file mode 100755 index 0000000000..00c386b949 --- /dev/null +++ b/scripts/colo-resource-agent/crm_master @@ -0,0 +1,44 @@ +#!/bin/bash + +# Fake crm_master for COLO testing +# +# Copyright (c) Lukas Straub +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. + +TMPDIR=3D"$HA_RSCTMP" +score=3D0 +query=3D0 + +OPTIND=3D1 +while getopts 'Qql:Dv:N:G' opt; do + case "$opt" in + Q|q) + # Noop + ;; + "l") + # Noop + ;; + "D") + score=3D0 + ;; + "v") + score=3D$OPTARG + ;; + "N") + TMPDIR=3D"$COLO_SMOKE_REMOTE_TMP" + ;; + "G") + query=3D1 + ;; + esac +done + +if (( query )); then + cat "${TMPDIR}/master_score" || exit 1 +else + echo $score > "${TMPDIR}/master_score" || exit 1 +fi + +exit 0 diff --git a/tests/acceptance/colo.py b/tests/acceptance/colo.py new file mode 100644 index 0000000000..94a6adabdd --- /dev/null +++ b/tests/acceptance/colo.py @@ -0,0 +1,444 @@ +#!/usr/bin/env python + +# High-level test for qemu COLO testing all failover cases while checking +# guest network connectivity +# +# Copyright (c) Lukas Straub +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. + +import select +import sys +import subprocess +import shutil +import os +import signal +import os.path +import json +import time +import tempfile + +from avocado import Test +from avocado.utils.archive import gzip_uncompress +from avocado.utils import network +from avocado_qemu import pick_default_qemu_bin, SRC_ROOT_DIR + +class ColoTest(Test): + timeout =3D 120 + + # Constants + OCF_SUCCESS =3D 0 + OCF_ERR_GENERIC =3D 1 + OCF_ERR_ARGS =3D 2 + OCF_ERR_UNIMPLEMENTED =3D 3 + OCF_ERR_PERM =3D 4 + OCF_ERR_INSTALLED =3D 5 + OCF_ERR_CONFIGURED =3D 6 + OCF_NOT_RUNNING =3D 7 + OCF_RUNNING_MASTER =3D 8 + OCF_FAILED_MASTER =3D 9 + + HOSTA =3D 10 + HOSTB =3D 11 + + QEMU_OPTIONS =3D (" -enable-kvm -cpu qemu64,+kvmclock -m 256" + " -device virtio-net,netdev=3Dhn0" + " -device virtio-blk,drive=3Dcolo-disk0") + COLO_RA =3D "scripts/colo-resource-agent/colo" + FAKEPATH =3D ".:scripts/colo-resource-agent" + + bridge_proc =3D None + ssh_proc =3D None + + def setUp(self): + # Qemu binary + default_qemu_bin =3D pick_default_qemu_bin() + self.QEMU_BINARY =3D self.params.get('qemu_bin', default=3Ddefault= _qemu_bin) + + # Find free port range + base_port =3D 1024 + while True: + base_port =3D network.find_free_port(start_port=3Dbase_port, \ + address=3D"127.0.0.1") + if base_port =3D=3D None: + self.cancel("Failed to find a free port") + for n in range(base_port, base_port +6): + if not network.is_port_free(n, "127.0.0.1"): + base_port =3D n +1 + break + else: + # for loop above didn't break + break + + self.BRIDGE_HOSTA_PORT =3D base_port + self.BRIDGE_HOSTB_PORT =3D base_port + 1 + self.SSH_PORT =3D base_port + 2 + self.COLO_BASE_PORT =3D base_port + 3 + + # Temporary directories + self.TMPDIR =3D tempfile.mkdtemp() + self.TMPA =3D os.path.join(self.TMPDIR, "hosta") + self.TMPB =3D os.path.join(self.TMPDIR, "hostb") + os.makedirs(self.TMPA) + os.makedirs(self.TMPB) + + # Disk images + self.HOSTA_IMAGE =3D os.path.join(self.TMPA, "image.raw") + self.HOSTB_IMAGE =3D os.path.join(self.TMPB, "image.raw") + + image_url =3D ("https://downloads.openwrt.org/releases/18.06.5/tar= gets/" + "x86/64/openwrt-18.06.5-x86-64-combined-ext4.img.gz") + image_hash =3D ("55589a3a9b943218b1734d196bcaa92a" + "3cfad91c07fa6891474b4291ce1b8ec2") + self.IMAGE_SIZE =3D "285736960b" + download =3D self.fetch_asset(image_url, asset_hash=3Dimage_hash, \ + algorithm=3D"sha256") + gzip_uncompress(download, self.HOSTA_IMAGE) + shutil.copyfile(self.HOSTA_IMAGE, self.HOSTB_IMAGE) + + self.log.info("Will put logs in \"%s\"" % self.outputdir) + self.RA_LOG =3D os.path.join(self.outputdir, "resource-agent.log") + self.HOSTA_LOGDIR =3D os.path.join(self.outputdir, "hosta") + self.HOSTB_LOGDIR =3D os.path.join(self.outputdir, "hostb") + os.makedirs(self.HOSTA_LOGDIR) + os.makedirs(self.HOSTB_LOGDIR) + + # Network bridge + self.BRIDGE_PIDFILE =3D os.path.join(self.TMPDIR, "bridge.pid") + pid =3D self.read_pidfile(self.BRIDGE_PIDFILE) + if not (pid and self.check_pid(pid)): + self.run_command(("%s -M none -daemonize -pidfile '%s'" + " -netdev socket,id=3Dhosta,listen=3D127.0.0.1:%s" + " -netdev hubport,id=3Dporta,hubid=3D0,netdev=3Dhosta" + " -netdev socket,id=3Dhostb,listen=3D127.0.0.1:%s" + " -netdev hubport,id=3Dportb,hubid=3D0,netdev=3Dhostb" + " -netdev user,net=3D192.168.1.1/24,host=3D192.168.1.2," + "hostfwd=3Dtcp:127.0.0.1:%s-192.168.1.1:22,id=3Dhost" + " -netdev hubport,id=3Dhostport,hubid=3D0,netdev=3Dhost") + % (self.QEMU_BINARY, self.BRIDGE_PIDFILE, + self.BRIDGE_HOSTA_PORT, self.BRIDGE_HOSTB_PORT, + self.SSH_PORT), 0) + + def tearDown(self): + try: + pid =3D self.read_pidfile(self.BRIDGE_PIDFILE) + if pid and self.check_pid(pid): + os.kill(pid, signal.SIGKILL) + except Exception(): + pass + try: + self.ra_stop(self.HOSTA) + except Exception(): + pass + try: + self.ra_stop(self.HOSTB) + except Exception(): + pass + try: + if self.ssh_proc: + self.ssh_proc.terminate() + except Exception(): + pass + + shutil.rmtree(self.TMPDIR) + + def run_command(self, cmdline, expected_status, env=3DNone, error_fail= =3DTrue): + proc =3D subprocess.Popen(cmdline, shell=3DTrue, stdout=3Dsubproce= ss.PIPE, \ + stderr=3Dsubprocess.STDOUT, \ + universal_newlines=3DTrue, env=3Denv) + stdout, stderr =3D proc.communicate() + if proc.returncode !=3D expected_status: + message =3D "command \"%s\" failed with code %s:\n%s" \ + % (cmdline, proc.returncode, stdout) + if error_fail: + self.log.error(message) + self.fail("command \"%s\" failed" % cmdline) + else: + self.log.info(message) + + return proc.returncode + + def cat_line(self, path): + line=3D"" + try: + fd =3D open(path, "r") + line =3D str.strip(fd.readline()) + fd.close() + except: + pass + return line + + def read_pidfile(self, pidfile): + try: + pid =3D int(self.cat_line(pidfile)) + except ValueError: + return None + else: + return pid + + def check_pid(self, pid): + try: + os.kill(pid, 0) + except OSError: + return False + else: + return True + + def ssh_ping(self, proc): + proc.stdin.write("ping\n") + if not select.select([proc.stdout], [], [], 30)[0]: + raise self.fail("ssh ping timeout reached") + if proc.stdout.readline() !=3D "ping\n": + raise self.fail("unexpected ssh ping answer") + + def ssh_open(self): + commandline =3D ("ssh -o \"UserKnownHostsFile /dev/null\"" + " -o \"StrictHostKeyChecking no\"" + " -p%s root@127.0.0.1") % self.SSH_PORT + + self.log.info("Connecting via ssh") + for i in range(10): + if self.run_command(commandline + " exit", 0, error_fail=3DFal= se) \ + =3D=3D 0: + proc =3D subprocess.Popen(commandline + " cat", shell=3DTr= ue, \ + stdin=3Dsubprocess.PIPE, \ + stdout=3Dsubprocess.PIPE, \ + stderr=3D0, \ + universal_newlines=3DTrue, + bufsize=3D1) + self.ssh_ping(proc) + return proc + else: + time.sleep(5) + self.fail("ssh connect timeout reached") + + def ssh_close(self, proc): + proc.terminate() + + def setup_base_env(self, host): + PATH =3D os.getenv("PATH", "") + env =3D { "PATH": "%s:%s" % (self.FAKEPATH, PATH), + "HA_LOGFILE": self.RA_LOG, + "OCF_RESOURCE_INSTANCE": "colo-test", + "OCF_RESKEY_CRM_meta_clone_max": "2", + "OCF_RESKEY_CRM_meta_notify": "true", + "OCF_RESKEY_CRM_meta_timeout": "30000", + "OCF_RESKEY_binary": self.QEMU_BINARY, + "OCF_RESKEY_disk_size": str(self.IMAGE_SIZE), + "OCF_RESKEY_checkpoint_interval": "1000", + "OCF_RESKEY_base_port": str(self.COLO_BASE_PORT), + "OCF_RESKEY_debug": "true"} + + if host =3D=3D self.HOSTA: + env.update({"OCF_RESKEY_options": + ("%s -netdev socket,id=3Dhn0,connect=3D127.0.0= .1:%s" + " -drive if=3Dnone,id=3Dparent0,format=3Draw,= file=3D'%s'") + % (self.QEMU_OPTIONS, self.BRIDGE_HOSTA_PORT, + self.HOSTA_IMAGE), + "OCF_RESKEY_active_hidden_dir": self.TMPA, + "OCF_RESKEY_listen_address": "127.0.0.1", + "OCF_RESKEY_log_dir": self.HOSTA_LOGDIR, + "OCF_RESKEY_CRM_meta_on_node": "127.0.0.1", + "HA_RSCTMP": self.TMPA, + "COLO_SMOKE_REMOTE_TMP": self.TMPB}) + else: + env.update({"OCF_RESKEY_options": + ("%s -netdev socket,id=3Dhn0,connect=3D127.0.0= .1:%s" + " -drive if=3Dnone,id=3Dparent0,format=3Draw,= file=3D'%s'") + % (self.QEMU_OPTIONS, self.BRIDGE_HOSTB_PORT, + self.HOSTB_IMAGE), + "OCF_RESKEY_active_hidden_dir": self.TMPB, + "OCF_RESKEY_listen_address": "127.0.0.2", + "OCF_RESKEY_log_dir": self.HOSTB_LOGDIR, + "OCF_RESKEY_CRM_meta_on_node": "127.0.0.2", + "HA_RSCTMP": self.TMPB, + "COLO_SMOKE_REMOTE_TMP": self.TMPA}) + return env + + def ra_start(self, host): + env =3D self.setup_base_env(host) + self.run_command(self.COLO_RA + " start", self.OCF_SUCCESS, env) + + def ra_stop(self, host): + env =3D self.setup_base_env(host) + self.run_command(self.COLO_RA + " stop", self.OCF_SUCCESS, env) + + def ra_monitor(self, host, expected_status): + env =3D self.setup_base_env(host) + self.run_command(self.COLO_RA + " monitor", expected_status, env) + + def ra_promote(self, host): + env =3D self.setup_base_env(host) + self.run_command(self.COLO_RA + " promote", self.OCF_SUCCESS, env) + + def ra_notify_start(self, host): + env =3D self.setup_base_env(host) + + env.update({"OCF_RESKEY_CRM_meta_notify_type": "post", + "OCF_RESKEY_CRM_meta_notify_operation": "start"}) + + if host =3D=3D self.HOSTA: + env.update({"OCF_RESKEY_CRM_meta_notify_master_uname": "127.0.= 0.1", + "OCF_RESKEY_CRM_meta_notify_start_uname": "127.0.0= .2"}) + else: + env.update({"OCF_RESKEY_CRM_meta_notify_master_uname": "127.0.= 0.2", + "OCF_RESKEY_CRM_meta_notify_start_uname": "127.0.0= .1"}) + + self.run_command(self.COLO_RA + " notify", self.OCF_SUCCESS, env) + + def ra_notify_stop(self, host): + env =3D self.setup_base_env(host) + + env.update({"OCF_RESKEY_CRM_meta_notify_type": "pre", + "OCF_RESKEY_CRM_meta_notify_operation": "stop"}) + + if host =3D=3D self.HOSTA: + env.update({"OCF_RESKEY_CRM_meta_notify_master_uname": "127.0.= 0.1", + "OCF_RESKEY_CRM_meta_notify_stop_uname": "127.0.0.= 2"}) + else: + env.update({"OCF_RESKEY_CRM_meta_notify_master_uname": "127.0.= 0.2", + "OCF_RESKEY_CRM_meta_notify_stop_uname": "127.0.0.= 1"}) + + self.run_command(self.COLO_RA + " notify", self.OCF_SUCCESS, env) + + def kill_qemu_pre(self, host, hang_qemu=3DFalse): + if host =3D=3D self.HOSTA: + pid =3D self.read_pidfile(os.path.join(self.TMPA, \ + "colo-test-qemu.pi= d")) + else: + pid =3D self.read_pidfile(os.path.join(self.TMPB, \ + "colo-test-qemu.pi= d")) + + if pid and self.check_pid(pid): + if hang_qemu: + os.kill(pid, signal.SIGSTOP) + else: + os.kill(pid, signal.SIGKILL) + while self.check_pid(pid): + time.sleep(1) + + def kill_qemu_post(self, host, hang_qemu=3DFalse): + if host =3D=3D self.HOSTA: + pid =3D self.read_pidfile(os.path.join(self.TMPA, \ + "colo-test-qemu.pi= d")) + else: + pid =3D self.read_pidfile(os.path.join(self.TMPB, \ + "colo-test-qemu.pi= d")) + + if hang_qemu and pid and self.check_pid(pid): + os.kill(pid, signal.SIGKILL) + while self.check_pid(pid): + time.sleep(1) + + def get_master_score(self, host): + if host =3D=3D self.HOSTA: + return int(self.cat_line(os.path.join(self.TMPA, "master_score= "))) + else: + return int(self.cat_line(os.path.join(self.TMPB, "master_score= "))) + + def _test_colo(self, hang_qemu=3DFalse, loop=3DFalse, do_ssh_ping=3DTr= ue): + self.ra_stop(self.HOSTA) + self.ra_stop(self.HOSTB) + + self.log.info("Startup") + self.ra_start(self.HOSTA) + self.ra_start(self.HOSTB) + + self.ra_monitor(self.HOSTA, self.OCF_SUCCESS) + self.ra_monitor(self.HOSTB, self.OCF_SUCCESS) + + self.log.info("Promoting") + self.ra_promote(self.HOSTA) + self.ra_notify_start(self.HOSTA) + + while self.get_master_score(self.HOSTB) !=3D 100: + self.ra_monitor(self.HOSTA, self.OCF_RUNNING_MASTER) + self.ra_monitor(self.HOSTB, self.OCF_SUCCESS) + time.sleep(1) + + if do_ssh_ping: + self.ssh_proc =3D self.ssh_open() + + primary =3D self.HOSTA + secondary =3D self.HOSTB + + while True: + self.log.info("Secondary failover") + self.kill_qemu_pre(primary, hang_qemu) + self.ra_notify_stop(secondary) + self.ra_monitor(secondary, self.OCF_SUCCESS) + self.ra_promote(secondary) + self.ra_monitor(secondary, self.OCF_RUNNING_MASTER) + self.kill_qemu_post(primary, hang_qemu) + if do_ssh_ping: + self.ssh_ping(self.ssh_proc) + tmp =3D primary + primary =3D secondary + secondary =3D tmp + + self.log.info("Secondary continue replication") + self.ra_start(secondary) + self.ra_notify_start(primary) + if do_ssh_ping: + self.ssh_ping(self.ssh_proc) + + # Wait for resync + while self.get_master_score(secondary) !=3D 100: + self.ra_monitor(primary, self.OCF_RUNNING_MASTER) + self.ra_monitor(secondary, self.OCF_SUCCESS) + time.sleep(1) + if do_ssh_ping: + self.ssh_ping(self.ssh_proc) + + self.log.info("Primary failover") + self.kill_qemu_pre(secondary, hang_qemu) + self.ra_monitor(primary, self.OCF_RUNNING_MASTER) + self.ra_notify_stop(primary) + self.ra_monitor(primary, self.OCF_RUNNING_MASTER) + self.kill_qemu_post(secondary, hang_qemu) + if do_ssh_ping: + self.ssh_ping(self.ssh_proc) + + self.log.info("Primary continue replication") + self.ra_start(secondary) + self.ra_notify_start(primary) + if do_ssh_ping: + self.ssh_ping(self.ssh_proc) + + # Wait for resync + while self.get_master_score(secondary) !=3D 100: + self.ra_monitor(primary, self.OCF_RUNNING_MASTER) + self.ra_monitor(secondary, self.OCF_SUCCESS) + time.sleep(1) + if do_ssh_ping: + self.ssh_ping(self.ssh_proc) + + if not loop: + break + + if do_ssh_ping: + self.ssh_close(self.ssh_proc) + + self.ra_stop(self.HOSTA) + self.ra_stop(self.HOSTB) + + self.ra_monitor(self.HOSTA, self.OCF_NOT_RUNNING) + self.ra_monitor(self.HOSTB, self.OCF_NOT_RUNNING) + self.log.info("all ok") + + def test_colo_peer_crashing(self): + """ + :avocado: tags=3Dcolo + :avocado: tags=3Darch:x86_64 + """ + self.log.info("Testing with peer qemu crashing") + self._test_colo() + + def test_colo_peer_hanging(self): + """ + :avocado: tags=3Dcolo + :avocado: tags=3Darch:x86_64 + """ + self.log.info("Testing with peer qemu hanging") + self._test_colo(hang_qemu=3DTrue) -- 2.20.1 From nobody Fri May 3 21:27:33 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1574358872; cv=none; d=zoho.com; s=zohoarc; b=Sd5hWLEanCUBz1/gAaRo5bB5fdqkXDn1dC7LHCPDdOjVxlmQ9zsynR/osvQbwRvy/BwCDDjDBM3rijFexwMQPJJhAtRFQtfm6nnqAN4szt+GnKSPdmt01kMQt4hzKMgSYO7pSgb/LH6UGywhnJNJz0LsyaIj6xbK6wR4n1uJsJM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1574358872; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=827P9uVe6YhRWzCuWJVs9esq/yZNcNZoevzRX72rGsM=; b=O09bFPaMefZ2XI69rqDCVR5T944as7Ogow7jJU4U8a/egjugX1xToEDmmkGw0mOOOkQbB2ohtPAO2WSbSIw3GPgyF8cKXLxxYuUSw2h3F+6AF6hJRB+TMx2/5zBkFzBhPxwHkcM6dPR5iMBXq8dQ/nxoB3MV3tOe2HqRYA6dCxk= ARC-Authentication-Results: i=1; mx.zoho.com; dkim=fail; spf=pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1574358872924121.12415442541703; Thu, 21 Nov 2019 09:54:32 -0800 (PST) Received: from localhost ([::1]:43284 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iXqey-00026h-OG for importer@patchew.org; Thu, 21 Nov 2019 12:54:29 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:42986) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iXqap-0000HW-ME for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:12 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iXqao-0007GW-Mb for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:11 -0500 Received: from mout.web.de ([212.227.17.11]:52839) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1iXqao-0007EP-Da for qemu-devel@nongnu.org; Thu, 21 Nov 2019 12:50:10 -0500 Received: from luklap ([89.247.255.160]) by smtp.web.de (mrweb101 [213.165.67.124]) with ESMTPSA (Nemesis) id 0MMEmv-1iUpNP2M3R-0082Fl; Thu, 21 Nov 2019 18:49:46 +0100 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=web.de; s=dbaedf251592; t=1574358586; bh=827P9uVe6YhRWzCuWJVs9esq/yZNcNZoevzRX72rGsM=; h=X-UI-Sender-Class:Date:From:To:Cc:Subject:In-Reply-To:References; b=NODxjq/i393jMDSdIDVnlAXzuct3wPjub5XxwFPknAK6MTi694px4693v/oIcOZh2 2LxTQIGUwXe9nCVOIulu1af0HAYuZDkPKg/PIsKYDuc90E4L+l2pwepVVL6stTQbj6 //oR3iQwV1ryflZSMDUc7k0djhdShksy426fQoys= X-UI-Sender-Class: c548c8c5-30a9-4db5-a2e7-cb6cb037b8f9 Date: Thu, 21 Nov 2019 18:49:42 +0100 From: Lukas Straub To: qemu-devel Subject: [PATCH 4/4] MAINTAINERS: Add myself as maintainer for COLO resource agent Message-ID: <56f06c61bfea6f36bbf190bcc6db8f0c3245e6c4.1574356137.git.lukasstraub2@web.de> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Provags-ID: V03:K1:sHYHMKOSI21+Wr7kHh+QpkLudZo14bWDu3tKwV0QmHcyC6NEGgP WwxpCoCEVZS3Thy+tgJ8SeNMTo5HWwEXk9LcJoRxogB/PWlScWMemARtNz1/JtJx36+Awie oyPLd6t0G8BF/Q4Hc3OTa4F3BILDab6C5B3HYQ0z13+6bdxkXI9dgw+314U1fsIVwaiV4gL cILLPYNx1dGW9ituJTafA== X-UI-Out-Filterresults: notjunk:1;V03:K0:uzQ+apF56xU=:JaHNpqWl9XQJ2zFx5ZmYQS sgc5kd8rjHm3kx86y3/mEzeUlbZRvWJiOLtacNsGAgztmYVRdi39E0lEsmg1Wm/Q2xtX4I8gi jIa3Gr4GeyfLgxi85mz5HDJiWcNXaWPecG9lX4OXAsnJ7fCcUtgFi3mXU5KEhBaJ8BjKbpx8g TqoTGEdJ0lbDgU+OpuEkZsEWP+MRUaTsC/VvlInl+qaSzt7hfLkn0yiz0QN1LIliYVP4N5juq LAV2pdi2UmDtcfHPxf0Z+rqQf5Atu/RkdGQk0jkDvJRTTV2zjwyhdqYxgWpOoiRSdw4HdsXD3 PJrBn3vVVx7hVl0VCx25DjkCF9R10N9sKOXfPJdxw8v3oqtZ8uStmB4N8vxZzfodOGBJ4cdL3 0y5gQlnCCvJsdzG6nA0UtoIhzX6UzRyATjpoLn4LiBaAnCyXfLdZtO4mScm0eTBoCPWBfCTYQ uEMKiKOYwGpX3Vd0bPZOIO7BWuzXWyrRSSM+djcW6YLtyQPZ4tlUUJSxeFeD958u6JwtHlxOw Ppm00kk+KouPGPqohvkdALOqtmsar7DXpsHZ/pZVjiz5+Sinyy8x/zynPCCN3kwcNMSSDT0o3 p7OYpEf0rbLZU6/gsvy1T4AO60QpidPT7SbGHKbuKs8Zhn/29gUZ/EAWujn2QCKXDvWmUQkeR Fc/5fS0LOzDkDX9U7P3//bjR+lsP9cIzUdOKkess5etr1nUybWfA5kISBKcE6GwdUiSjSbtNU 32G29DuImnm69hkb7SzaYNcrI/qoM5EEWNcKLxki78QvpCgZtWNvNuWQIAXavFhAemClvLPBA cnO82Bx4a/arOqqzu8AiMrcIyKefB8wWY0PMqKipAX+S67rSGLud22UwZ9zvLJ9UbUvABZjJo 5a1D6C+DiqKWhQhtrggpKIPDL2bJCRgOao1ph1skwjupsVVhBeR7n/QVARZrJOpp4X4d9dMuP XXi/iy3d2GkUAjKHVTiu2QS/KcNdtDWRW7Y6Y0H6ql+H4g9R66r0lUfrN0f0Pbg41cANF1DqP vtoxRqUbGnJ5NhS69kv8K7te95Luah4IKFXwUAqcqhctxyex2SgpP8wo+cPKYmNTptpgwQ2Bn klxavovVZXGuR2E9DJMLaHDTLrV/m1vyPknhZcTGHuIU4ggl/V8bR7oy5SAH0LiE65Nok25RW HUsnMEj8/dpKX1+b0opZb4+SFuh5IJcz3s2JrohVZKCyievfkgEF788oUb3ScldmqpcUCtWfv zeG9I90Ctt4TQYBEr+nvmMaoKsCxi47pan3qy4DB8KebIC8+Q9zhZUSZaaIk= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 212.227.17.11 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: "Zhang, Chen" , Jason Wang , Alberto Garcia , "Dr. David Alan Gilbert" Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) Content-Type: text/plain; charset="utf-8" While I'm not going to have much time for this, I'll still try to test and review patches. Signed-off-by: Lukas Straub --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index d6de200453..aad8356149 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2239,6 +2239,12 @@ F: net/colo* F: net/filter-rewriter.c F: net/filter-mirror.c +COLO resource agent and testing +M: Lukas Straub +S: Odd fixes +F: scripts/colo-resource-agent/* +F: tests/acceptance/colo.py + Record/replay M: Pavel Dovgalyuk R: Paolo Bonzini -- 2.20.1