diff --git a/src/cartservice/Dockerfile b/src/cartservice/Dockerfile
index ad2b1c3..a77331a 100644
--- a/src/cartservice/Dockerfile
+++ b/src/cartservice/Dockerfile
@@ -22,6 +22,7 @@ RUN apk add --no-cache \
libgcc \
libstdc++ \
libintl \
+ openssl \
icu
WORKDIR /app
COPY --from=builder /cartservice .
diff --git a/src/cartservice/Program.cs b/src/cartservice/Program.cs
index d1f80bb..4b88beb 100644
--- a/src/cartservice/Program.cs
+++ b/src/cartservice/Program.cs
@@ -13,6 +13,7 @@
// limitations under the License.
using System;
+using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -21,6 +22,8 @@ using cartservice.interfaces;
using CommandLine;
using Grpc.Core;
using Microsoft.Extensions.Configuration;
+using OpenCensus.Exporter.Stackdriver;
+using OpenCensus.Trace;
namespace cartservice
{
@@ -30,6 +33,8 @@ namespace cartservice
const string REDIS_ADDRESS = "REDIS_ADDR";
const string CART_SERVICE_PORT = "PORT";
+ const string PROJECT_ID = "PROJECT_ID";
+
[Verb("start", HelpText = "Starts the server listening on provided port")]
class ServerOptions
{
@@ -41,6 +46,9 @@ namespace cartservice
[Option('r', "redis", HelpText = "The ip of redis cache")]
public string Redis { get; set; }
+
+ [Option("projectId", HelpText = "The ProjectId to which telemetry will flow")]
+ public string ProjectId { get; set; }
}
static object StartServer(string host, int port, ICartStore cartStore)
@@ -104,55 +112,27 @@ namespace cartservice
Console.WriteLine($"Started as process with id {System.Diagnostics.Process.GetCurrentProcess().Id}");
// Set hostname/ip address
- string hostname = options.Host;
- if (string.IsNullOrEmpty(hostname))
- {
- Console.WriteLine($"Reading host address from {CART_SERVICE_ADDRESS} environment variable");
- hostname = Environment.GetEnvironmentVariable(CART_SERVICE_ADDRESS);
- if (string.IsNullOrEmpty(hostname))
- {
- Console.WriteLine($"Environment variable {CART_SERVICE_ADDRESS} was not set. Setting the host to 0.0.0.0");
- hostname = "0.0.0.0";
- }
- }
+ string hostname = ReadParameter("host address", options.Host, CART_SERVICE_ADDRESS, p => p, "0.0.0.0");
// Set the port
- int port = options.Port;
- if (options.Port <= 0)
+ int port = ReadParameter("cart service port", options.Port, CART_SERVICE_PORT, int.Parse, 8080);
+
+ string projectId = ReadParameter("cloud service project id", options.ProjectId, PROJECT_ID, p => p, null);
+
+ // Initialize Stackdriver Exporter - currently for tracing only
+ if (!string.IsNullOrEmpty(projectId))
{
- Console.WriteLine($"Reading cart service port from {CART_SERVICE_PORT} environment variable");
- string portStr = Environment.GetEnvironmentVariable(CART_SERVICE_PORT);
- if (string.IsNullOrEmpty(portStr))
- {
- Console.WriteLine($"{CART_SERVICE_PORT} environment variable was not set. Setting the port to 8080");
- port = 8080;
- }
- else
- {
- port = int.Parse(portStr);
- }
+ var exporter = new StackdriverExporter(
+ projectId,
+ Tracing.ExportComponent,
+ viewManager: null);
+ exporter.Start();
}
// Set redis cache host (hostname+port)
- ICartStore cartStore;
- string redis = ReadRedisAddress(options.Redis);
-
- // Redis was specified via command line or environment variable
- if (!string.IsNullOrEmpty(redis))
- {
- // If you want to start cart store using local cache in process, you can replace the following line with this:
- // cartStore = new LocalCartStore();
- cartStore = new RedisCartStore(redis);
-
- return StartServer(hostname, port, cartStore);
- }
- else
- {
- Console.WriteLine("Redis cache host(hostname+port) was not specified. Starting a cart service using local store");
- Console.WriteLine("If you wanted to use Redis Cache as a backup store, you should provide its address via command line or REDIS_ADDRESS environment variable.");
- cartStore = new LocalCartStore();
- }
+ string redis = ReadParameter("redis cache address", options.Redis, REDIS_ADDRESS, p => p, null);
+ ICartStore cartStore = InstrumentedCartStore.Create(redis);
return StartServer(hostname, port, cartStore);
},
errs => 1);
@@ -163,21 +143,48 @@ namespace cartservice
}
}
- private static string ReadRedisAddress(string address)
+ ///
+ /// Reads parameter in the right order
+ ///
+ /// Parameter description
+ /// Value provided from the command line
+ /// The name of environment variable where it could have been set
+ /// The method that parses environment variable and returns typed parameter value
+ /// Parameter's default value - in case other method failed to assign a value
+ /// The type of the parameter
+ /// Parameter value read from all the sources in the right order(priorities)
+ private static T ReadParameter(
+ string description,
+ T commandLineValue,
+ string environmentVariableName,
+ Func environmentParser,
+ T defaultValue)
{
- if (!string.IsNullOrEmpty(address))
+ // Command line argument
+ if(!EqualityComparer.Default.Equals(commandLineValue, default(T)))
{
- return address;
+ return commandLineValue;
}
- Console.WriteLine($"Reading redis cache address from environment variable {REDIS_ADDRESS}");
- string redis = Environment.GetEnvironmentVariable(REDIS_ADDRESS);
- if (!string.IsNullOrEmpty(redis))
+ // Environment variable
+ Console.Write($"Reading {description} from environment variable {environmentVariableName}. ");
+ string envValue = Environment.GetEnvironmentVariable(environmentVariableName);
+ if (!string.IsNullOrEmpty(envValue))
{
- return redis;
+ try
+ {
+ var envTyped = environmentParser(envValue);
+ Console.WriteLine("Done!");
+ return envTyped;
+ }
+ catch (Exception)
+ {
+ // We assign the default value later on
+ }
}
- return null;
+ Console.WriteLine($"Environment variable {environmentVariableName} was not set. Setting {description} to {defaultValue}");
+ return defaultValue;
}
}
}
diff --git a/src/cartservice/cartservice.csproj b/src/cartservice/cartservice.csproj
index 3fea4a1..de58b34 100644
--- a/src/cartservice/cartservice.csproj
+++ b/src/cartservice/cartservice.csproj
@@ -14,6 +14,8 @@
+
+
diff --git a/src/cartservice/cartstore/InstrumentedCartStore.cs b/src/cartservice/cartstore/InstrumentedCartStore.cs
new file mode 100644
index 0000000..1f41b0e
--- /dev/null
+++ b/src/cartservice/cartstore/InstrumentedCartStore.cs
@@ -0,0 +1,116 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.Threading.Tasks;
+using cartservice.interfaces;
+using Hipstershop;
+using OpenCensus.Trace;
+using OpenCensus.Trace.Sampler;
+
+namespace cartservice.cartstore
+{
+ ///
+ /// Wrapper for Cart Store - instrumented with OpenCensus tracing
+ ///
+ internal class InstrumentedCartStore : ICartStore
+ {
+ private readonly ICartStore cartStore;
+ private static ITracer tracer = Tracing.Tracer;
+ private ISpanBuilder initializeSpanBuilder, addItemSpanBuilder, emptySpanBuilder, getItemSpanBuilder, pingSpanBuilder;
+
+ public InstrumentedCartStore(ICartStore cartStore)
+ {
+ this.cartStore = cartStore;
+
+ // Create Span Builders for tracing
+ initializeSpanBuilder = CreateSpanBuilder("Initialize Cart Store");
+ addItemSpanBuilder = CreateSpanBuilder("Add Item");
+ emptySpanBuilder = CreateSpanBuilder("Empty Cart");
+ getItemSpanBuilder = CreateSpanBuilder("Get Cart");
+ pingSpanBuilder = CreateSpanBuilder("Ping");
+ }
+ public Task AddItemAsync(string userId, string productId, int quantity)
+ {
+ using (var span = addItemSpanBuilder.StartScopedSpan())
+ {
+ return cartStore.AddItemAsync(userId, productId, quantity);
+ }
+ }
+
+ public Task EmptyCartAsync(string userId)
+ {
+ using (var span = emptySpanBuilder.StartScopedSpan())
+ {
+ return cartStore.EmptyCartAsync(userId);
+ }
+ }
+
+ public Task GetCartAsync(string userId)
+ {
+ using (var span = getItemSpanBuilder.StartScopedSpan())
+ {
+ return cartStore.GetCartAsync(userId);
+ }
+ }
+
+ public Task InitializeAsync()
+ {
+ using (var span = initializeSpanBuilder.StartScopedSpan())
+ {
+ return cartStore.InitializeAsync();
+ }
+ }
+
+ public bool Ping()
+ {
+ using (var span = pingSpanBuilder.StartScopedSpan())
+ {
+ return cartStore.Ping();
+ }
+ }
+
+ public static ICartStore Create(string redis)
+ {
+ ICartStore cartStore;
+
+ // Redis was specified
+ if (!string.IsNullOrEmpty(redis))
+ {
+ // If you want to start cart store using local cache in process, you can replace the following line with this:
+ // cartStore = new LocalCartStore();
+ cartStore = new RedisCartStore(redis);
+ }
+ else
+ {
+ Console.WriteLine("Redis cache host(hostname+port) was not specified. Starting a cart service using local store");
+ Console.WriteLine("If you wanted to use Redis Cache as a backup store, you should provide its address via command line or REDIS_ADDRESS environment variable.");
+ cartStore = new LocalCartStore();
+ }
+
+ // We create the cart store wrapped with instrumentation
+ return new InstrumentedCartStore(cartStore);
+ }
+
+ private static ISpanBuilder CreateSpanBuilder(string spanName)
+ {
+ var spanBuilder = tracer
+ .SpanBuilder(spanName)
+ .SetRecordEvents(true)
+ .SetSampler(Samplers.AlwaysSample);
+
+ return spanBuilder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/cartservice/cartstore/RedisCartStore.cs b/src/cartservice/cartstore/RedisCartStore.cs
index bc7b7e8..7eb68cb 100644
--- a/src/cartservice/cartstore/RedisCartStore.cs
+++ b/src/cartservice/cartstore/RedisCartStore.cs
@@ -22,11 +22,15 @@ using Google.Protobuf;
using Grpc.Core;
using Hipstershop;
using StackExchange.Redis;
+using OpenCensus.Trace;
+using OpenCensus.Trace.Sampler;
namespace cartservice.cartstore
{
public class RedisCartStore : ICartStore
{
+ private static ITracer tracer = Tracing.Tracer;
+
private const string CART_FIELD_NAME = "cart";
private const int REDIS_RETRY_NUM = 5;
@@ -76,9 +80,10 @@ namespace cartservice.cartstore
return;
}
+ tracer.CurrentSpan.AddAnnotation("Connecting to Redis Cache");
Console.WriteLine("Connecting to Redis: " + connectionString);
redis = ConnectionMultiplexer.Connect(redisConnectionOptions);
-
+ tracer.CurrentSpan.AddAnnotation("Finished connecting to Redis Cache");
if (redis == null || !redis.IsConnected)
{
Console.WriteLine("Wasn't able to connect to redis");
@@ -149,7 +154,7 @@ namespace cartservice.cartstore
}
catch (Exception ex)
{
- throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}"));
+ throw new RpcException(new Grpc.Core.Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}"));
}
}
@@ -167,7 +172,7 @@ namespace cartservice.cartstore
}
catch (Exception ex)
{
- throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}"));
+ throw new RpcException(new Grpc.Core.Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}"));
}
}
@@ -194,7 +199,7 @@ namespace cartservice.cartstore
}
catch (Exception ex)
{
- throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}"));
+ throw new RpcException(new Grpc.Core.Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}"));
}
}
diff --git a/tests/cartservice/cartservice.tests.csproj b/tests/cartservice/cartservice.tests.csproj
index 4ada297..a24511e 100644
--- a/tests/cartservice/cartservice.tests.csproj
+++ b/tests/cartservice/cartservice.tests.csproj
@@ -1,14 +1,14 @@
- netcoreapp2.0
+ netcoreapp2.1
false
-
-
+
+