From 9ccc6348cf5d7c888822d6782b6763cabaffe940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Thu, 23 Jan 2025 13:31:26 +0000 Subject: [PATCH 1/5] Makefile: add sdksanity rule for testing on Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To allow compile-testing the C# code on Linux reintroduce the 'sdksanity' rule which got dropped accidentally. Also run the tests. Fixes: e6afe15bf ("Removed erroneously ported recipe.") Signed-off-by: Edwin Török --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 7f7386bf6b1..44892a82bf3 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,11 @@ doc: sdk: dune build --profile=$(PROFILE) @sdkgen xapi-sdk.install @ocaml/sdk-gen/install +# workaround for no .resx generation, just for compilation testing +sdksanity: sdk + cd _build/install/default/share/csharp/src && dotnet add package Newtonsoft.Json && dotnet build -f netstandard2.0 + cd _build/install/default/share/csharp && dotnet test XenServerTest -p:DefineConstants=BUILD_FOR_TEST + .PHONY: sdk-build-c sdk-build-c: sdk From 2f47bdc40639f2109463ffde6fd4e2e3abd16f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Tue, 9 Apr 2024 18:07:21 +0100 Subject: [PATCH 2/5] CP-44752: SDK(C#): add optional System.Diagnostics.DiagnosticSource as a dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This only exists on .Net 4.6+, so add it conditionally only when target >Netstandard 2.0 (which includes .Net 4.6). On the newer .Net versions this is already included. This allows creating activity spans, independently of the tracing framework used. We use version 8.0.1, because version 9.0.0 doesn't support .Net 6.0 anymore. Signed-off-by: Edwin Török --- ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj b/ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj index bf387509141..c3ce742e400 100644 --- a/ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj +++ b/ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj @@ -30,6 +30,9 @@ + + + True From 9c404ef1c913dc038130d07b8b54758de6c87679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Mon, 12 Aug 2024 14:53:17 +0100 Subject: [PATCH 3/5] CP-44752: SDK(C#): lock package dependencies to a specific hash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To avoid someone injecting a malicious binary with the same version in the future, lock all dependencies. Signed-off-by: Edwin Török --- .../csharp/autogen/src/XenServer.csproj | 1 + .../csharp/autogen/src/packages.lock.json | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 ocaml/sdk-gen/csharp/autogen/src/packages.lock.json diff --git a/ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj b/ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj index c3ce742e400..dca57a17557 100644 --- a/ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj +++ b/ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj @@ -18,6 +18,7 @@ packageIcon.png git README-NuGet.md + true diff --git a/ocaml/sdk-gen/csharp/autogen/src/packages.lock.json b/ocaml/sdk-gen/csharp/autogen/src/packages.lock.json new file mode 100644 index 00000000000..bca624389d3 --- /dev/null +++ b/ocaml/sdk-gen/csharp/autogen/src/packages.lock.json @@ -0,0 +1,84 @@ +{ + "version": 1, + "dependencies": { + ".NETFramework,Version=v4.5": { + "Microsoft.NETFramework.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.0.2, )", + "resolved": "1.0.2", + "contentHash": "5/cSEVld+px/CuRrbohO/djfg6++eR6zGpy88MgqloXvkj//WXWpFZyu/OpkXPN0u5m+dN/EVwLNYFUxD4h2+A==", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net45": "1.0.2" + } + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "Microsoft.NETFramework.ReferenceAssemblies.net45": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "Nm14pRmqB+4u2JEMdtngnbDcJidTmswMxOJ992TpTwiwcUTERxLlHwwSh0HiUoRjS0TO0sozsiB0h6FHjCUdEA==" + } + }, + ".NETStandard,Version=v2.0": { + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Direct", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.4.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + } + } + } +} \ No newline at end of file From 921dae5c9d60bffe001ed5d1301cda6c88cf2d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Fri, 12 Apr 2024 15:05:04 +0100 Subject: [PATCH 4/5] CP-44752: SDK(C#): Conditionally propagate W3C traceparent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is not available on .Net 4.5 Signed-off-by: Edwin Török --- ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs b/ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs index 519cc430d4e..78e096500d8 100644 --- a/ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs +++ b/ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs @@ -29,6 +29,9 @@ using System; using System.Collections.Generic; +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) +using System.Diagnostics; +#endif using System.IO; using System.Net; using System.Net.Security; @@ -293,6 +296,23 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre webRequest.Headers.Add(header.Key, header.Value); } +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + // propagate W3C traceparent and tracestate + // HttpClient would do this automatically on .NET 5, + // and .NET 6 would provide even more control over this: https://blog.ladeak.net/posts/opentelemetry-net6-httpclient + // the caller must ensure that the activity is in W3C format (by inheritance or direct setting) + var activity = Activity.Current; + if (activity != null && activity.IdFormat == ActivityIdFormat.W3C) + { + webRequest.Headers.Add("traceparent", activity.Id); + var state = activity.TraceStateString; + if (state?.Length > 0) + { + webRequest.Headers.Add("tracestate", state); + } + } +#endif + using (var str = webRequest.GetRequestStream()) { postStream.CopyTo(str); From f6c2641fb1c87991627a9597bfbf3e7f260cb10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Tue, 9 Apr 2024 18:57:00 +0100 Subject: [PATCH 5/5] CP-44752: SDK(C#): Conditional activity source for JsonRPC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doesn't exist on .Net45. Only creates these activity sources if a listener has been created by the caller, otherwise `activity` will be `null`, and the code would be a no-op by default. A listener is created by OpenTelemetry instrumentation for example. Signed-off-by: Edwin Török --- ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs | 101 +++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs b/ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs index 78e096500d8..a94eadf9b0c 100644 --- a/ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs +++ b/ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs @@ -158,6 +158,41 @@ public partial class JsonRpcClient { private int _globalId; +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + private static readonly Type ClassType = typeof(JsonRpcClient); + private static readonly System.Reflection.AssemblyName ClassAssemblyName= ClassType?.Assembly?.GetName(); + private static readonly ActivitySource source = new ActivitySource(ClassAssemblyName.Name + "." + ClassType?.FullName, ClassAssemblyName.Version?.ToString()); + + // Follow naming conventions from OpenTelemetry.SemanticConventions + // Not yet on NuGet though: + // dotnet add package OpenTelemetry.SemanticConventions + private static class RpcAttributes { + public const string AttributeRpcMethod = "rpc.method"; + public const string AttributeRpcSystem = "rpc.system"; + public const string AttributeRpcService = "rpc.service"; + public const string AttributeRpcJsonrpcErrorCode = "rpc.jsonrpc.error_code"; + public const string AttributeRpcJsonrpcErrorMessage = "rpc.jsonrpc.error_message"; + public const string AttributeRpcJsonrpcRequestId = "rpc.jsonrpc.request_id"; + public const string AttributeRpcJsonrpcVersion = "rpc.jsonrpc.version"; + + public const string AttributeRpcMessageType = "rpc.message.type"; + public static class RpcMessageTypeValues + { + public const string Sent = "SENT"; + + public const string Received = "RECEIVED"; + } + } + + private static class ServerAttributes { + public const string AttributeServerAddress = "server.address"; + } + + // not part of the SemanticConventions package + private const string ValueJsonRpc = "jsonrpc"; + private const string EventRpcMessage = "rpc.message"; +#endif + public JsonRpcClient(string baseUrl) { Url = baseUrl; @@ -210,6 +245,21 @@ protected virtual T Rpc(string callName, JToken parameters, JsonSerializer se // therefore the latter will be done only in DEBUG mode using (var postStream = new MemoryStream()) { +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + // the semantic convention is $package.$service/$method + using (Activity activity = source.CreateActivity("XenAPI/" + callName, ActivityKind.Client)) + { + // .NET 5 would use W3C format for the header by default but we build for .Net 4.x still + activity?.SetIdFormat(ActivityIdFormat.W3C); + activity?.Start(); + // Set the fields described in the OpenTelemetry Semantic Conventions: + // https://web.archive.org/web/20250119181511/https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/ + // https://web.archive.org/web/20241113162246/https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/ + activity?.SetTag(RpcAttributes.AttributeRpcSystem, ValueJsonRpc); + activity?.SetTag(ServerAttributes.AttributeServerAddress, new Uri(Url).Host); + activity?.SetTag(RpcAttributes.AttributeRpcMethod, callName); + activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcRequestId, id.ToString()); +#endif using (var sw = new StreamWriter(postStream)) { #if DEBUG @@ -236,37 +286,67 @@ protected virtual T Rpc(string callName, JToken parameters, JsonSerializer se switch (JsonRpcVersion) { case JsonRpcVersion.v2: +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcVersion, "2.0"); +#endif #if DEBUG string json2 = responseReader.ReadToEnd(); var res2 = JsonConvert.DeserializeObject>(json2, settings); #else var res2 = (JsonResponseV2)serializer.Deserialize(responseReader, typeof(JsonResponseV2)); #endif + if (res2.Error != null) { var descr = new List { res2.Error.Message }; descr.AddRange(res2.Error.Data.ToObject()); +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorCode, res2.Error.Code); + activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, descr); + activity?.SetStatus(ActivityStatusCode.Error); +#endif throw new Failure(descr); } + +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + activity?.SetStatus(ActivityStatusCode.Ok); +#endif return res2.Result; default: +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcVersion, "1.0"); +#endif #if DEBUG string json1 = responseReader.ReadToEnd(); var res1 = JsonConvert.DeserializeObject>(json1, settings); #else var res1 = (JsonResponseV1)serializer.Deserialize(responseReader, typeof(JsonResponseV1)); #endif + if (res1.Error != null) { var errorArray = res1.Error.ToObject(); - if (errorArray != null) + if (errorArray != null) { +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + activity?.SetStatus(ActivityStatusCode.Error); + // we can't be sure whether we'll have a Code here + // the exact format of an error object is not specified in JSONRPC v1 + activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, errorArray.ToString()); +#endif throw new Failure(errorArray); + } } +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + activity?.SetStatus(ActivityStatusCode.Ok); +#endif return res1.Result; } } } } +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + } +#endif } } @@ -319,6 +399,15 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre str.Flush(); } +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + if (activity != null) { + var tags = new ActivityTagsCollection{ + { RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Sent } + }; + activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags)); + } +#endif + HttpWebResponse webResponse = null; try { @@ -346,6 +435,16 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre str.CopyTo(responseStream); responseStream.Flush(); } + +#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER) + if (activity != null) { + var tags = new ActivityTagsCollection{ + { RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Received } + }; + activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags)); + } +#endif + } finally {