Initial commit from private

This commit is contained in:
2019-07-15 20:51:25 -04:00
commit 264f03a22f
55 changed files with 2006 additions and 0 deletions

30
Weather/Service/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,30 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (remote console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "publish",
"program": "dotnet",
"args": [
"/home/ckaczor/Weather/Service/Weather.Service.dll"
],
"cwd": "/home/ckaczor/Weather/Service",
"stopAtEntry": false,
"console": "internalConsole",
"pipeTransport": {
"pipeCwd": "${workspaceFolder}",
"pipeProgram": "C:\\Program Files (x86)\\PuTTY\\PLINK.EXE",
"pipeArgs": [
"root@172.23.10.6"
],
"debuggerPath": "/home/ckaczor/vsdbg/vsdbg"
},
"internalConsoleOptions": "openOnSessionStart"
}
]
}

35
Weather/Service/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Service.csproj"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "publish",
"type": "shell",
"presentation": {
"reveal": "always",
"panel": "shared"
},
"options": {
"cwd": "${workspaceFolder}"
},
"windows": {
"command": "${cwd}\\publish.bat"
},
"problemMatcher": [],
"group": "build"
}
]
}

View File

@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace Weather.Service.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new[] { "value1", "value2" };
}
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
[HttpPost]
public void Post([FromBody] string value) { }
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value) { }
[HttpDelete("{id}")]
public void Delete(int id) { }
}
}

View File

@@ -0,0 +1,81 @@
using Dapper;
using Microsoft.Extensions.Configuration;
using Npgsql;
using Weather.Models;
namespace Weather.Service.Data
{
public class Database
{
private readonly IConfiguration _configuration;
public Database(IConfiguration configuration)
{
_configuration = configuration;
}
public void EnsureDatabase()
{
var connectionStringBuilder = new NpgsqlConnectionStringBuilder
{
Host = _configuration["Weather:Database:Host"],
Username = _configuration["Weather:Database:User"],
Password = _configuration["Weather:Database:Password"],
Database = "postgres"
};
using (var connection = new NpgsqlConnection(connectionStringBuilder.ConnectionString))
{
var command = new NpgsqlCommand { Connection = connection };
connection.Open();
// Check to see if the database exists
command.CommandText = $"SELECT TRUE from pg_database WHERE datname='{_configuration["Weather:Database:Name"]}'";
var databaseExists = (bool?)command.ExecuteScalar();
// Create database if needed
if (!databaseExists.GetValueOrDefault(false))
{
command.CommandText = $"CREATE DATABASE {_configuration["Weather:Database:Name"]}";
command.ExecuteNonQuery();
}
// Switch to the database now that we're sure it exists
connection.ChangeDatabase(_configuration["Weather:Database:Name"]);
var schema = ResourceReader.GetString("Weather.Service.Data.Resources.Schema.sql");
// Make sure the database is up to date
command.CommandText = schema;
command.ExecuteNonQuery();
}
}
private NpgsqlConnection CreateConnection()
{
var connectionStringBuilder = new NpgsqlConnectionStringBuilder
{
Host = _configuration["Weather:Database:Host"],
Username = _configuration["Weather:Database:User"],
Password = _configuration["Weather:Database:Password"],
Database = _configuration["Weather:Database:Name"]
};
var connection = new NpgsqlConnection(connectionStringBuilder.ConnectionString);
connection.Open();
return connection;
}
public void StoreWeatherData(WeatherMessage weatherMessage)
{
using (var connection = CreateConnection())
{
var query = ResourceReader.GetString("Weather.Service.Data.Resources.CreateReading.sql");
connection.Execute(query, weatherMessage);
}
}
}
}

View File

@@ -0,0 +1,6 @@
INSERT INTO weather_reading (timestamp, wind_direction, wind_speed, humidity, humidity_temperature, rain, pressure,
pressure_temperature, battery_level, light_level, latitude, longitude, altitude,
satellite_count, gps_timestamp)
VALUES (:timestamp, :windDirection, :windSpeed, :humidity, :humidityTemperature, :rain, :pressure, :pressureTemperature,
:batteryLevel, :lightLevel, :latitude, :longitude, :altitude, :satelliteCount, :gpsTimestamp)
ON CONFLICT DO NOTHING

View File

@@ -0,0 +1,24 @@
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
CREATE TABLE IF NOT EXISTS weather_reading
(
timestamp timestamptz NOT NULL
CONSTRAINT weather_reading_pk
PRIMARY KEY,
wind_direction int NOT NULL,
wind_speed double precision NOT NULL,
humidity double precision NOT NULL,
humidity_temperature double precision NOT NULL,
rain double precision NOT NULL,
pressure double precision NOT NULL,
pressure_temperature double precision NOT NULL,
battery_level double precision NOT NULL,
light_level double precision NOT NULL,
latitude double precision NOT NULL,
longitude double precision NOT NULL,
altitude double precision NOT NULL,
satellite_count double precision NOT NULL,
gps_timestamp timestamptz NOT NULL
);
SELECT create_hypertable('weather_reading', 'timestamp', if_not_exists => TRUE);

View File

