[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH 4 of 7 v2] blktap3/tapback: Introduce back-end XenStore path handler
This patch introduces the handler executed when the back-end XenStore path gets modified. A back-end XenStore path is modified as a result of a device creation/removal request. The device is created/removed depending on whether its path exists/does not exist in XenStore. Creating a device comprises creating the in-memory representation of it and adding it to the list of devices, locating the tapdisk designated to serve this VBD, and setting a XenStore watch to the front-end path of the newly-created device. Deleting a device comprises removing that XenStore watch and deallocating its in-memory representation. The main issues in this patch are the following: * Function tapback_backend_handle_backend_watch calls tapback_backend_scan even when we know in which domain a device changed, wouldn't it make sense from a performance perspective to only probe the devices of that domain instead of probing ALL devices of ALL domains? * Is there a race condition between the tapback daemon and the toolstack regarding partially brought up devices? I'm trying to figure out what the "serial" thing does (check functions tapback_backend_create_device and tapback_backend_probe_device) but I don't get it. Even if there is such a race condition, I'm not convinced that the way that "serial" thing is implemented fixes it. diff -r a9beb303b541 -r 8094db37a249 tools/blktap3/tapback/backend.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/blktap3/tapback/backend.c Fri Jan 04 12:09:05 2013 +0000 @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2012 Citrix Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * This file contains the handler executed when the back-end XenStore path gets + * modified. + */ + +#include "tapback.h" +#include "xenstore.h" +#include "tap-ctl-info.h" + +/** + * Removes the XenStore watch from the front-end. + * + * @param device the VBD whose front-end XenStore path should stop being + * watched + */ +static void +tapback_device_unwatch_frontend_state(vbd_t * const device) +{ + assert(device); + + if (device->frontend_state_path) + xs_unwatch(blktap3_daemon.xs, device->frontend_state_path, + BLKTAP3_FRONTEND_TOKEN); + + free(device->frontend_state_path); + device->frontend_state_path = NULL; +} + +/** + * Destroys and deallocates the back-end part of a VBD. + * + * @param device the VBD to destroy + */ +static void +tapback_backend_destroy_device(vbd_t * const device) +{ + assert(device); + + TAILQ_REMOVE(&blktap3_daemon.devices, device, backend_entry); + + tapback_device_unwatch_frontend_state(device); + + if (device->frontend_path) { + free(device->frontend_path); + device->frontend_path = NULL; + } + + if (device->name) { + free(device->name); + device->name = NULL; + } + + /* + * XXX device->bdev is expected to have been freed + */ + + free(device); +} + +/** + * Retrieves the tapdisk designated to serve this device, storing this + * information in the supplied VBD handle. + * + * @param bdev the VBD handle for whose tapdisk handle should be retrieved + * @returns 0 on success, an error code otherwise + * + * FIXME How does this thing work since bdev->dev is never initialised? + * Probably because they're both initialised to 0 and we use one tapdisk per + * VBD? + * + * XXX Only called by blkback_probe. + */ +static inline int +blkback_find_tapdisk(vbd_t * const bdev) +{ + struct tqh_tap_list list; + tap_list_t *tap = NULL; + int err = 0; + + assert(bdev); + + if ((err = tap_ctl_list(&list))) { + WARN("error listing tapdisks: %s\n", strerror(err)); + return err; + } + + err = ESRCH; + if (!TAILQ_EMPTY(&list)) { + tap_list_for_each_entry(tap, &list) + if (tap->minor == minor(bdev->dev)) { + err = 0; + memcpy(&bdev->tap, tap, sizeof(bdev->tap)); + break; + } + tap_ctl_list_free(&list); + } else + WARN("no tapdisks\n"); + + return err; +} + +/** + * Creates a device and adds it to the list of devices. + * Initiates a XenStore watch to the front-end state. + * + * Creating the device implies initializing the handle and retrieving all the + * information of the tapdisk serving this VBD. + * + * @param domid the ID of the domain where the VBD is created + * @param name the name of the device + * @returns 0 on success, an error code otherwise + */ +static inline int +tapback_backend_create_device(const domid_t domid, const char * const name) +{ + vbd_t *device = NULL; + int err = 0; + char *end = NULL; + + assert(name); + + DBG("creating device %d/%s\n", domid, name); + + if (!(device = calloc(1, sizeof(*device)))) { + WARN("error allocating memory\n"); + err = -errno; + goto fail; + } + + /* + * TODO replace with alloc_serial() and check for overflow + */ + device->serial = blktap3_daemon.serial++; + device->domid = domid; + + TAILQ_INSERT_TAIL(&blktap3_daemon.devices, device, backend_entry); + + if (!(device->name = strdup(name))) { + err = -errno; + goto fail; + } + + /* + * Get the front-end path from XenStore. We need this to talk to front-end. + */ + if (!(device->frontend_path = tapback_device_read(device, "frontend"))) { + err = -errno; + goto fail; + } + + /* + * Write to XenStore tapback-serial. + */ + if ((err = tapback_device_printf(device, BLKTAP3_SERIAL, false, "%lld", + device->serial))) + goto fail; + + /* + * Get the tapdisk that is serving this virtual block device, along with + * it's parameters. + */ + device->devid = strtoul(name, &end, 0); + if (*end != 0 || end == name) { + err = -EINVAL; + goto fail; + } + + if ((err = blkback_find_tapdisk(device))) { + WARN("error looking for tapdisk: %s", strerror(err)); + goto fail; + } + + /* + * get the VBD parameters from the tapdisk + */ + if ((err = tap_ctl_info(device->tap.pid, device->tap.minor, + &device->sectors, &device->sector_size, &device->info))) { + WARN("error retrieving disk characteristics: %s\n", strerror(err)); + goto fail; + } + + DBG("got %s-%d-%d with tapdev %d/%d\n", BLKTAP3_BACKEND_NAME, + device->domid, device->devid, device->tap.pid, device->tap.minor); + + /* + * Finally, watch the front-end path in XenStore for changes, i.e. + * /local/domain/<domid>/device/vbd/<devname>/state + * After this, we wait for the front-end to switch state to continue with + * the initialisation. + */ + if (!(device->frontend_state_path= mprintf("%s/state", + device->frontend_path))) { + err = -errno; + goto fail; + } + /* + * We use the same token for all front-end watches. We don't have to use a + * unique token for each front-end watch because when a front-end watch + * fires we are given the XenStore path that changed. + */ + if (!xs_watch(blktap3_daemon.xs, device->frontend_state_path, + BLKTAP3_FRONTEND_TOKEN)) { + free(device->frontend_state_path); + err = -errno; + goto fail; + } + + return 0; + +fail: + WARN("error creating device: domid=%d name=%s err=%d (%s)\n", + domid, name, err, strerror(-err)); + if (device) + tapback_backend_destroy_device(device); + + return err; +} + +/** + * Creates (removes) a device depending on the existence (non-existence) of the + * "backend/<backend name>/@domid/@devname" XenStore path. + * + * @param domid the ID of the domain where the VBD is created + * @param devname device name + * @returns 0 on success, an error code otherwise + */ +static int +tapback_backend_probe_device(const domid_t domid, const char * const devname) +{ + int should_exist = 0, create = 0, remove = 0; + vbd_t *device = NULL; + char * s = NULL; + + assert(devname); + + DBG("probe device domid=%d name=%s\n", domid, devname); + + /* + * Ask XenStore if the device _should_ exist. + */ + s = tapback_xs_read(blktap3_daemon.xs, blktap3_daemon.xst, "%s/%d/%s", + BLKTAP3_BACKEND_PATH, domid, devname); + should_exist = s != NULL; + free(s); + + /* + * Search the device list for this specific device. + */ + tapback_backend_find_device(device, + device->domid == domid && !strcmp(device->name, devname)); + + /* + * If XenStore says that the device should exist but it's not in our device + * list, we must create it. If it's the other way round, this is a removal. + * If XenStore says that the device should exist and it's already in our + * device list, we must compare the serials in case the device got removed + * and re-created. + */ + remove = device && !should_exist; + create = !device && should_exist; + + DBG("should exist=%d device=%p remove=%d create=%d\n", + should_exist, device, remove, create); + + /* + * The device exists in our list of device and XenStore says it should + * exist, we need to check if the device has been removed and re-added to + * XenStore. If this has happened the device's serial in XenStore will be + * different from the one in our list (sync with fast remove/re-create + * cycles). + */ + if (device && should_exist) { + char *s = NULL; + long long serial = 0; + + /* + * read the serial + */ + if (!(s = tapback_device_read(device, BLKTAP3_SERIAL))) { + WARN("error reading serial for %s: %s\n", devname, + strerror(errno)); + return -errno; + } + serial = atoll(s); + free(s); + + remove = create = serial != device->serial; + + /* + * TODO Is it possible that neither create nor remove are true? If so, + * why was this function called in the first place? + */ + WARN_ON(!remove && !create, "%d/%s neither created nor removed\n", + domid, devname); + } + + /* + * Remember that remove and create may both be true at the same time, as + * this indicates that the device has been removed and re-created too fast. + * In this case, we too need to remove and re-create the device, + * respectively. + */ + + if (remove) + tapback_backend_destroy_device(device); + + if (create) { + const int err = tapback_backend_create_device(domid, devname); + if (0 != err) { + WARN("error creating device %d on domain %d: %s\n", devname, domid, + strerror(-err)); + return err; + } + } + + return 0; +} + +/** + * Scans XenStore for all blktap3 devices and probes each one of them. + * + * XXX Only called by tapback_backend_handle_backend_watch. + */ +static int +tapback_backend_scan(void) +{ + vbd_t *device = NULL, *next = NULL; + unsigned int i = 0, j = 0, n = 0, m = 0; + char **dir = NULL; + + /* + * scrap all non-existent devices + * FIXME Why do we do this? + * FIXME Is this costly? + */ + + tapback_backend_for_each_device(device, next) + tapback_backend_probe_device(device->domid, device->name); + + /* + * probe the new ones + * + * FIXME We're checking each and every device in each and every domain, + * could there be a performance issue in the presence of many VMs/VBDs? + * (e.g. boot-storm) + */ + if (!(dir = xs_directory(blktap3_daemon.xs, blktap3_daemon.xst, + BLKTAP3_BACKEND_PATH, &n))) + return 0; + + for (i = 0; i < n; i++) { /* for each domain */ + char *path = NULL, **sub = NULL, *end = NULL; + domid_t domid = 0; + + /* + * Get the domain ID. + */ + domid = strtoul(dir[i], &end, 0); + if (*end != 0 || end == dir[i]) + continue; + + /* + * Read the devices of this domain. + */ + path = mprintf("%s/%d", BLKTAP3_BACKEND_PATH, domid); + assert(path != NULL); + sub = xs_directory(blktap3_daemon.xs, blktap3_daemon.xst, path, &m); + free(path); + + /* + * Probe each device. + */ + for (j = 0; j < m; j++) + tapback_backend_probe_device(domid, sub[j]); + + free(sub); + } + + free(dir); + return 0; +} + +int +tapback_backend_handle_backend_watch(char * const path) +{ + char *s = NULL, *end = NULL, *name = NULL; + domid_t domid = 0; + + assert(path); + + s = strtok(path, "/"); + assert(!strcmp(s, XENSTORE_BACKEND)); + if (!(s = strtok(NULL, "/"))) + return tapback_backend_scan(); + + assert(!strcmp(s, BLKTAP3_BACKEND_NAME)); + if (!(s = strtok(NULL, "/"))) + return tapback_backend_scan(); + + domid = strtoul(s, &end, 0); + if (*end != 0 || end == s) { + WARN("invalid domain ID \'%s\'\n", s); + return -EINVAL; + } + + /* + * FIXME since we know which domain changed, why scan the whole thing? + * Add the domid as an optional parameter to tapback_backend_scan. + */ + if (!(name = strtok(NULL, "/"))) + return tapback_backend_scan(); + + /* + * Create or remove a specific device. + */ + return tapback_backend_probe_device(domid, name); +} _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxx http://lists.xen.org/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |