Compare commits

...

3 Commits

10 changed files with 301 additions and 50 deletions

View File

@ -8,6 +8,8 @@ using MBS.Core;
using Mocha.Core;
using Mocha.Core.UI.Server;
using Mocha.Core.Logging;
using Mocha.Core.Logging.Loggers;
/// <summary>
/// Provides the entry point for a simple application which starts a Web server
@ -33,8 +35,6 @@ public class Program : WebApplication
private LibraryHandle l_System, l_Web;
public Oms Oms { get; }
private System.IO.StreamWriter? logSt = null;
protected override WebServer CreateWebServer()
{
@ -56,21 +56,15 @@ public class Program : WebApplication
Console.Error.WriteLine("oms: error: failed to write PID file; graceful control will be unavailable");
}
string logFile = "/var/log/mocha/oms-dotnet.log";
try
{
OpenLogFile(logFile);
}
catch (UnauthorizedAccessException ex)
{
logFile = System.IO.Path.Combine(new string[] { System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "oms-dotnet.log" });
OpenLogFile(logFile);
}
DefaultFileLogger logger = new DefaultFileLogger();
ConsoleLogger logger2 = new ConsoleLogger();
Log.Loggers.Add(logger);
Log.Loggers.Add(logger2);
logSt?.WriteLine("**********");
logSt?.WriteLine("Mocha .NET OMS, version {0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
logSt?.WriteLine(String.Format("{0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString()));
logSt?.WriteLine("**********");
Log.WriteLine("**********");
Log.WriteLine("Mocha .NET OMS, version {0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
Log.WriteLine(String.Format("{0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString()));
Log.WriteLine("**********");
// this all has to be done before the Web server is started...
Oms.Initialize();
@ -132,17 +126,6 @@ public class Program : WebApplication
base.OnStartup(e);
}
private void OpenLogFile(string logFile)
{
string logDir = System.IO.Path.GetDirectoryName(logFile);
if (!System.IO.Directory.Exists(logDir))
{
System.IO.Directory.CreateDirectory(logDir);
}
logSt = new StreamWriter(System.IO.File.Open(logFile, FileMode.Append, FileAccess.Write, FileShare.Read), System.Text.Encoding.UTF8);
logSt.AutoFlush = true;
}
private bool UpdateMochafile()
{
bool enableMochaFile = true;
@ -151,11 +134,11 @@ public class Program : WebApplication
// check to see if we have a Mochafile that we need to load
// (this should only be done on development build)
string Mochafilename = "/var/mocha/uploads/Mochafile.json";
logSt.WriteLine(String.Format("oms-dotnet: looking for Mochafile '{0}'", Mochafilename));
Log.WriteLine("oms-dotnet: looking for Mochafile '{0}'", Mochafilename);
if (System.IO.File.Exists(Mochafilename))
{
logSt.WriteLine("oms-dotnet: Mochafile found");
Log.WriteLine("oms-dotnet: Mochafile found");
if (LoadMochafile(Mochafilename))
{
return true;
@ -163,24 +146,24 @@ public class Program : WebApplication
}
else
{
logSt.WriteLine("oms-dotnet: Mochafile not found");
Log.WriteLine("oms-dotnet: Mochafile not found");
}
}
else
{
logSt.WriteLine("oms-dotnet: skipping Mochafile processing (not enabled)");
Log.WriteLine("oms-dotnet: skipping Mochafile processing (not enabled)");
}
return false;
}
private bool LoadMochafile(string mochafilename)
{
logSt?.Write(String.Format("oms-dotnet: loading Mochafile at '{0}'", mochafilename));
Log.WriteLine("oms-dotnet: loading Mochafile at '{0}'", mochafilename);
JsonObject? json = JsonNode.Parse(System.IO.File.ReadAllText(mochafilename)) as JsonObject;
if (json == null)
{
logSt?.Write("oms-dotnet: Mochafile error: could not parse JSON");
Log.WriteLine("oms-dotnet: Mochafile error: could not parse JSON");
return false;
}
@ -193,16 +176,16 @@ public class Program : WebApplication
if (jo.ContainsKey("name"))
{
string name = jo["name"].GetValue<string>();
logSt?.Write(String.Format("oms-dotnet: Mochafile says create tenant '{0}'", name));
Log.WriteLine("oms-dotnet: Mochafile says create tenant '{0}'", name);
TenantHandle th = Oms.CreateTenant(name);
logSt?.Write(String.Format("oms-dotnet: create tenant ok, handle is '{0}'", th));
Log.WriteLine("oms-dotnet: create tenant ok, handle is '{0}'", th);
}
}
}
}
logSt?.Write("oms-dotnet: Mochafile processing complete");
Log.WriteLine("oms-dotnet: Mochafile processing complete");
return true;
}

View File

@ -31,6 +31,17 @@ public abstract class InstanceCommand : TenantedCommand
if (e.Context.Request.PathParts.Length < 5)
return true;
string tenantName = e.Context.Request.PathParts[2];
TenantHandle th = oms.GetTenantByName(tenantName);
if (th != TenantHandle.Empty)
{
oms.SelectTenant(th);
}
else
{
Console.Error.WriteLine("failed to select tenant '{0}'", tenantName);
}
string iid = e.Context.Request.PathParts[4];
if (oms.TryParseInstanceRef(iid, out InstanceHandle ih))
{

View File

@ -48,19 +48,29 @@ public class TenantsListCommand : OmsServerCommand
}
else if (e.Context.Request.PathParts.Length > 3)
{
if (e.Context.Request.PathParts[3] == "instances")
if (e.Context.Request.PathParts[3] == "create")
{
string tenantName = e.Context.Request.PathParts[2];
TenantHandle th = oms.CreateTenant(tenantName);
JsonObject obj = new JsonObject();
obj.Add("result", JsonValue.Create("success"));
obj.Add("tenantHandle", th.ToString());
sw.Write(obj.ToJsonString());
}
else if (e.Context.Request.PathParts[3] == "instances")
{
if (e.Context.Request.PathParts.Length > 4)
{
if (e.Context.Request.PathParts[4] == "create")
{
}
else if (e.Context.Request.PathParts.Length >= 5)
{
//
oms.TryParseInstanceRef(e.Context.Request.PathParts[4], out InstanceHandle h);
if (e.Context.Request.PathParts.Length >= 6)
{
string command = e.Context.Request.PathParts[5];
@ -69,7 +79,7 @@ public class TenantsListCommand : OmsServerCommand
/*
InstanceHandle instParent = oms.GetParentClass(ih);
List<InstanceHandle> relatedTasks = new List<InstanceHandle>();
$instInstance = $this->getInstanceByGlobalIdentifier(KnownClassGuids::Instance);
$relatedTasks0 = $this->getRelatedInstances($instInstance, KnownRelationshipGuids::Class__has_related__Task);
foreach ($relatedTasks0 as $task)
@ -103,7 +113,7 @@ public class TenantsListCommand : OmsServerCommand
}
else
{
}
}
}

View File

@ -0,0 +1,39 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// This file is part of Mocha.NET.
//
// Mocha.NET is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Mocha.NET is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Mocha.NET. If not, see <https://www.gnu.org/licenses/>.
namespace Mocha.Core.Logging;
public static class Log
{
public static List<Logger> Loggers { get; } = new List<Logger>();
public static void WriteLine(string text)
{
foreach (Logger logger in Loggers)
{
logger.WriteLine(text);
}
}
public static void WriteLine(string text, params object[] format)
{
foreach (Logger logger in Loggers)
{
logger.WriteLine(text, format);
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// This file is part of Mocha.NET.
//
// Mocha.NET is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Mocha.NET is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Mocha.NET. If not, see <https://www.gnu.org/licenses/>.
namespace Mocha.Core.Logging;
public class Logger
{
public Stream? Stream { get; protected set; }
private StreamWriter? _sw;
protected StreamWriter? StreamWriter
{
get
{
if (_sw == null)
{
if (Stream != null)
{
_sw = new StreamWriter(Stream, System.Text.Encoding.UTF8);
_sw.AutoFlush = true;
}
}
return _sw;
}
}
private System.IO.StreamWriter? sw = null;
protected Logger()
{
}
public Logger(Stream st)
{
Stream = st;
}
public void WriteLine(string text)
{
StreamWriter?.WriteLine(text);
}
public void WriteLine(string text, params object?[] format)
{
StreamWriter?.WriteLine(text, format);
}
}

View File

@ -0,0 +1,26 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// This file is part of Mocha.NET.
//
// Mocha.NET is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Mocha.NET is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Mocha.NET. If not, see <https://www.gnu.org/licenses/>.
namespace Mocha.Core.Logging.Loggers;
public class ConsoleLogger : Logger
{
public ConsoleLogger()
{
Stream = Console.OpenStandardOutput();
}
}

View File

@ -0,0 +1,47 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// This file is part of Mocha.NET.
//
// Mocha.NET is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Mocha.NET is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Mocha.NET. If not, see <https://www.gnu.org/licenses/>.
namespace Mocha.Core.Logging.Loggers;
public class DefaultFileLogger : Logger
{
public DefaultFileLogger()
{
string logFile = "/var/log/mocha/oms-dotnet.log";
try
{
OpenLogFile(logFile);
}
catch (UnauthorizedAccessException ex)
{
logFile = System.IO.Path.Combine(new string[] { System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "oms-dotnet.log" });
OpenLogFile(logFile);
}
}
private void OpenLogFile(string logFile)
{
string logDir = System.IO.Path.GetDirectoryName(logFile);
if (!System.IO.Directory.Exists(logDir))
{
System.IO.Directory.CreateDirectory(logDir);
}
Stream st = System.IO.File.Open(logFile, FileMode.Append, FileAccess.Write, FileShare.Read);
}
}

View File

@ -593,11 +593,18 @@ public abstract class Oms
public InstanceHandle GetParentClass(IInstanceReference ir)
{
InstanceHandle relInstance__for__Class = GetInstance(KnownRelationshipGuids.Instance__for__Class);
IEnumerable<InstanceHandle> irs = GetRelatedInstances(ir.GetHandle(), relInstance__for__Class);
if (irs.Count() > 0)
if (ir != null)
{
return irs.First();
InstanceHandle relInstance__for__Class = GetInstance(KnownRelationshipGuids.Instance__for__Class);
IEnumerable<InstanceHandle> irs = GetRelatedInstances(ir.GetHandle(), relInstance__for__Class);
if (irs.Count() > 0)
{
return irs.First();
}
}
else
{
}
return InstanceHandle.Empty;
}
@ -1113,6 +1120,12 @@ public abstract class Oms
InstanceHandle assignsToParm = GetRelatedInstance(parm, GetInstance(KnownRelationshipGuids.Parameter_Assignment__assigns_to__Work_Data));
IInstanceReference assignsFromWorkData = GetRelatedInstance(parm, GetInstance(KnownRelationshipGuids.Parameter_Assignment__assigns_from__Executable_returning_Work_Data));
if (assignsFromWorkData == null)
{
Console.Error.WriteLine("oms: error: assigns from work data not set for parameter assignment '{0}'", parm.GlobalIdentifier);
continue;
}
if (IsInstanceOf(assignsFromWorkData, GetInstance(KnownInstanceGuids.Classes.Class)))
{
assignsFromWorkData = context.GetWorkData<IInstanceReference>(assignsFromWorkData);

View File

@ -24,12 +24,12 @@ namespace Mocha.Core.Tests;
public class TenantedTests : OmsTestsBase
{
protected override void AfterSetup()
{
base.AfterSetup();
protected override void AfterSetup()
{
base.AfterSetup();
Oms.CreateTenant("omstest");
}
}
[Test]
public void Create_Class_On_First_Tenant_Doesnt_Show_Up_On_Second_Tenant()
@ -53,7 +53,7 @@ public class TenantedTests : OmsTestsBase
Oms.SelectTenant(Oms.GetTenantByName("super"));
Class cls = Oms.GetInstance<Class>(TEST_CLASS_GUID);
Oms.SelectTenant(Oms.GetTenantByName("omstest"));
Class cls2 = Oms.GetInstance<Class>(TEST_CLASS_GUID);
@ -77,4 +77,18 @@ public class TenantedTests : OmsTestsBase
InstanceHandle att2 = Oms.GetInstance(KnownAttributeGuids.Text.ContentType);
Assert.That(val, Is.Not.EqualTo("Test Value"));
}
[Test]
public void Get_Instance_After_Create_Tenant_Doesnt_Trash_Super_Tenant()
{
Oms.SelectTenant(Oms.GetTenantByName("super"));
Class cls = Oms.GetInstance<Class>(TEST_CLASS_GUID);
Assert.That(cls, Is.Not.Null);
Oms.CreateTenant("dummy");
Class cls2 = Oms.GetInstance<Class>(TEST_CLASS_GUID);
Assert.That(cls2, Is.Not.Null);
}
}

View File

@ -63,6 +63,55 @@ public class RemoteTests
Assert.That(json["tenants"].AsArray()[0].ToString(), Is.EqualTo("super"));
}
/// <summary>
/// Checks to make sure that the class Class [1$1] on the super tenant exists.
/// </summary>
/// <returns></returns>
[Test]
public async Task Get_Class_Instance()
{
HttpClient client = new HttpClient();
HttpResponseMessage resp = client.Send(new HttpRequestMessage(HttpMethod.Get, BuildUrl("/tenants/super/instances/1$1")));
Assert.That(resp.StatusCode, Is.EqualTo(System.Net.HttpStatusCode.OK));
Assert.That(resp.Content.Headers.ContentType.ToString(), Is.EqualTo("application/json"));
string content = await resp.Content.ReadAsStringAsync();
JsonObject json = JsonNode.Parse(content) as JsonObject;
Assert.That(json["globalIdentifier"].ToString(), Is.EqualTo(KnownInstanceGuids.Classes.Class.ToString()));
}
/// <summary>
/// Checks to make sure that the class Class [1$1] on the super tenant exists after a new tenant is created.
/// </summary>
/// <returns></returns>
[Test]
public async Task Get_Class_Instance_After_Create_Tenant()
{
// This test was failing not because the class did not exist, but because after the call to create a tenant,
// the call to request instance details WAS NOT SELECTING THE PROPER TENANT. In other words,
// /tenants/***/instances... would NEVER select the specified tenant. In this case, it was using the tenant
// already selected from the call to create, e.g. 'contoso1', rather than the tenant we requested in the API
// call, 'super'.
//
// This API call was ALWAYS broken!
HttpClient client = new HttpClient();
HttpResponseMessage resp1 = client.Send(new HttpRequestMessage(HttpMethod.Get, BuildUrl("/tenants/contoso1/create")));
string cvm = await resp1.Content.ReadAsStringAsync();
HttpResponseMessage resp = client.Send(new HttpRequestMessage(HttpMethod.Get, BuildUrl("/tenants/super/instances/1$1")));
Assert.That(resp.StatusCode, Is.EqualTo(System.Net.HttpStatusCode.OK));
Assert.That(resp.Content.Headers.ContentType.ToString(), Is.EqualTo("application/json"));
string content = await resp.Content.ReadAsStringAsync();
JsonObject json = JsonNode.Parse(content) as JsonObject;
Assert.That(json["globalIdentifier"].ToString(), Is.EqualTo(KnownInstanceGuids.Classes.Class.ToString()));
}
[Test]
public async Task Instance_Json_Test()
{