
On Sat, 28 Jan 2012 11:16:03 -0700 Eric Blake <eblake@redhat.com> wrote:
On 01/27/2012 11:21 PM, KAMEZAWA Hiroyuki wrote:
remote protocol driver for virDomainGetCPUStats()
Note: Unlike other users of virTypedParameter with RPC, this interface can return zero-filled entries because the interface assumes 2 dimentional array. Then, the function has its own serialize/deserialize
s/dimentional/dimensional/
routine.
daemon/remote.c | 146 +++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 117 ++++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 22 ++++++ src/remote_protocol-structs | 15 ++++ --- daemon/remote.c | 147 ++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 117 +++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 22 ++++++- src/remote_protocol-structs | 15 +++
Odd that the diffstat listed twice.
I'm going to reorder my review, to start with the .x file.
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 0f354bb..205aeeb 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -208,6 +208,12 @@ const REMOTE_DOMAIN_SEND_KEY_MAX = 16; */ const REMOTE_DOMAIN_INTERFACE_PARAMETERS_MAX = 16;
+/* + * Upper limit on list of (real) cpu parameters + * nparams(<=16) * ncpus(<=128) <= 2048. + */ +const REMOTE_DOMAIN_GET_CPU_STATS_MAX = 2048;
We should enforce the two limits independently, which means we also need two smaller constants (it's a shame that rpcgen doesn't allow products when computing a constant, but we can document how we arrive at various numbers). We can reuse VIR_NODE_CPU_STATS_MAX for one of the two limits.
Ok.
+ /* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */ typedef opaque remote_uuid[VIR_UUID_BUFLEN];
@@ -1149,6 +1155,19 @@ struct remote_domain_get_block_io_tune_ret { int nparams; };
+struct remote_domain_get_cpu_stats_args { + remote_nonnull_domain dom; + unsigned int nparams; + int start_cpu; + unsigned int ncpus; + unsigned int flags; +}; + +struct remote_domain_get_cpu_stats_ret { + remote_typed_param params<REMOTE_DOMAIN_GET_CPU_STATS_MAX>; + int nparams; +};
Looks good.
+ /* Network calls: */
struct remote_num_of_networks_ret { @@ -2667,7 +2686,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SET_INTERFACE_PARAMETERS = 256, /* autogen autogen */ REMOTE_PROC_DOMAIN_GET_INTERFACE_PARAMETERS = 257, /* skipgen skipgen */ REMOTE_PROC_DOMAIN_SHUTDOWN_FLAGS = 258, /* autogen autogen */ - REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259 /* autogen autogen */ + REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259, /* autogen autogen */ + REMOTE_PROC_DOMAIN_GET_CPU_STATS = 260 /* skipgen skipgen */
Trivial conflict resolution.
+static int +remoteDispatchDomainGetCPUStats(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client ATTRIBUTE_UNUSED, + virNetMessagePtr hdr ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_domain_get_cpu_stats_args *args, + remote_domain_get_cpu_stats_ret *ret) +{ + virDomainPtr dom = NULL; + struct daemonClientPrivate *priv; + virTypedParameterPtr params = NULL; + remote_typed_param *info = NULL; + int cpu, idx, src, dest; + int rv = -1; + unsigned int return_size = 0; + int percpu_len = 0; + + priv = virNetServerClientGetPrivateData(client); + if (!priv->conn) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + /* + * nparams * ncpus should be smaller than + * REMOTE_DOMAIN_GET_CPU_STATS_MAX but nparams + * and ncpus are limited by API. check it. + */ + if (args->nparams > 16) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("nparams too large")); + goto cleanup; + } + if (args->ncpus > 128) {
Use the constants from the .x file, rather than open-coding things.
Sorry.
Oops, I just realized that I mentioned that libvirt.c should not have a length check, but then I forgot to delete it. Meanwhile, even if libvirt.c does not have a length check, it still needs to have a multiplication overflow check.
+ /* + * Here, we return values based on the real param size returned by + * a driver rather than the passed one. RPC Client stub should decode + * it to fit callar's expectation.
s/callar/caller/
+ * + * If cpu ID is sparse, The whole array for some cpu IDs will be + * zero-cleared. This doesn't meet to remoteSerializeTypedParameters() + * and we do serialization by ourselves.
We can still use remoteSerializeTypedParameters; we just have to teach it to skip zero entries like it already can skip string entries.
Yes. you're right.
Oh, I just realized that to support string entries, we have to support a flags of VIR_TYPED_PARAM_STRING_OKAY.
Ah, I missed that.
+success: + rv = 0; + ret->params.params_len = return_size; + ret->params.params_val = info; + ret->nparams = percpu_len; +cleanup: + if (rv < 0) { + virNetMessageSaveError(rerr); + if (info) {
Cleanup is a lot simpler if we let the existing serialization skip zero entries.
Ah, skip is not
+++ b/src/remote/remote_driver.c @@ -2305,6 +2305,122 @@ done: return rv; }
+static int remoteDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + struct private_data *priv = domain->conn->privateData; + remote_domain_get_cpu_stats_args args; + remote_domain_get_cpu_stats_ret ret; + remote_typed_param *info; + int rv = -1; + int cpu, idx, src, dest; + + remoteDriverLock(priv); + + make_nonnull_domain(&args.dom, domain); + args.nparams = nparams; + args.start_cpu = start_cpu; + args.ncpus = ncpus; + args.flags = flags; + + memset(&ret, 0, sizeof(ret));
This side needs to validate incoming parameters before sending over the wire. Hmm, remoteNodeGetCPUStats needs to do likewise.
+ + if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_CPU_STATS, + (xdrproc_t) xdr_remote_domain_get_cpu_stats_args, + (char *) &args, + (xdrproc_t) xdr_remote_domain_get_cpu_stats_ret, + (char *) &ret) == -1) + goto done; + + if (nparams == 0) { + rv = ret.nparams; + goto cleanup; + } + /* + * the returned arrray's size is not same to nparams * ncpus. And + * if cpu ID is not contiguous, all-zero entries can be found. + */ + memset(params, 0, sizeof(virTypedParameter) * nparams * ncpus);
I prefer sizeof(expr) over sizeof(type), in case expr ever changes types.
+ + /* Here, ret.nparams is always smaller than nparams */ + info = ret.params.params_val; + + for (cpu = 0; cpu < ncpus; ++cpu) { + for (idx = 0; idx < ret.nparams; ++idx) { + src = cpu * ret.nparams + idx; + dest = cpu * nparams + idx; + + if (info[src].value.type == 0) /* skip zeroed ones */ + continue;
Again, open coding things is not nice. If we write the server to never send zero entries (which is a good thing - less data over the wire), then the client needs to re-create zero entries by calling deserialization in a loop - deserialize ret.nparams entries at every nparams slot.
+ + rv = ret.nparams; +cleanup: + if (rv < 0) { + int max = nparams * ncpus; + int i; + + for (i = 0; i < max; i++) { + if (params[i].type == VIR_TYPED_PARAM_STRING) + VIR_FREE(params[i].value.s);
Potential free of bogus memory if anything can reach cleanup with rv < 0 but before we sanitized the contents of params.
Here's what I'm squashing in:
seems nicer ! Thank you for kindly reviewing. Thanks, -Kame