/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include "volume_error.h" #include "volume_dlist.h" #include "volume_output.h" #include "layout_device_cache.h" #include "layout_device_util.h" #include "layout_discovery.h" #include "layout_dlist_util.h" #include "layout_messages.h" #include "layout_request.h" #include "layout_slice.h" #include "layout_svm_util.h" #define _LAYOUT_HSP_C static int layout_explicit_hsp( devconfig_t *hsprequest, dlist_t *devices, devconfig_t **hsp); static int layout_default_hsp( devconfig_t *request, dlist_t *devices, devconfig_t **hsp); static int populate_hsp( devconfig_t *request, devconfig_t *hsp, dlist_t *devices); static int assemble_hsp( devconfig_t *hsp, dlist_t *newspares, dlist_t *devices); static int get_uniquely_sized_slices( dlist_t *devices, dlist_t **unique); static int remove_undersized_slices( dlist_t *unique, dlist_t **avail); static int find_spare_for_component( devconfig_t *component, dlist_t *all_spares, dlist_t *hbas, dlist_t *disks, boolean_t *found); static int choose_spare_for_component( devconfig_t *comp, dlist_t **all_spares, dlist_t **new_spares, dlist_t **avail, dlist_t *used_hbas, dlist_t *used_disks, uint16_t npaths); /* * FUNCTION: layout_hsp(devconfig_t *request, devconfig_t hsprequest, * dlist_t *devices, dlist_t **results) * * INPUT: request - pointer to the toplevel request devconfig_t * hsp - pointer to the optional HSP request devconfig_t * devices - pointer to a list of devices to be served by the HSP * * OUTPUT: results - pointer to a list result devconfig_t, if the HSP * to service the input list of devices needs to be * created or modified, it will be appended to the list. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Main layout driver for HSP, attempts to build/populate a * single HSP to service the list of devices. * * If the input hsprequest is NULL, use the default HSP scheme: * a. use the first HSP in the diskset * b. create an HSP if the diskset has none * * If the hsprequest is not NULL: * a. if the request names an HSP and it already exists, use it * b. if the request names an HSP and it does not exist, create it * c. if the request specifies components, use them */ int layout_hsp( devconfig_t *request, devconfig_t *hsprequest, dlist_t *devices, dlist_t **results) { int error = 0; devconfig_t *hsp = NULL; oprintf(OUTPUT_TERSE, gettext(" ->Layout a %s\n"), devconfig_type_to_str(TYPE_HSP)); if (hsprequest == NULL) { error = layout_default_hsp(request, devices, &hsp); } else { error = layout_explicit_hsp(hsprequest, devices, &hsp); } if (error != 0) { print_debug_failure_msg(devconfig_type_to_str(TYPE_HSP), get_error_string(error)); } else if (hsp != NULL) { if (devconfig_get_components(hsp) == NULL) { /* HSP is usable as it is */ free_devconfig(hsp); hsp = NULL; } else { dlist_t *item = NULL; if ((item = dlist_new_item(hsp)) == NULL) { error = ENOMEM; } else { *results = dlist_append(item, *results, AT_TAIL); print_layout_success_msg(); } } } return (error); } /* * FUNCTION: layout_default_hsp(devconfig_t *request, * dlist_t *devices, devconfig_t **hsp) * * INPUT: request - pointer to the toplevel request devconfig_t * devices - pointer to a list of devices to be served by the HSP * * OUTPUT: hsp - pointer to a devconfig_t to hold the resulting HSP * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Layout driver for default HSP construction. * * a. use the first HSP in the diskset * b. create an HSP if the diskset has none * c. add spares to the HSP to service the list of input devices. */ static int layout_default_hsp( devconfig_t *request, dlist_t *devices, devconfig_t **hsp) { char *dsname = get_request_diskset(); char *hspname = NULL; boolean_t free_hspname = B_FALSE; devconfig_t *default_hsp = NULL; int error = 0; oprintf(OUTPUT_TERSE, gettext(" -->Using default HSP scheme...\n")); if ((error = get_default_hsp_name(request, &hspname)) != 0) { volume_set_error( gettext("error getting HSP name from defaults\n")); return (error); } if (hspname != NULL) { if ((error = hsp_get_by_name(dsname, hspname, &default_hsp)) != 0) { volume_set_error( gettext("error getting default HSP by name\n")); return (error); } } else { /* no default HSP name, get diskset's default HSP */ if ((error = hsp_get_default_for_diskset(dsname, &default_hsp)) != 0) { volume_set_error( gettext("error getting default HSP\n")); return (error); } if (default_hsp == NULL) { /* no default HSP name, no default HSP, make one */ if ((error = get_next_hsp_name(&hspname)) != 0) { volume_set_error( gettext("error making default HSP name\n")); return (error); } free_hspname = B_TRUE; } } if (default_hsp != NULL) { /* Found existing default HSP, copy it */ dlist_t *spares = devconfig_get_components(default_hsp); ((error = devconfig_get_name(default_hsp, &hspname)) != 0) || (error = new_devconfig(hsp, TYPE_HSP)) || (error = devconfig_set_name(*hsp, hspname)); if (error == 0) { devconfig_set_components(*hsp, spares); devconfig_set_components(default_hsp, NULL); oprintf(OUTPUT_TERSE, gettext(" --->Using %s from disk set %s...\n"), hspname, dsname); } else { free_devconfig(*hsp); *hsp = NULL; } } else { /* no existing default HSP, make it */ ((error = new_devconfig(hsp, TYPE_HSP)) != 0) || (error = devconfig_set_name(*hsp, hspname)); if (error == 0) { oprintf(OUTPUT_VERBOSE, gettext(" --->Created %s for disk set %s...\n "), hspname, dsname); } else { free_devconfig(*hsp); *hsp = NULL; } if (free_hspname == B_TRUE) { free(hspname); } } if (error == 0) { error = populate_hsp(request, *hsp, devices); } return (error); } /* * FUNCTION: layout_explicit_hsp(devconfig_t *hsprequest, * dlist_t *devices, devconfig_t **hsp) * * INPUT: hsprequest - pointer to the explicit HSP request devconfig_t * devices - pointer to a list of devices to be served by the HSP * * OUTPUT: hsp - pointer to a HSP devconfig_t to hold resulting HSP * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Layout driver for an explicit HSP request. * * a. if the request names an HSP and it already exists, use it * b. if the request names an HSP and it does not exist, create it * c. if the request specifies components, use them * otherwise, add new spares to handle the input list * of devices. */ static int layout_explicit_hsp( devconfig_t *hsprequest, dlist_t *devices, devconfig_t **hsp) { char *dsname = get_request_diskset(); char *hspname = NULL; dlist_t *rspares = NULL; int error = 0; oprintf(OUTPUT_VERBOSE, gettext(" --->Explicit HSP request...\n")); (void) devconfig_get_name(hsprequest, &hspname); if (hspname != NULL) { (void) hsp_get_by_name(dsname, hspname, hsp); if (*hsp != NULL) { oprintf(OUTPUT_VERBOSE, gettext(" --->Using %s...\n"), hspname); } else { /* named HSP doesn't exist, create it */ ((error = new_devconfig(hsp, TYPE_HSP)) != 0) || (error = devconfig_set_name(*hsp, hspname)); if (error == 0) { oprintf(OUTPUT_VERBOSE, gettext(" --->%s does not exist, " "created...\n"), hspname); } else { free_devconfig(*hsp); *hsp = NULL; } free(hspname); } } if (error == 0) { /* does the hsprequest specify spares? */ rspares = devconfig_get_components(hsprequest); if (rspares != NULL) { /* put requested spares into HSP */ dlist_t *list = NULL; dlist_t *iter = NULL; for (iter = rspares; (iter != NULL) && (error == 0); iter = iter->next) { dlist_t *item = NULL; if ((dlist_new_item(iter->obj)) == NULL) { error = ENOMEM; } else { list = dlist_append(item, list, AT_TAIL); } } if (error == 0) { error = assemble_hsp(*hsp, rspares, devices); } } else { /* select new spares */ error = populate_hsp(hsprequest, *hsp, devices); } } return (error); } /* * FUNCTION: populate_hsp(devconfig_t *request, devconfig_t *hsp, * dlist_t *devices) * * INPUT: request - pointer to a request devconfig_t * hsp - pointer to a HSP devconfig_t * devices - pointer to a list of devices to be served by the HSP * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Processes the input HSP request and add spares sufficient * to service the input list of devices. * * Determine the available HBAs, disks, and slices. * Sort thru the input list of devices and determine * the unique component sizes which need to be spared. * Filter the available slices and remove those that are * too small to serve as spares. * * Iterate each device and its components and see if the * HSP currently has a sufficient spare, if not, try * to select one from the available slices. * * If a spare cannot be found for any device component, * the HSP layout process stops. * * If spares are found for all device components, add * any required new ones to the HSP. */ static int populate_hsp( devconfig_t *request, devconfig_t *hsp, dlist_t *devices) { int error = 0; uint16_t npaths = 0; dlist_t *usable_hbas = NULL; dlist_t *sel_hbas = NULL; dlist_t *disks = NULL; dlist_t *iter = NULL; dlist_t *avail = NULL; /* available slices */ dlist_t *slices = NULL; /* avail slices of sufficient size */ dlist_t *unique = NULL; /* volume slices that need spares */ dlist_t *curspares = NULL; /* current spares in the HSP */ dlist_t *newspares = NULL; /* slices to add to HSP */ dlist_t *allspares = NULL; /* current and new spares */ ((error = get_usable_hbas(&usable_hbas)) != 0) || (error = select_hbas_with_n_disks(request, usable_hbas, 1, &sel_hbas, &disks)) || (error = disks_get_avail_slices(request, disks, &avail)) || (error = get_volume_npaths(request, &npaths)); if (error != 0) { dlist_free_items(sel_hbas, NULL); dlist_free_items(disks, NULL); dlist_free_items(avail, NULL); return (error); } if (disks == NULL || dlist_length(disks) == 0) { /* all disks have been consumed by the devices */ volume_set_error( gettext(" no available disks to populate HSP\n")); dlist_free_items(sel_hbas, NULL); dlist_free_items(avail, NULL); return (-1); } if (avail == NULL || dlist_length(avail) == 0) { /* all slices have been consumed by the devices */ volume_set_error( gettext(" no available slices to populate HSP\n")); dlist_free_items(sel_hbas, NULL); dlist_free_items(disks, NULL); return (-1); } dlist_free_items(sel_hbas, NULL); dlist_free_items(disks, NULL); /* build list of slices needing to be spared */ ((error = get_uniquely_sized_slices(devices, &unique)) != 0) || /* and list of slices of sufficient size to spare for them */ (error = remove_undersized_slices(unique, &avail)); if (error != 0) { dlist_free_items(avail, NULL); dlist_free_items(unique, NULL); dlist_free_items(slices, NULL); return (error); } /* get spares currently in the HSP */ curspares = devconfig_get_components(hsp); /* clone current spares list */ for (iter = curspares; (iter != NULL) && (error == 0); iter = iter->next) { dlist_t *item = dlist_new_item(iter->obj); if (item == NULL) { error = ENOMEM; } else { allspares = dlist_append(item, allspares, AT_TAIL); } } if (error != 0) { dlist_free_items(avail, NULL); dlist_free_items(unique, NULL); dlist_free_items(slices, NULL); dlist_free_items(allspares, NULL); return (error); } /* * examine device component slices and see if the HSP already * has a suitable spare. If not, select the best available * of the same (or larger) size */ for (iter = devices; (iter != NULL) && (error == 0); iter = iter->next) { devconfig_t *device = (devconfig_t *)iter->obj; dlist_t *components = devconfig_get_components(device); dlist_t *hbas = NULL; dlist_t *disks = NULL; dlist_t *iter1; error = get_hbas_and_disks_used_by_volume(device, &hbas, &disks); for (iter1 = components; (iter1 != NULL) && (error == 0); iter1 = iter1->next) { devconfig_t *comp = (devconfig_t *)iter1->obj; boolean_t found = B_FALSE; if ((error = find_spare_for_component( comp, allspares, hbas, disks, &found)) == 0) { if (found != B_TRUE) { error = choose_spare_for_component( comp, &allspares, &newspares, &avail, hbas, disks, npaths); } } } dlist_free_items(disks, NULL); dlist_free_items(hbas, NULL); } if (error == 0) { /* existing spares are no longer needed */ dlist_free_items(curspares, free_devconfig_object); curspares = NULL; error = assemble_hsp(hsp, newspares, devices); } else { dlist_free_items(newspares, free_devconfig_object); newspares = NULL; } dlist_free_items(avail, NULL); dlist_free_items(slices, NULL); dlist_free_items(unique, NULL); dlist_free_items(allspares, NULL); return (error); } /* * FUNCTION: assemble_hsp(devconfig_t *hsp, dlist_t *newspares, * dlist_t *devices) * * INPUT: request - pointer to a HSP devconfig_t * newspare - pointer to a list of new spares for the HSP * devices - pointer to a list of devices to be served by the HSP * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Final assembly of an HSP. Attach new spare components * and associate the HSP with each device in the input list. */ static int assemble_hsp( devconfig_t *hsp, dlist_t *newspares, dlist_t *devices) { dlist_t *iter; char *hspname = NULL; int error = 0; /* add new spares to HSP */ (void) devconfig_set_components(hsp, newspares); (void) devconfig_get_name(hsp, &hspname); /* associate HSP with each of the devices */ for (iter = devices; (iter != NULL) && (error == 0); iter = iter->next) { devconfig_t *dev = iter->obj; devconfig_t *hspcomp = NULL; dlist_t *item = NULL; char *devname = NULL; ((error = devconfig_get_name(dev, &devname)) != 0) || (error = new_devconfig(&hspcomp, TYPE_HSP)) || (error = devconfig_set_name(hspcomp, hspname)); if (error != 0) { free_devconfig(hspcomp); } else if ((item = dlist_new_item(hspcomp)) == NULL) { free_devconfig(hspcomp); error = ENOMEM; } else { dlist_t *comps = devconfig_get_components(dev); comps = dlist_append(comps, item, AT_TAIL); (void) devconfig_set_components(dev, comps); oprintf(OUTPUT_VERBOSE, gettext(" --->volume %s will use HSP %s\n"), devname, hspname); } } return (error); } /* * FUNCTION: get_uniquely_sized_slices(dlist_t *devices, * dlist_t **unique) * * INPUT: devices - pointer to a list of devconfig_t devices * * OUTPUT: unique - pointer to a list of uniquely size slices * from the input list of devices. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Examine each device's slice components and build a list * of uniquely sized slices. */ static int get_uniquely_sized_slices( dlist_t *devices, dlist_t **unique) { int error = 0; dlist_t *iter = NULL; for (iter = devices; (iter != NULL) && (error == 0); iter = iter->next) { dlist_t *iter1; for (iter1 = devconfig_get_components((devconfig_t *)iter->obj); (iter1 != NULL) && (error == 0); iter1 = iter1->next) { devconfig_t *comp = (devconfig_t *)iter1->obj; if (dlist_contains(*unique, comp, compare_devconfig_sizes) != B_TRUE) { dlist_t *item = NULL; if ((item = dlist_new_item(comp)) == NULL) { error = ENOMEM; } else { *unique = dlist_insert_ordered(item, *unique, ASCENDING, compare_devconfig_sizes); } } } } return (error); } /* * FUNCTION: remove_undersized_slices(dlist_t *unique, * dlist_t **avail) * * INPUT: avail - pointer to a list of available slices * unique - pointer to a list of uniquely size slices * * OUTPUT: avail - pointer to an updated list of available slices * that are at least as large as slices in the * unique list. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: filter available slices and remove those that aren't * large enough for the device components which need spares. * * For each uniquely sized slice, find all available slices * that are larger and add them to the filtered list. */ static int remove_undersized_slices( dlist_t *unique, dlist_t **avail) { dlist_t *filtered = NULL; dlist_t *iter = NULL; int error = 0; for (iter = unique; (iter != NULL) && (error == 0); iter = iter->next) { devconfig_t *uslice = (devconfig_t *)iter->obj; uint64_t usize = 0; dlist_t *iter2 = NULL; error = devconfig_get_size(uslice, &usize); for (iter2 = *avail; (iter2 != NULL) && (error == 0); iter2 = iter2->next) { dm_descriptor_t aslice = (uintptr_t)iter2->obj; uint64_t asize = 0; error = slice_get_size(aslice, &asize); if (asize >= usize) { /* this slice is large enough */ dlist_t *item = NULL; if ((item = dlist_new_item((void *)(uintptr_t)aslice)) == NULL) { error = ENOMEM; } else { filtered = dlist_insert_ordered(item, filtered, ASCENDING, compare_slice_sizes); } } } } if (error == 0) { dlist_free_items(*avail, NULL); *avail = filtered; } else { dlist_free_items(filtered, NULL); } return (error); } /* * FUNCTION: find_spare_for_component(devconfig_t *component, * dlist_t *all_spares, dlist_t *hbas, dlist_t *disks, * boolean_t *found) * * INPUT: comp - pointer to a devconfig_t slice compenent that * needs to be spared * all_spares - pointer to a list of spares currently * in the pool or that will be added * hbas - pointer to a list of HBAs the component's * parent device utilizes * disks - pointer to a list of disks the component's * parent device utilizes * * OUTPUT: found - pointer to a boolean_t to hold the result. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Find a spare for the input component. * * Searches the input list of spares to see if one is * sufficient. * * A suffcient spare is one that is large enough to spare * for the input component and not on the same disk as any * of the components in the parent device. * * The optimal spare would be on a different controller/HBA * as the component and any of the components in the parent * device. We settle for sufficient. */ static int find_spare_for_component( devconfig_t *component, dlist_t *all_spares, dlist_t *hbas, dlist_t *disks, boolean_t *found) { dlist_t *iter = NULL; uint64_t csize = 0; int error = 0; *found = B_FALSE; (void) devconfig_get_size(component, &csize); for (iter = all_spares; (iter != NULL) && (*found == B_FALSE) && (error == 0); iter = iter->next) { devconfig_t *spare = (devconfig_t *)iter->obj; char *spname = NULL; uint64_t spsize = 0; if (((error = devconfig_get_name(spare, &spname)) != 0) || ((error = devconfig_get_size(spare, &spsize)) != 0)) { continue; } if (spsize >= csize) { dm_descriptor_t disk = NULL; /* see if spare's disk is independent of the volume */ error = get_disk_for_named_slice(spname, &disk); if ((error == 0) && (dlist_contains(disks, (void *)(uintptr_t)disk, compare_descriptor_names) == B_FALSE)) { *found = B_TRUE; } } } if ((*found == B_TRUE) && (get_max_verbosity() >= OUTPUT_DEBUG)) { char *cname = NULL; (void) devconfig_get_name(component, &cname); oprintf(OUTPUT_DEBUG, gettext(" found existing spare for: %s (%llu)\n"), cname, csize); } return (error); } /* * FUNCTION: choose_spare_for_component(devconfig_t *component, * dlist_t *all_spares, dlist_t **new_spares, * dlist_t avail, uint16_t npaths, dlist_t *used_hbas, * dlist_t *used_disks) * * INPUT: comp - pointer to a devconfig_t slice compenent that * needs to be spared * all_spares - pointer to a list of spares currently * in the pool and those to be added * new_spares - pointer to a list of spares that need to * be added to the pool * avail - list of available slices * npaths - required number of paths for the spare * used_hbas - list of HBAs used by the component's parent * used_disks - list of disks used by the component's parent * * OUTPUT: all_spares - the possibly updated list of all spares * new_spares - the possibly updated list of spares which * need to be added to the pool. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Find a new spare for the input component. * * Select a spare from the available slice list and add * it to the new_spares list. * * The spare slice chosen should be on a unique HBA and * disk relative to the input lists of used HBAs and disks * and any spares in the pool. */ static int choose_spare_for_component( devconfig_t *component, dlist_t **all_spares, dlist_t **new_spares, dlist_t **avail, dlist_t *used_hbas, dlist_t *used_disks, uint16_t npaths) { devconfig_t *spare = NULL; uint64_t csize = 0; int error = 0; (void) devconfig_get_size(component, &csize); if (get_max_verbosity() >= OUTPUT_DEBUG) { char *cname = NULL; (void) devconfig_get_name(component, &cname); oprintf(OUTPUT_DEBUG, gettext(" select new spare for: %s (%llu)\n"), cname, csize); } /* * find a spare for the input component. * select the best one from the available list that * is on a unique disk. */ /* * 1st B_TRUE: require a different disk than those used by * all spares and devices * 2nd B_TRUE: requested size is the minimum acceptable * 1st B_FALSE: do not add an extra cylinder when resizing slice, * this is only necessary for Stripe components whose * sizes get rounded down to an interlace multiple and * then down to a cylinder boundary. */ error = choose_slice(csize, npaths, *avail, *all_spares, used_hbas, used_disks, B_TRUE, B_TRUE, B_FALSE, &spare); if ((error == 0) && (spare == NULL)) { /* can't find one on a unique disk, try again on any disk */ /* BEGIN CSTYLED */ /* * 1st B_FALSE: don't require a different disk than those used * by all spares and devices * 2nd B_TRUE: requested size is still the minimum acceptable * 2nd B_FALSE: do not add an extra cylinder when resizing slice * this is only necessary for Stripe components whose * sizes get rounded down to an interlace multiple and * then down to a cylinder boundary. */ /* END CSTYLED */ error = choose_slice( csize, npaths, *avail, *all_spares, used_hbas, used_disks, B_FALSE, B_TRUE, B_FALSE, &spare); } if ((error == 0) && (spare != NULL)) { dlist_t *rmvd = NULL; dlist_t *item = NULL; char *spname = NULL; if ((item = dlist_new_item(spare)) == NULL) { error = ENOMEM; } else { /* add spare to the all spares list */ *all_spares = dlist_append(item, *all_spares, AT_HEAD); if ((item = dlist_new_item(spare)) == NULL) { error = ENOMEM; } else { /* add spare to the new spares list */ *new_spares = dlist_insert_ordered( item, *new_spares, ASCENDING, compare_devconfig_sizes); /* remove it from the available list */ *avail = dlist_remove_equivalent_item(*avail, spare, compare_devconfig_and_descriptor_names, &rmvd); if (rmvd != NULL) { free(rmvd); } /* add the spare to the used slice list */ error = devconfig_get_name(spare, &spname); if (error == 0) { error = add_used_slice_by_name(spname); } } } } else { /* no spare, give up on layout */ oprintf(OUTPUT_TERSE, gettext(" <---Failed: insufficient suitable spares\n")); volume_set_error( gettext("failed to find sufficient spares for HSP\n")); error = -1; } return (error); }