Added support for running C# code in a dotnet core environment

This commit is contained in:
Klavs Madsen
2017-01-21 13:57:45 +01:00
parent 8339e120ce
commit f5270d97b1
8 changed files with 369 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
FROM microsoft/dotnet:1.1.0-runtime
WORKDIR /fission-workdir
COPY out .
EXPOSE 8888
ENTRYPOINT ["dotnet"]
CMD ["fission-dotnet.dll"]
+65
View File
@@ -0,0 +1,65 @@
using Fission.DotNetCore.Compiler;
using System.Collections.Generic;
using Nancy;
using System.IO;
using System;
namespace Fission.DotNetCore
{
public class ExecutorModule : NancyModule
{
private const string CODE_PATH = "/userfunc/user";
private static Function _userFunc;
public ExecutorModule()
{
Post("/specialize", args => Specialize());
Get("/", _ => Run());
Post("/", _ => Run());
Put("/", _ => Run());
Head("/", _ => Run());
Options("/", _ => Run());
Delete("/", _ => Run());
}
private object Specialize()
{
var errors = new List<string>();
if (File.Exists(CODE_PATH))
{
var code = File.ReadAllText(CODE_PATH);
_userFunc = FissionCompiler.Compile(code, out errors);
if (_userFunc == null)
{
var errstr = string.Join(Environment.NewLine, errors);
Console.WriteLine(errstr);
var response = (Response)errstr;
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
}
return null;
}
else
{
var response = (Response)"Unable to locate code";
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
}
}
private object Run()
{
if (_userFunc == null)
{
var response = (Response)"Generic container: no requests supported";
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
}
var args = ((DynamicDictionary)Request.Query).ToDictionary();
return _userFunc.Invoke(args);
}
}
}
+59
View File
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
namespace Fission.DotNetCore.Compiler
{
//adapted from this article http://www.tugberkugurlu.com/archive/compiling-c-sharp-code-into-memory-and-executing-it-with-roslyn
class FissionCompiler
{
public static Function Compile(string code, out List<string> errors) {
errors = new List<string>();
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location)
};
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}");
}
}
else
{
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
var type = assembly.GetType("Fission");
var info = type.GetMember("Run").First() as MethodInfo;
return new Function(info);
}
}
return null;
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
using System.Reflection;
using System.Collections.Generic;
namespace Fission.DotNetCore.Compiler
{
class Function
{
private readonly MethodInfo _info;
public Function(MethodInfo info)
{
if (info == null) throw new ArgumentNullException(nameof(info));
_info = info;
}
public object Invoke(Dictionary<string, object> args)
{
return _info.Invoke(null, new[] { args });
}
}
}
+30
View File
@@ -0,0 +1,30 @@
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Nancy.Owin;
namespace Fission.DotNetCore
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseKestrel()
.UseUrls("http://*:8888")
.UseStartup<Startup>()
.Build();
host.Run();
}
}
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseOwin(x => x.UseNancy());
}
}
}
+158
View File
@@ -0,0 +1,158 @@
# Fission: dotnet C# Environment
This is a simple dotnet C# environment for Fission.
It's a Docker image containing the dotnet 1.1.0 runtime. The image
uses Kestrel with Nancy to host the internal web server and uses
Roslyn to compile the uploaded code.
The image supports compiling and running code with types defined in
mscorlib and does not at present support other library references.
One workaround for this would be to add the references to this project's
project.json file and rebuild the container.
The environment works via convention where you create a C# class
called Fission which has a static method named Run taking a single
parameter, a dictionary containing any querystring parameters.
Example of simplest possible class to be executed:
```
using System;
using System.Collections.Generic;
public class Fission {
public static string Run(Dictionary<string, object> args) {
return null;
}
}
```
Please see examples below.
## Rebuilding and pushing the image
To rebuild the image you need either a computer with dotnet 1.1.0
installed or else you will have to map the source directory into a
container containing the dotnet 1.1.0 environment.
### Locally installed Dotnet 1.1.0
Simply move to the source directory in a terminal and run the ./build.sh script.
The script will restore dependencies, compile a release build and
and build the container. If you need to change the name of the container
simply change it in the script.
After the build finishes push the new image to a Docker registry using the
standard procedure.
### Build in a container
Move to the directory containing the source and start the Docker container
with dotnet and mount the current directory to a build location:
```
docker run -it --rm -v $PWD:/build microsoft/dotnet
```
Move to the build directory inside the container and restore the packages:
```
cd /build
dotnet restore
log : Restoring packages for /source/project.json...
log : Installing System.Net.WebSockets 4.0.0.
log : Installing runtime.native.System.IO.Compression 4.1.0.
...
```
Compile and publish a release build of the source to the 'out' folder:
```
dotnet publish -c Release -o out
Publishing source for .NETCoreApp,Version
...
```
Exit the build container and build the Docker container on the local host:
```
exit
docker build -t USER/dotnet-env .
```
After the build finishes push the new image to a Docker registry using the
standard procedure.
## Echo example
### Setup fission environment
First you need to setup the fission according to your cluster setup as
specified here: https://github.com/fission/fission
### Create the class to run
Secondly you need to create a file /tmp/func.cs containing the following code:
```
using System;
using System.Collections.Generic;
public class Fission {
public static string Run(Dictionary<string, object> args) {
return (string)args["text"];
}
}
```
### Run the example
Lastly to run the example:
```
$ fission env create --name dotnet --image fission/dotnet-env
$ fission function create --name echo --env dotnet --code /tmp/func.cs
$ fission route create --method GET --url /echo --function echo
$ curl http://$FISSION_ROUTER/echo?text=hello%20world!
hello world
```
## Addition service example
### Setup fission environment
First you need to setup the fission according to your cluster setup as
specified here: https://github.com/fission/fission
### Create the class to run
Secondly you need to create a file /tmp/func.cs containing the following code:
```
using System;
using System.Collections.Generic;
public class Fission {
public static string Run(Dictionary<string, object> args) {
var x = Convert.ToInt32(args["x"]);
var y = Convert.ToInt32(args["y"]);
return (x+y).ToString();
}
}
```
### Run the example
Lastly to run the example:
```
$ fission env create --name dotnet --image fission/dotnet-env
$ fission function create --name addition --env dotnet --code /tmp/func.cs
$ fission route create --method GET --url /add --function addition
$ curl "http://$FISSION_ROUTER/add?x=30&y=12"
42
```
+3
View File
@@ -0,0 +1,3 @@
dotnet restore
dotnet publish -c Release -o out
docker build -t fission/dotnet-env .
+24
View File
@@ -0,0 +1,24 @@
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true,
"outputName": "fission-dotnet"
},
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.1.0",
"type": "platform"
},
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.Owin": "1.0.0",
"Nancy": "2.0.0-barneyrubble",
"Microsoft.CodeAnalysis.CSharp": "1.3.0-*",
"System.Runtime.Loader": "4.0.0-*",
},
"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}