1) Instrumented cart service with OpenCensus Trace API, exporting traces to Stackdriver
2) Migrated cart service+tests to .NET Core 2.1. This required to update base image to install openssl package
This commit is contained in:
parent
d966bc7c5d
commit
6fef196f33
6 changed files with 188 additions and 57 deletions
|
@ -22,6 +22,7 @@ RUN apk add --no-cache \
|
|||
libgcc \
|
||||
libstdc++ \
|
||||
libintl \
|
||||
openssl \
|
||||
icu
|
||||
WORKDIR /app
|
||||
COPY --from=builder /cartservice .
|
||||
|
|
|
@ -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)
|
||||
/// <summary>
|
||||
/// Reads parameter in the right order
|
||||
/// </summary>
|
||||
/// <param name="description">Parameter description</param>
|
||||
/// <param name="commandLineValue">Value provided from the command line</param>
|
||||
/// <param name="environmentVariableName">The name of environment variable where it could have been set</param>
|
||||
/// <param name="environmentParser">The method that parses environment variable and returns typed parameter value</param>
|
||||
/// <param name="defaultValue">Parameter's default value - in case other method failed to assign a value</param>
|
||||
/// <typeparam name="T">The type of the parameter</typeparam>
|
||||
/// <returns>Parameter value read from all the sources in the right order(priorities)</returns>
|
||||
private static T ReadParameter<T>(
|
||||
string description,
|
||||
T commandLineValue,
|
||||
string environmentVariableName,
|
||||
Func<string, T> environmentParser,
|
||||
T defaultValue)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(address))
|
||||
// Command line argument
|
||||
if(!EqualityComparer<T>.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
<PackageReference Include="grpc.tools" Version="1.12.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
|
||||
<PackageReference Include="OpenCensus" Version="0.1.0-alpha-33381" />
|
||||
<PackageReference Include="OpenCensus.Exporter.Stackdriver" Version="0.1.0-alpha-33381" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="1.2.6" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
116
src/cartservice/cartstore/InstrumentedCartStore.cs
Normal file
116
src/cartservice/cartstore/InstrumentedCartStore.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for Cart Store - instrumented with OpenCensus tracing
|
||||
/// </summary>
|
||||
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<Cart> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.6.0" />
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.6.0" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.6.1" />
|
||||
<PackageReference Include="Grpc" Version="1.12.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="1.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue