ntpa/Ntp.Analyzer.Process/Initializer.cs

498 lines
17 KiB
C#

//
// Copyright (c) 2013-2017 Carsten Sonne Larsen <cs@innolan.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using Ntp.Analyzer.Config;
using Ntp.Analyzer.Config.Node;
using Ntp.Analyzer.Data;
using Ntp.Analyzer.Data.Import;
using Ntp.Analyzer.Export;
using Ntp.Analyzer.Import;
using Ntp.Analyzer.Monitor.Client;
using Ntp.Analyzer.Monitor.Server;
using Ntp.Analyzer.Objects;
using Ntp.Analyzer.Process.Description;
using Ntp.Analyzer.Process.Log;
using Ntp.Common.App;
using Ntp.Common.IO;
using Ntp.Common.Log;
using Ntp.Common.Process;
using Ntp.Common.System;
using Ntp.Data;
using Ntp.Data.Provider;
namespace Ntp.Analyzer.Process
{
public sealed class Initializer
{
/// <summary>
/// Initializes a new instance of the <see cref="Initializer" /> class.
/// </summary>
/// <param name="configFile">Config file to use.</param>
/// <param name="pid">Process ID.</param>
/// <param name="pidFile">Where to write Process ID file.</param>
/// <param name="name">Name of process ID. Used when shutting down.</param>
/// <param name="initlog">Log used during initialization.</param>
public Initializer(string configFile, int pid, string pidFile, string name, LogGroup initlog)
{
this.configFile = configFile;
this.pid = pid;
this.pidFile = pidFile;
this.name = name;
this.initlog = initlog;
Nodes = new List<IRequest>();
Listeners = new List<Listener>();
version = "0.8.2";
}
private static bool firstrun = true;
private readonly string configFile;
private readonly LogGroup initlog;
private readonly string name;
private readonly int pid;
private readonly string pidFile;
private readonly string version;
private Configuration config;
public SignalHandler Controller { get; private set; }
public Scheduler Scheduler { get; private set; }
public LogBase Log { get; private set; }
public List<IRequest> Nodes { get; }
public List<Listener> Listeners { get; }
public bool Ready { get; private set; }
/// <summary>
/// Run the NTP Analyzer.
/// </summary>
public void Run()
{
// Neutralize.
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
bool proceed = InitializeConfiguration();
if (proceed)
proceed = InitializeSecurity();
if (proceed)
proceed = InitializeLog();
if (proceed)
{
InitializeApplication();
InitializeListeners();
InitializeCluster();
InitializeScheduler();
proceed = InitializeDatabase();
}
if (proceed)
InitializeData();
Ready = proceed;
firstrun = false;
}
/// <summary>
/// Initializes the application and database state.
/// </summary>
private void InitializeApplication()
{
// Start signal controller
Controller = new SignalHandler(name, initlog);
Controller.Start();
// Initialize application state
ApplicationState.Version = version;
ApplicationState.Pid = pid;
ApplicationState.Name = name;
ApplicationState.Config = config;
ApplicationState.ConfigFile = configFile;
ApplicationState.Log = Log;
if (config.Heartbeat == null)
return;
var beat = new Heartbeat(Log, config.Heartbeat.Rate);
beat.Start();
}
/// <summary>
/// Initializes the cluster nodes.
/// </summary>
private void InitializeCluster()
{
if (config.Cluster == null)
return;
try
{
foreach (var node in config.Cluster.Nodes)
{
var ip = IPAddress.Parse(node.Ip);
var req = new TextRequest(ip, node.Port);
Nodes.Add(req);
Log.ClusterReady(node);
}
}
catch (Exception e)
{
initlog.ClusterError(e);
}
}
/// <summary>
/// Initializes the configuration.
/// </summary>
private bool InitializeConfiguration()
{
if (!File.Exists(configFile))
{
initlog.NoConfig(configFile);
return false;
}
var builder = new ConfigBuilder(configFile);
config = builder.Execute();
if (!builder.Errors.Any() && config != null)
return true;
initlog.AddErrors(builder.Errors);
return false;
}
private void InitializeData()
{
if (!config.Database.Initialize)
return;
TimeServerWebAdapter.Initialize(config.Database.Import);
try
{
// Initialize hosts
foreach (var server in config.Servers)
{
var host = DataFace.Instance.Hosts.SingleOrDefault(h => h.Id == server.HostId);
if (host == null)
{
IPAddress ip;
try
{
ip = Dns.GetHostAddresses(server.ServerName)[0];
}
catch
{
ip = IPAddress.None;
}
int? orgId = null;
if (TimeServers.List.ContainsKey(server.ServerName))
{
orgId = TimeServers.List[server.ServerName];
Log.KnownServer(orgId.Value, server.ServerName);
}
else if (!Equals(ip, IPAddress.None) && TimeServers.List.ContainsKey(ip.ToString()))
{
orgId = TimeServers.List[ip.ToString()];
Log.KnownServer(orgId.Value, ip.ToString());
}
host = new Host(server.HostId, server.ServerName, ip.ToString(), orgId);
DataFace.Instance.Hosts.Save(host);
Log.NewHost(server, ip);
}
else if (host.OrgId == null && TimeServers.List.ContainsKey(host.Ip))
{
host.OrgId = TimeServers.List[host.Ip];
Log.KnownServer(host.OrgId.Value, host.Ip);
DataFace.Instance.Hosts.Save(host);
}
else if (host.OrgId == null && TimeServers.List.ContainsKey(host.Name))
{
host.OrgId = TimeServers.List[host.Name];
Log.KnownServer(host.OrgId.Value, host.Name);
DataFace.Instance.Hosts.Save(host);
}
// Flush old association entries
DataFace.Instance.AssociationEntries.Delete(server.HostId);
// Initialize peers
var peerImporter = ImportFactory.CreatePeerImporter(server.ServerName, server.ServerType, host, Log);
foreach (var entry in peerImporter.ToList())
{
var currentEntry = entry;
var peerList = DataFace.Instance.Peers.Where(p => p.Ip == currentEntry.Remote).ToList();
if (!peerList.Any())
{
// Create new peer in database
TimeServer timeServer = null;
if (TimeServers.List.ContainsKey(entry.Remote))
{
int orgId = TimeServers.List[entry.Remote];
timeServer = DataFace.Instance.Servers[orgId];
Log.KnownServer(orgId, entry.Remote);
}
var hostName = entry.Remote;
try
{
var hostEntry = Dns.GetHostEntry(entry.Remote);
hostName = hostEntry.HostName;
}
catch (Exception e)
{
Log.HostNameNotFound(entry.Remote, e);
}
var peer = new Peer(hostName, entry.Remote, timeServer);
DataFace.Instance.Peers.Save(peer);
Log.NewPeer(entry);
}
else if (TimeServers.List.ContainsKey(entry.Remote))
{
// Try to update existing peer
var peer = peerList.First();
if (peer.Server != null && peer.Server.IsOrgServer)
continue;
int orgId = TimeServers.List[entry.Remote];
var timeServer = DataFace.Instance.Servers[orgId];
Log.KnownServer(orgId, entry.Remote);
if (timeServer == null)
continue;
peer.Server = timeServer;
DataFace.Instance.Peers.Save(peer);
}
}
}
}
catch (Exception e)
{
Log.TableError(e);
}
}
private bool InitializeDatabase()
{
SqlDatabaseFactory.Initialize(config.Database);
DataFace.Initialize(Log);
var checker = new SqlDatabaseChecker(SqlDatabaseFactory.Instance, Controller, initlog);
checker.CheckConnection();
if (Controller.Stopped)
return false;
try
{
var initializer = new DatabaseInitializer(
config.Database.Initialize,
config.Database.Upgrade,
Log);
return initializer.Execute();
}
catch (Exception e)
{
initlog.DatabaseError(e);
}
return false;
}
/// <summary>
/// Initializes the listeners.
/// </summary>
private void InitializeListeners()
{
try
{
foreach (var monitor in config.Monitors)
{
var listener = new Listener(monitor.Ip, monitor.Port, Log);
listener.Open();
Listeners.Add(listener);
initlog.ListenerReady(listener);
}
}
catch (Exception e)
{
initlog.ListenerError(e);
}
}
/// <summary>
/// Initializes the log.
/// </summary>
private bool InitializeLog()
{
try
{
Log = LogFactory.CreateLog(config.Log);
Log.Initialize();
}
catch (Exception e)
{
initlog.InitializationError(e);
return false;
}
initlog.Add(Log);
if (firstrun)
initlog.Starting(version);
initlog.ConfigFile(configFile);
initlog.ProcessId(pid);
initlog.InstanceName(name);
if (config.Permission?.AppUserId == null)
return true;
if (Permission.SetUserId(config.Permission.AppUserId.Value))
initlog.UserId(config.Permission.AppUserId.Value);
return true;
}
/// <summary>
/// Initializes the scheduler.
/// </summary>
private void InitializeScheduler()
{
Job.Reset();
try
{
Scheduler = new Scheduler(Log);
Log.JobInitStart();
foreach (var bulk in config.Bulks)
Scheduler.Add(new BulkStatJob(bulk, Scheduler.Log));
// Add jobs to schedule.
foreach (var server in config.Servers)
{
Scheduler.Add(new HostStatJob(server.HostStats, Scheduler.Log));
Scheduler.Add(new HostIoStatJob(server.HostIoStats, Scheduler.Log));
Scheduler.Add(new PeerStatJob(server.PeerStats, Scheduler.Log));
Scheduler.Add(new DriftStatJob(server.DriftStats, Scheduler.Log));
Scheduler.Add(new AboutPageJob(server.Menu, server.AboutPage, Scheduler.Log));
foreach (var peerPage in server.PeerPages)
Scheduler.Add(new PeerPageJob(server.Menu, peerPage, Scheduler.Log));
foreach (var hostPage in server.HostPages)
Scheduler.Add(new HostPageJob(server.Menu, hostPage, Scheduler.Log));
foreach (var hostGraph in server.HostGraphs)
Scheduler.Add(new HostGraphJob(hostGraph, Scheduler.Log));
foreach (var trafficGraph in server.TrafficGraphs)
Scheduler.Add(new TrafficGraphJob(trafficGraph, Scheduler.Log));
foreach (var peerGraph in server.PeerGraphs)
Scheduler.Add(new PeerGraphJob(peerGraph, Scheduler.Log));
foreach (var peerSummaryPage in server.PeerSummaryPages)
Scheduler.Add(new PeerSummaryJob(server.Menu, peerSummaryPage, Scheduler.Log));
Scheduler.Add(new HostGraphPageJob(server.HostGraphPage, Scheduler.Log));
Scheduler.Add(new PeerGraphPageJob(server.PeerGraphPage, Scheduler.Log));
}
// Add the surveillance/notify job.
if (config.Notify != null)
{
var adminJob = new NotifyJob(config.Notify, Log, pid, configFile);
Scheduler.Add(adminJob);
adminJob.SetJobList(Scheduler);
}
Log.JobInitEnd();
}
catch (Exception e)
{
initlog.SchedulerError(e);
}
ApplicationState.Scheduler = Scheduler;
ApplicationState.StartupTime = Scheduler.StartTime;
}
/// <summary>
/// Initializes the security settings.
/// </summary>
private bool InitializeSecurity()
{
// Drop privileges
if (config.Permission?.AppUserId != null)
{
if (!Permission.SetUserId(config.Permission.AppUserId.Value))
{
initlog.UserIdError(config.Permission.AppUserId.Value);
}
}
// Create pid file
if (pidFile != null)
{
try
{
File.WriteAllText(pidFile, pid.ToString(CultureInfo.InvariantCulture));
}
catch (Exception e)
{
initlog.PidFileError(e);
return false;
}
}
// Set permissions
if (config.Permission == null)
return true;
FileSystemDestination.FileMask = config.Permission.FileMode;
FileSystemDestination.FileUserId = config.Permission?.UserId;
FileSystemDestination.FileGroupId = config.Permission?.GroupId;
return true;
}
}
}