Extend the existing mem-claim test to verify both the legacy XENMEM_claim_pages and the new XEN_DOMCTL_claim_memory hypercalls.
It tests both host-wide claims (NUMA_NO_NODE) and node-specific claims (assuming at least a single NUMA node, node 0 is provided) to ensure the new infrastructure works correctly. It also checks the protection of host- and node-claims against allocations without sufficient, specific claims. Signed-off-by: Bernhard Kaindl <[email protected]> --- tools/tests/mem-claim/test-mem-claim.c | 277 +++++++++++++++++++++++-- 1 file changed, 254 insertions(+), 23 deletions(-) diff --git a/tools/tests/mem-claim/test-mem-claim.c b/tools/tests/mem-claim/test-mem-claim.c index ad038e45d188..a98d3e43ff54 100644 --- a/tools/tests/mem-claim/test-mem-claim.c +++ b/tools/tests/mem-claim/test-mem-claim.c @@ -2,6 +2,7 @@ #include <err.h> #include <errno.h> #include <inttypes.h> +#include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> @@ -20,10 +21,13 @@ static unsigned int nr_failures; #define MB_PAGES(x) (MB(x) / XC_PAGE_SIZE) +#define CLAIM_TEST_ORDER 9 /* 2M */ + static xc_interface *xch; static uint32_t domid = DOMID_INVALID; static xc_physinfo_t physinfo; +static unsigned int claim_test_node; static struct xen_domctl_createdomain create = { .flags = XEN_DOMCTL_CDF_hvm | XEN_DOMCTL_CDF_hap, @@ -38,10 +42,138 @@ static struct xen_domctl_createdomain create = { }, }; -static void run_tests(void) +typedef int (*claim_fn_t)(xc_interface *xch, uint32_t domid, + unsigned long pages); + +/* Wrapper function to test claiming memory using xc_domain_claim_pages. */ +static int wrap_claim_pages(xc_interface *xch, + uint32_t domid, + unsigned long pages) +{ + return xc_domain_claim_pages(xch, domid, pages); +} + +/* Wrapper function to test claiming memory using xc_domain_claim_memory. */ +static int wrap_claim_memory(xc_interface *xch, + uint32_t domid, + unsigned long pages) +{ + memory_claim_t claim[] = { + XEN_NODE_CLAIM_INIT(pages, XEN_DOMCTL_CLAIM_MEMORY_NO_NODE) + }; + + return xc_domain_claim_memory(xch, domid, 1, claim); +} + +/* Wrapper to test claiming memory using xc_domain_claim_memory on a NUMA node */ +static int wrap_claim_memory_node(xc_interface *xch, + uint32_t domid, + unsigned long pages) { int rc; + memory_claim_t claims[UINT8_MAX + 1] = {}; /* + 1 to test overflow check */ + + /* claim with a node that is not present */ + claims[0] = (memory_claim_t)XEN_NODE_CLAIM_INIT(pages, physinfo.nr_nodes); + /* Check the return value of claiming memory on an invalid node */ + rc = xc_domain_claim_memory(xch, domid, 1, claims); + if ( rc != -1 || errno != ENOENT ) + { + fail("Expected claim failure on invalid node to fail with ENOENT\n"); + return rc; + } + /* + * Check the return value of claiming on two nodes (not yet implemented) + * and that the valid claim is rejected when nr_claims > 1. We expect that + * the API will reject the call due exceeding nr_claims before it checks + * the validity of the node(s), so we expect EINVAL rather than ENOENT. + */ + rc = xc_domain_claim_memory(xch, domid, 2, claims); + if ( rc != -1 || errno != EINVAL ) + { + fail("Expected nr_claims == 2 to fail with EINVAL (for now)\n"); + return rc; + + } + /* Likewise check with nr_claims > MAX_UINT8 to test overflow */ + rc = xc_domain_claim_memory(xch, domid, UINT8_MAX + 1, claims); + if ( rc != -1 || errno != EINVAL ) + { + fail("Expected nr_claims = UINT8_MAX + 1 to fail with EINVAL\n"); + return rc; + } + /* Likewise check with a node of MAX_UINT8 + 1 to test overflow */ + claims[0].node = UINT8_MAX + 1; + rc = xc_domain_claim_memory(xch, domid, 1, claims); + if ( rc != -1 || errno != ENOENT ) + { + fail("Expected node == UINT8_MAX + 1 to fail with ENOENT\n"); + return rc; + } + /* Test with pages exceeding INT32_MAX to check overflow */ + claims[0] = (memory_claim_t)XEN_NODE_CLAIM_INIT((unsigned)INT32_MAX + 1, 0); + rc = xc_domain_claim_memory(xch, domid, 1, claims); + if ( rc != -1 || errno != ENOMEM ) + { + fail("Expected ENOMEM with pages > INT32_MAX\n"); + return rc; + } + /* Test with pad not set to zero */ + claims[0] = (memory_claim_t)XEN_NODE_CLAIM_INIT(pages, claim_test_node); + claims[0].pad = 1; + rc = xc_domain_claim_memory(xch, domid, 1, claims); + if ( rc != -1 || errno != EINVAL ) + { + fail("Expected EINVAL with pad not set to zero\n"); + return rc; + } + + /* Pass a valid claim for the selected node and continue the test */ + claims[0] = (memory_claim_t)XEN_NODE_CLAIM_INIT(pages, claim_test_node); + return xc_domain_claim_memory(xch, domid, 1, claims); +} + +static int get_node_free_pages(unsigned int node, unsigned long *free_pages) +{ + int rc; + unsigned int num_nodes = 0; + xc_meminfo_t *meminfo; + + rc = xc_numainfo(xch, &num_nodes, NULL, NULL); + if ( rc ) + return rc; + + if ( node >= num_nodes ) + { + errno = EINVAL; + return -1; + } + + meminfo = calloc(num_nodes, sizeof(*meminfo)); + if ( !meminfo ) + return -1; + + rc = xc_numainfo(xch, &num_nodes, meminfo, NULL); + if ( rc ) + goto out; + + *free_pages = meminfo[node].memfree / XC_PAGE_SIZE; + + out: + free(meminfo); + return rc; +} + +static void run_test(claim_fn_t claim_call_wrapper, const char *claim_name, + bool host_wide_claim) +{ + int rc; + uint64_t free_heap_bytes; + unsigned long free_pages, claim_pages; + const unsigned long request_pages = 1UL << CLAIM_TEST_ORDER; + + printf(" Testing %s\n", claim_name); /* * Check that the system is quiescent. Outstanding claims is a global * field. @@ -51,7 +183,7 @@ static void run_tests(void) return fail("Failed to obtain physinfo: %d - %s\n", errno, strerror(errno)); - printf("Free pages: %"PRIu64", Oustanding claims: %"PRIu64"\n", + printf("Free pages: %"PRIu64", Outstanding claims: %"PRIu64"\n", physinfo.free_pages, physinfo.outstanding_pages); if ( physinfo.outstanding_pages ) @@ -98,13 +230,30 @@ static void run_tests(void) return fail(" Unexpected outstanding claim of %"PRIu64" pages\n", physinfo.outstanding_pages); - /* - * Set a claim for 4M. This should be the only claim in the system, and - * show up globally. - */ - rc = xc_domain_claim_pages(xch, domid, MB_PAGES(4)); + rc = xc_availheap(xch, 0, 0, host_wide_claim ? -1 : (int)claim_test_node, + &free_heap_bytes); if ( rc ) - return fail(" Failed to claim 4M of RAM: %d - %s\n", + return fail(" Failed to query available heap: %d - %s\n", + errno, strerror(errno)); + + free_pages = free_heap_bytes / XC_PAGE_SIZE; + if ( !host_wide_claim ) + { + rc = get_node_free_pages(claim_test_node, &free_pages); + if ( rc ) + return fail(" Failed to query free pages on node %u: %d - %s\n", + claim_test_node, errno, strerror(errno)); + } + + if ( free_pages <= request_pages + 1 ) + return fail(" Not enough free pages (%lu) to test %s claim enforcement\n", + free_pages, host_wide_claim ? "host-wide" : "node"); + + claim_pages = free_pages - request_pages + 1; + + rc = claim_call_wrapper(xch, domid, claim_pages); + if ( rc ) + return fail(" Failed to claim calculated RAM amount: %d - %s\n", errno, strerror(errno)); rc = xc_physinfo(xch, &physinfo); @@ -112,17 +261,51 @@ static void run_tests(void) return fail(" Failed to obtain physinfo: %d - %s\n", errno, strerror(errno)); - if ( physinfo.outstanding_pages != MB_PAGES(4) ) - return fail(" Expected claim to be 4M, got %"PRIu64" pages\n", - physinfo.outstanding_pages); + if ( physinfo.outstanding_pages != claim_pages ) + return fail(" Expected claim to be %lu pages, got %"PRIu64" pages\n", + claim_pages, physinfo.outstanding_pages); + + { + uint32_t other_domid = DOMID_INVALID; + xen_pfn_t other_ram[] = { 0 }; + unsigned int memflags = host_wide_claim ? 0 : XENMEMF_exact_node(claim_test_node); + + rc = xc_domain_create(xch, &other_domid, &create); + if ( rc ) + return fail(" Second domain create failure: %d - %s\n", + errno, strerror(errno)); + + rc = xc_domain_setmaxmem(xch, other_domid, -1); + if ( rc ) + { + fail(" Failed to set maxmem for second domain: %d - %s\n", + errno, strerror(errno)); + goto destroy_other; + } + + rc = xc_domain_populate_physmap_exact( + xch, other_domid, ARRAY_SIZE(other_ram), CLAIM_TEST_ORDER, + memflags, other_ram); + if ( rc == 0 ) + fail(" Expected %s claim to block second-domain allocation\n", + host_wide_claim ? "host-wide" : "node"); + + destroy_other: + rc = xc_domain_destroy(xch, other_domid); + if ( rc ) + return fail(" Failed to destroy second domain: %d - %s\n", + errno, strerror(errno)); + } /* - * Allocate 2M of RAM to the domain. This should be deducted from global - * claim. + * Allocate one CLAIM_TEST_ORDER chunk to the domain. This should reduce + * the outstanding claim by request_pages. For node claims, request memory + * from the claimed node. */ xen_pfn_t ram[] = { 0 }; rc = xc_domain_populate_physmap_exact( - xch, domid, ARRAY_SIZE(ram), 9 /* Order 2M */, 0, ram); + xch, domid, ARRAY_SIZE(ram), CLAIM_TEST_ORDER, + host_wide_claim ? 0 : XENMEMF_node(claim_test_node), ram); if ( rc ) return fail(" Failed to populate physmap domain: %d - %s\n", errno, strerror(errno)); @@ -132,9 +315,9 @@ static void run_tests(void) return fail(" Failed to obtain physinfo: %d - %s\n", errno, strerror(errno)); - if ( physinfo.outstanding_pages != MB_PAGES(2) ) - return fail(" Expected claim to be 2M, got %"PRIu64" pages\n", - physinfo.outstanding_pages); + if ( physinfo.outstanding_pages != claim_pages - request_pages ) + return fail(" Expected claim to be %lu pages, got %"PRIu64" pages\n", + claim_pages - request_pages, physinfo.outstanding_pages); /* * Destroying the domain should release the outstanding 2M claim. @@ -161,6 +344,8 @@ static void run_tests(void) int main(int argc, char **argv) { int rc; + unsigned int num_nodes = 0; + xc_meminfo_t *meminfo = NULL; printf("Memory claims tests\n"); @@ -169,14 +354,60 @@ int main(int argc, char **argv) if ( !xch ) err(1, "xc_interface_open"); - run_tests(); + rc = xc_numainfo(xch, &num_nodes, NULL, NULL); + if ( rc || !num_nodes ) + err(1, "xc_numainfo"); + + meminfo = calloc(num_nodes, sizeof(*meminfo)); + if ( !meminfo ) + err(1, "calloc"); - if ( domid != DOMID_INVALID ) + rc = xc_numainfo(xch, &num_nodes, meminfo, NULL); + if ( rc ) + err(1, "xc_numainfo"); + + claim_test_node = 0; + for ( unsigned int i = 1; i < num_nodes; i++ ) { - rc = xc_domain_destroy(xch, domid); - if ( rc ) - fail(" Failed to destroy domain: %d - %s\n", - errno, strerror(errno)); + if ( meminfo[i].memfree > meminfo[claim_test_node].memfree ) + claim_test_node = i; + } + + free(meminfo); + + struct { + claim_fn_t fn; + const char *name; + bool host_wide; + } tests[] = { + { + .fn = wrap_claim_pages, + .name = "xc_domain_claim_pages", + .host_wide = true, + }, + { + .fn = wrap_claim_memory, + .name = "xc_domain_claim_memory", + .host_wide = true, + }, + { + .fn = wrap_claim_memory_node, + .name = "xc_domain_claim_memory_node", + .host_wide = false, + }, + }; + size_t num_tests = sizeof(tests) / sizeof(tests[0]); + for ( size_t i = 0; i < num_tests; i++ ) + { + run_test(tests[i].fn, tests[i].name, tests[i].host_wide); + if ( domid != DOMID_INVALID ) + { + rc = xc_domain_destroy(xch, domid); + if ( rc ) + fail(" Failed to destroy domain: %d - %s\n", + errno, strerror(errno)); + domid = DOMID_INVALID; + } } return !!nr_failures; -- 2.39.5
