提交 475fe457 编写于 作者: P Peter Maydell

Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2018-03-13-v2' into staging

nbd patches for 2018-03-13

- Eric Blake: iotests: Fix stuck NBD process on 33
- Vladimir Sementsov-Ogievskiy: 0/5 nbd server fixing and refactoring before BLOCK_STATUS
- Eric Blake: nbd/server: Honor FUA request on NBD_CMD_TRIM
- Stefan Hajnoczi: 0/2 block: fix nbd-server-stop crash after blockdev-snapshot-sync
- Vladimir Sementsov-Ogievskiy: nbd block status base:allocation

# gpg: Signature made Tue 13 Mar 2018 20:48:37 GMT
# gpg:                using RSA key A7A16B4A2527436A
# gpg: Good signature from "Eric Blake <eblake@redhat.com>"
# gpg:                 aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>"
# gpg:                 aka "[jpeg image of size 6874]"
# Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2  F3AA A7A1 6B4A 2527 436A

* remotes/ericb/tags/pull-nbd-2018-03-13-v2:
  iotests: new test 209 for NBD BLOCK_STATUS
  iotests: add file_path helper
  iotests.py: tiny refactor: move system imports up
  nbd: BLOCK_STATUS for standard get_block_status function: client part
  block/nbd-client: save first fatal error in nbd_iter_error
  nbd: BLOCK_STATUS for standard get_block_status function: server part
  nbd/server: add nbd_read_opt_name helper
  nbd/server: add nbd_opt_invalid helper
  iotests: add 208 nbd-server + blockdev-snapshot-sync test case
  block: let blk_add/remove_aio_context_notifier() tolerate BDS changes
  nbd/server: Honor FUA request on NBD_CMD_TRIM
  nbd/server: refactor nbd_trip: split out nbd_handle_request
  nbd/server: refactor nbd_trip: cmd_read and generic reply
  nbd/server: fix: check client->closing before sending reply
  nbd/server: fix sparse read
  nbd/server: move nbd_co_send_structured_error up
  iotests: Fix stuck NBD process on 33