@@ -0,0 +1,119 @@
using JetBrains.Annotations;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Weather.Models;
using Weather.Service.Data;
namespace Weather.Service
{
[UsedImplicitly]
public class MessageHandler : IHostedService
{
private readonly IConfiguration _configuration;
private readonly Database _database;
private IConnection _queueConnection;
private IModel _queueModel;
private HubConnection _hubConnection;
private static DateTime _lastLogTime = DateTime.MinValue;
private static long _messageCount;
public MessageHandler(IConfiguration configuration, Database database)
{
_configuration = configuration;
_database = database;
}
public Task StartAsync(CancellationToken cancellationToken)
{
WriteLog("MessageHandler: Start");
var factory = new ConnectionFactory
{
HostName = _configuration["Weather:Queue:Host"],
UserName = _configuration["Weather:Queue:User"],
Password = _configuration["Weather:Queue:Password"]
};
_queueConnection = factory.CreateConnection();
_queueModel = _queueConnection.CreateModel();
_queueModel.QueueDeclare(_configuration["Weather:Queue:Name"], true, false, false, null);
var consumer = new EventingBasicConsumer(_queueModel);
consumer.Received += DeviceEventHandler;
_queueModel.BasicConsume(_configuration["Weather:Queue:Name"], true, consumer);
_hubConnection = new HubConnectionBuilder().WithUrl(_configuration["Hub:Weather"]).Build();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
WriteLog("MessageHandler: Stop");
_hubConnection.StopAsync(cancellationToken).Wait(cancellationToken);
_queueModel.Close();
_queueConnection.Close();
return Task.CompletedTask;
}
private void DeviceEventHandler(object model, BasicDeliverEventArgs eventArgs)
{
var body = eventArgs.Body;
var message = Encoding.UTF8.GetString(body);
_messageCount++;
if ((DateTime.Now - _lastLogTime).TotalMinutes >= 1)
{
WriteLog($"Number of messages received since {_lastLogTime} = {_messageCount}");
_lastLogTime = DateTime.Now;
_messageCount = 0;
}
var weatherMessage = JsonConvert.DeserializeObject<WeatherMessage>(message);
if (weatherMessage.Type == MessageType.Text)
{
WriteLog(weatherMessage.Message);
return;
}
_database.StoreWeatherData(weatherMessage);
try
{
if (_hubConnection.State == HubConnectionState.Disconnected)
_hubConnection.StartAsync().Wait();
_hubConnection.InvokeAsync("SendLatestReading", message).Wait();
}
catch (Exception exception)
{
WriteLog($"Hub exception: {exception}");
}
}
private static void WriteLog(string message)
{
Console.WriteLine(message);
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace Weather.Service
{
public static class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
private static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddEnvironmentVariables();
}).UseStartup<Startup>();
}
}
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Service": {
"commandName": "Project",
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5001"
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;
namespace Weather.Service
{
public static class ResourceReader
{
public static string GetString(string resourceName)
{
var assembly = Assembly.GetExecutingAssembly();
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
if (stream == null)
{
throw new Exception($"Resource {resourceName} not found in {assembly.FullName}. Valid resources are: {string.Join(", ", assembly.GetManifestResourceNames())}.");
}
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<AssemblyName>Weather.Service</AssemblyName>
<RootNamespace>Weather.Service</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Remove="Data\Resources\CreateReading.sql" />
<None Remove="Data\Resources\Schema.sql" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Data\Resources\CreateReading.sql" />
<EmbeddedResource Include="Data\Resources\Schema.sql" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="1.60.6" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="1.1.0" />
<PackageReference Include="Npgsql" Version="4.0.7" />
<PackageReference Include="RabbitMQ.Client" Version="5.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Models\Models.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup>
<ActiveDebugProfile>Service</ActiveDebugProfile>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Weather.Service.Data;
namespace Weather.Service
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<Database>();
services.AddHostedService<MessageHandler>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder applicationBuilder, IHostingEnvironment environment)
{
if (environment.IsDevelopment())
applicationBuilder.UseDeveloperExceptionPage();
var database = applicationBuilder.ApplicationServices.GetService<Database>();
database.EnsureDatabase();
applicationBuilder.UseMvc();
}
}
}

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View File

@@ -0,0 +1,18 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"Weather": {
"Database": {
"Name": "weather"
},
"Queue": {
"Name": "weather"
}
},
"Hub": {
"Weather": "http://hub-server/weatherHub"
}
}

View File

@@ -0,0 +1,68 @@
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: weather-database
namespace: home-monitor
labels:
app: weather-database
spec:
replicas: 1
selector:
matchLabels:
app: weather-database
serviceName: weather-database
template:
metadata:
labels:
app: weather-database
spec:
containers:
- name: weather-database
image: timescale/timescaledb
terminationMessagePath: "/dev/termination-log"
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: weather-database-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: weather-database-credentials
key: password
- name: POSTGRES_DB
value: weather
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
nodeSelector:
kubernetes.io/hostname: kubernetes
schedulerName: default-scheduler
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: local-path
resources:
requests:
storage: 4Gi
---
kind: Service
apiVersion: v1
metadata:
name: weather-database
spec:
ports:
- name: client
port: 5432
selector:
app: weather-database
type: ClusterIP

View File

@@ -0,0 +1,59 @@
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: weather-service
namespace: home-monitor
labels:
app: weather-service
spec:
replicas: 1
selector:
matchLabels:
app: weather-service
template:
metadata:
labels:
app: weather-service
spec:
containers:
- name: weather-service
image: ckaczor/home-monitor-weather-service:latest
terminationMessagePath: "/dev/termination-log"
terminationMessagePolicy: File
imagePullPolicy: Always
securityContext:
privileged: true
env:
- name: Weather__Queue__Host
value: weather-queue
- name: Weather__Queue__User
valueFrom:
secretKeyRef:
name: weather-queue-credentials
key: username
- name: Weather__Queue__Password
valueFrom:
secretKeyRef:
name: weather-queue-credentials
key: password
- name: Weather__Database__Host
value: weather-database
- name: Weather__Database__User
valueFrom:
secretKeyRef:
name: weather-database-credentials
key: username
- name: Weather__Database__Password
valueFrom:
secretKeyRef:
name: weather-database-credentials
key: password
- name: Hub__Weather
value: http://hub-service/weatherHub
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
nodeSelector:
kubernetes.io/hostname: kubernetes
schedulerName: default-scheduler