Signed-off-by: NPeter Maydell <peter.maydell@linaro.org>
......@@ -31,6 +31,13 @@
static AioContext *blk_aiocb_get_aio_context(BlockAIOCB *acb);
typedef struct BlockBackendAioNotifier {
void (*attached_aio_context)(AioContext *new_context, void *opaque);
void (*detach_aio_context)(void *opaque);
void *opaque;
QLIST_ENTRY(BlockBackendAioNotifier) list;
} BlockBackendAioNotifier;
struct BlockBackend {
char *name;
int refcnt;
......@@ -69,6 +76,7 @@ struct BlockBackend {
bool allow_write_beyond_eof;
NotifierList remove_bs_notifiers, insert_bs_notifiers;
QLIST_HEAD(, BlockBackendAioNotifier) aio_notifiers;
int quiesce_counter;
VMChangeStateEntry *vmsh;
......@@ -247,6 +255,36 @@ static int blk_root_inactivate(BdrvChild *child)
return 0;
}
static void blk_root_attach(BdrvChild *child)
{
BlockBackend *blk = child->opaque;
BlockBackendAioNotifier *notifier;
trace_blk_root_attach(child, blk, child->bs);
QLIST_FOREACH(notifier, &blk->aio_notifiers, list) {
bdrv_add_aio_context_notifier(child->bs,
notifier->attached_aio_context,
notifier->detach_aio_context,
notifier->opaque);
}
}
static void blk_root_detach(BdrvChild *child)
{
BlockBackend *blk = child->opaque;
BlockBackendAioNotifier *notifier;
trace_blk_root_detach(child, blk, child->bs);
QLIST_FOREACH(notifier, &blk->aio_notifiers, list) {
bdrv_remove_aio_context_notifier(child->bs,
notifier->attached_aio_context,
notifier->detach_aio_context,
notifier->opaque);
}
}
static const BdrvChildRole child_root = {
.inherit_options = blk_root_inherit_options,
......@@ -260,6 +298,9 @@ static const BdrvChildRole child_root = {
.activate = blk_root_activate,
.inactivate = blk_root_inactivate,
.attach = blk_root_attach,
.detach = blk_root_detach,
};
/*
......@@ -287,6 +328,7 @@ BlockBackend *blk_new(uint64_t perm, uint64_t shared_perm)
notifier_list_init(&blk->remove_bs_notifiers);
notifier_list_init(&blk->insert_bs_notifiers);
QLIST_INIT(&blk->aio_notifiers);
QTAILQ_INSERT_TAIL(&block_backends, blk, link);
return blk;
......@@ -364,6 +406,7 @@ static void blk_delete(BlockBackend *blk)
}
assert(QLIST_EMPTY(&blk->remove_bs_notifiers.notifiers));
assert(QLIST_EMPTY(&blk->insert_bs_notifiers.notifiers));
assert(QLIST_EMPTY(&blk->aio_notifiers));
QTAILQ_REMOVE(&block_backends, blk, link);
drive_info_del(blk->legacy_dinfo);
block_acct_cleanup(&blk->stats);
......@@ -1857,8 +1900,15 @@ void blk_add_aio_context_notifier(BlockBackend *blk,
void (*attached_aio_context)(AioContext *new_context, void *opaque),
void (*detach_aio_context)(void *opaque), void *opaque)
{
BlockBackendAioNotifier *notifier;
BlockDriverState *bs = blk_bs(blk);
notifier = g_new(BlockBackendAioNotifier, 1);
notifier->attached_aio_context = attached_aio_context;
notifier->detach_aio_context = detach_aio_context;
notifier->opaque = opaque;
QLIST_INSERT_HEAD(&blk->aio_notifiers, notifier, list);
if (bs) {
bdrv_add_aio_context_notifier(bs, attached_aio_context,
detach_aio_context, opaque);
......@@ -1871,12 +1921,25 @@ void blk_remove_aio_context_notifier(BlockBackend *blk,
void (*detach_aio_context)(void *),
void *opaque)
{
BlockBackendAioNotifier *notifier;
BlockDriverState *bs = blk_bs(blk);
if (bs) {
bdrv_remove_aio_context_notifier(bs, attached_aio_context,
detach_aio_context, opaque);
}
QLIST_FOREACH(notifier, &blk->aio_notifiers, list) {
if (notifier->attached_aio_context == attached_aio_context &&
notifier->detach_aio_context == detach_aio_context &&
notifier->opaque == opaque) {
QLIST_REMOVE(notifier, list);
g_free(notifier);
return;
}
}
abort();
}
void blk_add_remove_bs_notifier(BlockBackend *blk, Notifier *notify)
......
......@@ -228,6 +228,48 @@ static int nbd_parse_offset_hole_payload(NBDStructuredReplyChunk *chunk,
return 0;
}
/* nbd_parse_blockstatus_payload
* support only one extent in reply and only for
* base:allocation context
*/
static int nbd_parse_blockstatus_payload(NBDClientSession *client,
NBDStructuredReplyChunk *chunk,
uint8_t *payload, uint64_t orig_length,
NBDExtent *extent, Error **errp)
{
uint32_t context_id;
if (chunk->length != sizeof(context_id) + sizeof(extent)) {
error_setg(errp, "Protocol error: invalid payload for "
"NBD_REPLY_TYPE_BLOCK_STATUS");
return -EINVAL;
}
context_id = payload_advance32(&payload);
if (client->info.meta_base_allocation_id != context_id) {
error_setg(errp, "Protocol error: unexpected context id %d for "
"NBD_REPLY_TYPE_BLOCK_STATUS, when negotiated context "
"id is %d", context_id,
client->info.meta_base_allocation_id);
return -EINVAL;
}
extent->length = payload_advance32(&payload);
extent->flags = payload_advance32(&payload);
if (extent->length == 0 ||
(client->info.min_block && !QEMU_IS_ALIGNED(extent->length,
client->info.min_block)) ||
extent->length > orig_length)
{
error_setg(errp, "Protocol error: server sent status chunk with "
"invalid length");
return -EINVAL;
}
return 0;
}
/* nbd_parse_error_payload
* on success @errp contains message describing nbd error reply
*/
......@@ -481,6 +523,7 @@ static coroutine_fn int nbd_co_receive_one_chunk(
typedef struct NBDReplyChunkIter {
int ret;
bool fatal;
Error *err;
bool done, only_structured;
} NBDReplyChunkIter;
......@@ -490,11 +533,12 @@ static void nbd_iter_error(NBDReplyChunkIter *iter, bool fatal,
{
assert(ret < 0);
if (fatal || iter->ret == 0) {
if ((fatal && !iter->fatal) || iter->ret == 0) {
if (iter->ret != 0) {
error_free(iter->err);
iter->err = NULL;
}
iter->fatal = fatal;
iter->ret = ret;
error_propagate(&iter->err, *local_err);
} else {
......@@ -640,6 +684,68 @@ static int nbd_co_receive_cmdread_reply(NBDClientSession *s, uint64_t handle,
return iter.ret;
}
static int nbd_co_receive_blockstatus_reply(NBDClientSession *s,
uint64_t handle, uint64_t length,
NBDExtent *extent, Error **errp)
{
NBDReplyChunkIter iter;
NBDReply reply;
void *payload = NULL;
Error *local_err = NULL;
bool received = false;
assert(!extent->length);
NBD_FOREACH_REPLY_CHUNK(s, iter, handle, s->info.structured_reply,
NULL, &reply, &payload)
{
int ret;
NBDStructuredReplyChunk *chunk = &reply.structured;
assert(nbd_reply_is_structured(&reply));
switch (chunk->type) {
case NBD_REPLY_TYPE_BLOCK_STATUS:
if (received) {
s->quit = true;
error_setg(&local_err, "Several BLOCK_STATUS chunks in reply");
nbd_iter_error(&iter, true, -EINVAL, &local_err);
}
received = true;
ret = nbd_parse_blockstatus_payload(s, &reply.structured,
payload, length, extent,
&local_err);
if (ret < 0) {
s->quit = true;
nbd_iter_error(&iter, true, ret, &local_err);
}
break;
default:
if (!nbd_reply_type_is_error(chunk->type)) {
s->quit = true;
error_setg(&local_err,
"Unexpected reply type: %d (%s) "
"for CMD_BLOCK_STATUS",
chunk->type, nbd_reply_type_lookup(chunk->type));
nbd_iter_error(&iter, true, -EINVAL, &local_err);
}
}
g_free(payload);
payload = NULL;
}
if (!extent->length && !iter.err) {
error_setg(&iter.err,
"Server did not reply with any status extents");
if (!iter.ret) {
iter.ret = -EIO;
}
}
error_propagate(errp, iter.err);
return iter.ret;
}
static int nbd_co_request(BlockDriverState *bs, NBDRequest *request,
QEMUIOVector *write_qiov)
{
......@@ -782,6 +888,51 @@ int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes)
return nbd_co_request(bs, &request, NULL);
}
int coroutine_fn nbd_client_co_block_status(BlockDriverState *bs,
bool want_zero,
int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map,
BlockDriverState **file)
{
int64_t ret;
NBDExtent extent = { 0 };
NBDClientSession *client = nbd_get_client_session(bs);
Error *local_err = NULL;
NBDRequest request = {
.type = NBD_CMD_BLOCK_STATUS,
.from = offset,
.len = MIN(MIN_NON_ZERO(QEMU_ALIGN_DOWN(INT_MAX,
bs->bl.request_alignment),
client->info.max_block), bytes),
.flags = NBD_CMD_FLAG_REQ_ONE,
};
if (!client->info.base_allocation) {
*pnum = bytes;
return BDRV_BLOCK_DATA;
}
ret = nbd_co_send_request(bs, &request, NULL);
if (ret < 0) {
return ret;
}
ret = nbd_co_receive_blockstatus_reply(client, request.handle, bytes,
&extent, &local_err);
if (local_err) {
error_report_err(local_err);
}
if (ret < 0) {
return ret;
}
assert(extent.length);
*pnum = extent.length;
return (extent.flags & NBD_STATE_HOLE ? 0 : BDRV_BLOCK_DATA) |
(extent.flags & NBD_STATE_ZERO ? BDRV_BLOCK_ZERO : 0);
}
void nbd_client_detach_aio_context(BlockDriverState *bs)
{
NBDClientSession *client = nbd_get_client_session(bs);
......@@ -826,6 +977,7 @@ int nbd_client_init(BlockDriverState *bs,
client->info.request_sizes = true;
client->info.structured_reply = true;
client->info.base_allocation = true;
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
tlscreds, hostname,
&client->ioc, &client->info, errp);
......
......@@ -61,4 +61,10 @@ void nbd_client_detach_aio_context(BlockDriverState *bs);
void nbd_client_attach_aio_context(BlockDriverState *bs,
AioContext *new_context);
int coroutine_fn nbd_client_co_block_status(BlockDriverState *bs,
bool want_zero,
int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map,
BlockDriverState **file);
#endif /* NBD_CLIENT_H */
......@@ -585,6 +585,7 @@ static BlockDriver bdrv_nbd = {
.bdrv_detach_aio_context = nbd_detach_aio_context,
.bdrv_attach_aio_context = nbd_attach_aio_context,
.bdrv_refresh_filename = nbd_refresh_filename,
.bdrv_co_block_status = nbd_client_co_block_status,
};
static BlockDriver bdrv_nbd_tcp = {
......@@ -604,6 +605,7 @@ static BlockDriver bdrv_nbd_tcp = {
.bdrv_detach_aio_context = nbd_detach_aio_context,
.bdrv_attach_aio_context = nbd_attach_aio_context,
.bdrv_refresh_filename = nbd_refresh_filename,
.bdrv_co_block_status = nbd_client_co_block_status,
};
static BlockDriver bdrv_nbd_unix = {
......@@ -623,6 +625,7 @@ static BlockDriver bdrv_nbd_unix = {
.bdrv_detach_aio_context = nbd_detach_aio_context,
.bdrv_attach_aio_context = nbd_attach_aio_context,
.bdrv_refresh_filename = nbd_refresh_filename,
.bdrv_co_block_status = nbd_client_co_block_status,
};
static void bdrv_nbd_init(void)
......
......@@ -7,6 +7,8 @@ bdrv_lock_medium(void *bs, bool locked) "bs %p locked %d"
# block/block-backend.c
blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x"
blk_co_pwritev(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x"
blk_root_attach(void *child, void *blk, void *bs) "child %p blk %p bs %p"
blk_root_detach(void *child, void *blk, void *bs) "child %p blk %p bs %p"
# block/io.c
bdrv_co_preadv(void *bs, int64_t offset, int64_t nbytes, unsigned int flags) "bs %p offset %"PRId64" nbytes %"PRId64" flags 0x%x"
......
......@@ -260,6 +260,7 @@ struct NBDExportInfo {
/* In-out fields, set by client before nbd_receive_negotiate() and
* updated by server results during nbd_receive_negotiate() */
bool structured_reply;
bool base_allocation; /* base:allocation context for NBD_CMD_BLOCK_STATUS */
/* Set by server results during nbd_receive_negotiate() */
uint64_t size;
......@@ -267,6 +268,8 @@ struct NBDExportInfo {
uint32_t min_block;
uint32_t opt_block;
uint32_t max_block;
uint32_t meta_base_allocation_id;
};
typedef struct NBDExportInfo NBDExportInfo;
......
......@@ -595,6 +595,111 @@ static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,
return QIO_CHANNEL(tioc);
}
/* nbd_negotiate_simple_meta_context:
* Set one meta context. Simple means that reply must contain zero (not
* negotiated) or one (negotiated) contexts. More contexts would be considered
* as a protocol error. It's also implied that meta-data query equals queried
* context name, so, if server replies with something different then @context,
* it considered as error too.
* return 1 for successful negotiation, context_id is set
* 0 if operation is unsupported,
* -1 with errp set for any other error
*/
static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
const char *export,
const char *context,
uint32_t *context_id,
Error **errp)
{
int ret;
NBDOptionReply reply;
uint32_t received_id;
bool received;
uint32_t export_len = strlen(export);
uint32_t context_len = strlen(context);
uint32_t data_len = sizeof(export_len) + export_len +
sizeof(uint32_t) + /* number of queries */
sizeof(context_len) + context_len;
char *data = g_malloc(data_len);
char *p = data;
stl_be_p(p, export_len);
memcpy(p += sizeof(export_len), export, export_len);
stl_be_p(p += export_len, 1);
stl_be_p(p += sizeof(uint32_t), context_len);
memcpy(p += sizeof(context_len), context, context_len);
ret = nbd_send_option_request(ioc, NBD_OPT_SET_META_CONTEXT, data_len, data,
errp);
g_free(data);
if (ret < 0) {
return ret;
}
if (nbd_receive_option_reply(ioc, NBD_OPT_SET_META_CONTEXT, &reply,
errp) < 0)
{
return -1;
}
ret = nbd_handle_reply_err(ioc, &reply, errp);
if (ret <= 0) {
return ret;
}
if (reply.type == NBD_REP_META_CONTEXT) {
char *name;
size_t len;
if (nbd_read(ioc, &received_id, sizeof(received_id), errp) < 0) {
return -1;
}
be32_to_cpus(&received_id);
len = reply.length - sizeof(received_id);
name = g_malloc(len + 1);
if (nbd_read(ioc, name, len, errp) < 0) {
g_free(name);
return -1;
}
name[len] = '\0';
if (strcmp(context, name)) {
error_setg(errp, "Failed to negotiate meta context '%s', server "
"answered with different context '%s'", context,
name);
g_free(name);
return -1;
}
g_free(name);
received = true;
/* receive NBD_REP_ACK */
if (nbd_receive_option_reply(ioc, NBD_OPT_SET_META_CONTEXT, &reply,
errp) < 0)
{
return -1;
}
ret = nbd_handle_reply_err(ioc, &reply, errp);
if (ret <= 0) {
return ret;
}
}
if (reply.type != NBD_REP_ACK) {
error_setg(errp, "Unexpected reply type %" PRIx32 " expected %x",
reply.type, NBD_REP_ACK);
return -1;
}
if (received) {
*context_id = received_id;
return 1;
}
return 0;
}
int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
QCryptoTLSCreds *tlscreds, const char *hostname,
......@@ -606,10 +711,12 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
int rc;
bool zeroes = true;
bool structured_reply = info->structured_reply;
bool base_allocation = info->base_allocation;
trace_nbd_receive_negotiate(tlscreds, hostname ? hostname : "<null>");
info->structured_reply = false;
info->base_allocation = false;
rc = -EINVAL;
if (outioc) {
......@@ -700,6 +807,16 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
info->structured_reply = result == 1;
}
if (info->structured_reply && base_allocation) {
result = nbd_negotiate_simple_meta_context(
ioc, name, "base:allocation",
&info->meta_base_allocation_id, errp);
if (result < 0) {
goto fail;
}
info->base_allocation = result == 1;
}
/* Try NBD_OPT_GO first - if it works, we are done (it
* also gives us a good message if the server requires
* TLS). If it is not available, fall back to
......
此差异已折叠。
......@@ -105,6 +105,7 @@ for align in 512 4k; do
done
done
_cleanup_test_img
# Trigger truncate that would shrink qcow2 L1 table, which is done by
# clearing one entry (8 bytes) with bdrv_co_pwrite_zeroes()
......
#!/usr/bin/env python
#
# Copyright (C) 2018 Red Hat, 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, see <http://www.gnu.org/licenses/>.
#
# Creator/Owner: Stefan Hajnoczi <stefanha@redhat.com>
#
# Check that the runtime NBD server does not crash when stopped after
# blockdev-snapshot-sync.
import iotests
with iotests.FilePath('disk.img') as disk_img_path, \
iotests.FilePath('disk-snapshot.img') as disk_snapshot_img_path, \
iotests.FilePath('nbd.sock') as nbd_sock_path, \
iotests.VM() as vm:
img_size = '10M'
iotests.qemu_img_pipe('create', '-f', iotests.imgfmt, disk_img_path, img_size)
iotests.log('Launching VM...')
(vm.add_drive(disk_img_path, 'node-name=drive0-node', interface='none')
.launch())
iotests.log('Starting NBD server...')
iotests.log(vm.qmp('nbd-server-start', addr={
"type": "unix",
"data": {
"path": nbd_sock_path,
}
}))
iotests.log('Adding NBD export...')
iotests.log(vm.qmp('nbd-server-add', device='drive0-node', writable=True))
iotests.log('Creating external snapshot...')
iotests.log(vm.qmp('blockdev-snapshot-sync',
node_name='drive0-node',
snapshot_node_name='drive0-snapshot-node',
snapshot_file=disk_snapshot_img_path))
iotests.log('Stopping NBD server...')
iotests.log(vm.qmp('nbd-server-stop'))
Launching VM...
Starting NBD server...
{u'return': {}}
Adding NBD export...
{u'return': {}}
Creating external snapshot...
{u'return': {}}
Stopping NBD server...
{u'return': {}}
#!/usr/bin/env python
#
# Tests for NBD BLOCK_STATUS extension
#
# Copyright (c) 2018 Virtuozzo International GmbH
#
# 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, see <http://www.gnu.org/licenses/>.
#
import iotests
from iotests import qemu_img_create, qemu_io, qemu_img_verbose, qemu_nbd, \
file_path
iotests.verify_image_format(supported_fmts=['qcow2'])
disk, nbd_sock = file_path('disk', 'nbd-sock')
nbd_uri = 'nbd+unix:///exp?socket=' + nbd_sock
qemu_img_create('-f', iotests.imgfmt, disk, '1M')
qemu_io('-f', iotests.imgfmt, '-c', 'write 0 512K', disk)
qemu_nbd('-k', nbd_sock, '-x', 'exp', '-f', iotests.imgfmt, disk)
qemu_img_verbose('map', '-f', 'raw', '--output=json', nbd_uri)
[{ "start": 0, "length": 524288, "depth": 0, "zero": false, "data": true},
{ "start": 524288, "length": 524288, "depth": 0, "zero": true, "data": false}]
......@@ -204,3 +204,5 @@
205 rw auto quick
206 rw auto
207 rw auto
208 rw auto quick
209 rw auto quick
......@@ -23,12 +23,14 @@ import subprocess
import string
import unittest
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
import qtest
import struct
import json
import signal
import logging
import atexit
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
import qtest
# This will not work if arguments contain spaces but is necessary if we
......@@ -249,6 +251,37 @@ class FilePath(object):
return False
def file_path_remover():
for path in reversed(file_path_remover.paths):
try:
os.remove(path)
except OSError:
pass
def file_path(*names):
''' Another way to get auto-generated filename that cleans itself up.
Use is as simple as:
img_a, img_b = file_path('a.img', 'b.img')
sock = file_path('socket')
'''
if not hasattr(file_path_remover, 'paths'):
file_path_remover.paths = []
atexit.register(file_path_remover)
paths = []
for name in names:
filename = '{0}-{1}'.format(os.getpid(), name)
path = os.path.join(test_dir, filename)
file_path_remover.paths.append(path)
paths.append(path)
return paths[0] if len(paths) == 1 else paths
class VM(qtest.QEMUQtestMachine):
'''A QEMU VM'''
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册