Skip to content

Commit d9140d3

Browse files
committed
Merge branch 'develop'
2 parents bc8628b + 746d71e commit d9140d3

26 files changed

+548
-180
lines changed

docs-src/configuration.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ Configuration is accomplished by modifying the appsettings.json file that comes
1313
"UseCompression": false,
1414
"PrimaryQueueConsumers": 16,
1515
"SecondaryQueueConsumers": 4,
16-
"QueueName": "InEngine:Queue",
16+
"QueueDriver": "redis",
17+
"QueueName": "InEngineQueue",
1718
"RedisHost": "localhost",
1819
"RedisPort": 6379,
1920
"RedisDb": 0,
@@ -39,6 +40,7 @@ Configuration is accomplished by modifying the appsettings.json file that comes
3940
| UseCompression | bool | A situation performance optimization that compresses queued messages. |
4041
| PrimaryQueueConsumers | string | The number of consumers to schedule for the secondary queue. |
4142
| SecondaryQueueConsumers | string | The number of consumers to schedule for the secondary queue. |
43+
| QueueDriver | string | The driver to use to interact with a queue data store. |
4244
| QueueName | string | The base name of the queue, used to form the Redis Queue keys. |
4345
| RedisHost | string | The Redis hostname to connect to. |
4446
| RedisPort | integer | Redis's port. |

docs-src/index.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
InEngine.NET allows commands to be [queued](queuing), [scheduled](scheduling), and run directly.
1+
InEngine.NET is a plugin-based software application that allows commands to be [queued](queuing), [scheduled](scheduling), and run directly.
22

33
## How does it work?
44

55
InEngine.NET uses a plugin system to dynamically load .NET assemblies and execute code.
66
It also has a built-in command for launching external non-.NET programs.
77

8-
Get started by pulling the latest binaries from the [latest release](https://github.com/InEngine-NET/InEngine.NET/releases) on GitHub.
8+
Get started by pulling the binaries from the [latest release](https://github.com/InEngine-NET/InEngine.NET/releases) on GitHub.
99

10-
Then run a [command](commands):
10+
Then run a command the **echo** command from the core plugin:
1111

1212
```bash
1313
inengine.exe -pInEngine.Core echo --text"Hello, world"
1414
```
15-
Or if you\'re a Linux or Mac OS X fan (like me!), use the **inengine** shell script ([Mono](http://www.mono-project.com/download/) required.):
15+
Or if you're a Linux or Mac OS X fan (like me!), use the **inengine** shell script ([Mono](http://www.mono-project.com/download/) is required):
1616

1717
```bash
18-
# or if you\'re a Linux fan like me...
19-
2018
inengine -pInEngine.Core echo --text"Hello, world"
2119
```
2220

@@ -32,7 +30,11 @@ Now run a command in a container:
3230
docker run --rm inengine -pInEngine.Core echo --text"Hello, world"
3331
```
3432

35-
Want to queue our example echo command to run in (possibly) another server?
33+
## How does queueing work?
34+
35+
There are a lot of [queuing](queuing) features, but this is the gist...
36+
37+
Want to queue our example echo command to run in the background or possibly on another server?
3638

3739
Use the core plugin's **queue:publish** command:
3840

@@ -48,11 +50,11 @@ Use the core plugin's **queue:consume** command of course:
4850
inengine.exe -pInEngine.Core queue:consume
4951
```
5052

51-
But what if I want to run some non-.NET code?
53+
## How do I run non-.NET commands?
5254

53-
Use the core plugin's **proc** command to execute any program you like.
55+
There is a special **proc** command in the core plugin that allows for the execution of any program you can run at the command line.
5456

55-
Create a hello world python script called **helloworld.py**:
57+
For example, create a python script called **helloworld.py** that contains this:
5658

5759
```python
5860
print 'Hello, world!'

docs-src/queuing.md

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@
22

33
InEngine.NET's queue functionality allows for commands to be run in the background with a simple publish/consume model.
44

5-
## Prerequisites
5+
## Queue Drivers
66

7-
Redis is required to use the InEngine.NET queue feature.
8-
It can be installed on Ubuntu with this command:
7+
To make use of queue features, a queue driver must be specified in [appsettings.json](configuration).
8+
These are the available drivers...
9+
10+
### File
11+
12+
The file driver writes queued messages to the file system.
13+
It is useful for testing and development, but probably not suitable for production.
14+
15+
### Redis
16+
17+
Redis is suitable for production use.
18+
InEngine.NET utilizes Redis' durable queue features which mean messages will not be lost if InEngine.NET unexpectedly fails.
19+
20+
Redis can be installed on Ubuntu with this command:
921

1022
```bash
1123
sudo apt-get install redis-server
@@ -19,22 +31,78 @@ sudo service redis start
1931

2032
<div class="alert alert-info">
2133
It is highly recommended to <a href="https://redis.io/topics/security#authentication-feature">set a password</a> for Redis.
22-
</div>
34+
</div>
35+
36+
### Sync
37+
38+
The sync driver causes the publish command to run a published command synchronously.
39+
All other queue commands and methods are not supported and will throw an exception if called.
40+
This driver can be useful for plugin development and testing.
2341

2442
## Publishing Commands
2543

26-
### From Code
44+
### With C# Classes
45+
46+
[Commands](commands) can be published programmatically with the **InEngine.Core.Queuing.Queue** class:
47+
48+
```c#
49+
Queue.Make().Publish(new MyCommand());
50+
```
51+
52+
Or publish to the secondary queue by passing true to the Make method:
53+
54+
```c#
55+
Queue.Make(true).Publish(new MyCommand());
56+
```
57+
58+
!!! note "Do I have to use Queue.Make()?"
59+
Queue.Make() is a factory method that autoloads the queue settings from appsettings.json, creates the appropriate queue driver, and returns an instance of Queue.
60+
You can create your own Queue object an initialize it if you want.
61+
At the very least you can assign the object returned by Queue.Make() to a local variable or load it into a DI container for later use.
62+
63+
### With Lambda Expressions
64+
65+
Lambda expressions, aka anonymous functions, can be queued.
66+
The disadvantage to queuing lambdas is that the helpful functionality available in **InEngine.Core.AbstractCommand** is not available.
67+
68+
This is how you queue a lambda:
69+
70+
```c#
71+
Queue.Make().Publish(() => Console.WriteLine("Hello, world!"));
72+
```
73+
74+
Here is a neat shortcut for commands without parameters:
75+
76+
```c#
77+
Queue.Make().Publish(() => Foo.Bar());
78+
// Can be rewritten as...
79+
Queue.Make().Publish(Foo.Bar);
80+
```
81+
82+
### Sequentially In a Chain
83+
84+
Chained commands run in the order specified.
85+
This is useful for when order matters.
86+
87+
Also, if one command in the chain fails, then subsequent commands are not run at all.
88+
This affords the opportunity to add additional code that records which command failed, then resuming the command chain where it left off.
2789

28-
[Commands](commands) can be published programmatically with the **InEngine.Core.Queuing.Broker** class:
90+
Here is a an example of how to chain an imaginary file transfer command together:
2991

3092
```c#
31-
Broker.Make().Publish(new MyCommand());
93+
Subject.Publish(new[] {
94+
new MyFileTransfer(filePath1),
95+
new MyFileTransfer(filePath2),
96+
new MyFileTransfer(filePath3),
97+
});
3298
```
3399

34-
Or publish to the secondary queue:
35100

36101
```c#
37-
Broker.Make(true).Publish(new MyCommand());
102+
Subject.Publish(new List<AbstractCommand>() {
103+
new AlwaysSucceed(),
104+
new Echo() { VerbatimText = "Hello, world!"},
105+
});
38106
```
39107

40108
### From the Command Line
@@ -62,17 +130,17 @@ inengine.exe -pInEngine.Core queue:publish --command-plugin=InEngine.Core.dll --
62130
## Consuming Commands
63131

64132
### From Code
65-
Consuming a command is also accomplished with the Broker class:
133+
Consuming a command is also accomplished with the Queue class:
66134

67135
```c#
68-
Broker.Make().Consume();
136+
Queue.Make().Consume();
69137
```
70138

71139
The make method takes an optional second argument to indicate if the secondary queue should be used instead of the primary queue.
72140

73141
```c#
74142
// Uses secondary queue.
75-
Broker.Make(true).Consume();
143+
Queue.Make(true).Consume();
76144
```
77145

78146
Commands can be consumed from the command line as well with this simple command:
@@ -104,7 +172,7 @@ The **queue:length** command shows a quick summary of pending, in-progress, and
104172
inengine.exe -pInEngine.Core queue:length
105173
```
106174

107-
### Peek at Commands
175+
### Peek at Queued Commands
108176

109177
The **queue:peek** command allows for queued commands to be inspected:
110178

docs-src/scheduling.md

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,45 @@ A job schedule is created by adding a class to your plugin assembly that impleme
88

99
```c#
1010
using System;
11-
using Quartz;
1211

1312
namespace MyCommandPlugin
1413
{
1514
public class Jobs : IJobs
1615
{
17-
public void Schedule(IScheduler scheduler)
16+
public void Schedule(Schedule schedule)
1817
{
1918
// Schedule some jobs
2019
}
2120
}
2221
}
2322
```
2423

25-
This class is automatically discovered by the InEngine.NET scheduler.
24+
The InEngine.NET scheduler automatically discovers this class.
2625
It will call the Jobs.Schedule method with an initialized **InEngine.Scheduling.Schedule** object.
2726

2827
```c#
2928
using System;
30-
using Quartz;
3129

3230
namespace MyCommandPlugin
3331
{
3432
public class Jobs : IJobs
3533
{
3634
public void Schedule(Schedule schedule)
3735
{
36+
/*
37+
* Run MyCommand every five minutes.
38+
*/
3839
schedule.Job(new MyCommand()).EveryFiveMinutes();
40+
41+
/*
42+
* Run a lambda expression every ten minutes.
43+
*/
44+
schedule.Job(() => Console.WriteLine("Hello, world!")).EveryTenMinutes();
45+
46+
/*
47+
* Run a parameterless method every five minutes.
48+
*/
49+
schedule.Job(() => Foo.Bar).EverySecond();
3950
}
4051
}
4152
}
@@ -219,10 +230,22 @@ sudo supervisorctl reread
219230
sudo supervisorctl update
220231
```
221232

222-
Now, simply start the InEngine Scheduler.
233+
Now, simply start the scheduler workers with the **supervisorctl** program:
223234

224235
```bash
225236
sudo supervisorctl start inengine-scheduler:*
226237
```
227238

239+
### In a Container with Docker
240+
241+
Install [Docker](https://www.docker.com/what-docker) first, then pull the **ethanhann/inengine** image:
242+
243+
```bash
244+
docker pull ethanhann/inengine:latest
245+
```
246+
247+
Now run the scheduler:
228248

249+
```bash
250+
docker run --rm inengine -s
251+
```

mkdocs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ copyright: © 2017 - Ethan Hann
88
edit_uri: ''
99

1010
pages:
11-
- Getting Started: 'index.md'
11+
- Quickstart: 'index.md'
1212
- Documentation:
1313
- commands.md
1414
- scheduling.md
@@ -26,7 +26,7 @@ theme:
2626
primary: 'blue'
2727
accent: 'blue'
2828
feature:
29-
tabs: false
29+
tabs: true
3030

3131
markdown_extensions:
3232
- admonition
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System.Collections.Generic;
2+
using BeekmanLabs.UnitTesting;
3+
using InEngine.Commands;
4+
using InEngine.Core.Commands;
5+
using InEngine.Core.Exceptions;
6+
using Moq;
7+
using NUnit.Framework;
8+
using Quartz;
9+
10+
namespace InEngine.Core.Test.Commands
11+
{
12+
[TestFixture]
13+
public class ChainTest : TestBase<Chain>
14+
{
15+
[SetUp]
16+
public void Setup()
17+
{
18+
}
19+
20+
[Test]
21+
public void ShouldRunChainOfCommands()
22+
{
23+
var mockCommand1 = new Mock<AbstractCommand>();
24+
var mockCommand2 = new Mock<AbstractCommand>();
25+
var commands = new[] {
26+
mockCommand1.Object,
27+
mockCommand2.Object,
28+
};
29+
Subject.Commands = commands;
30+
31+
Subject.Run();
32+
33+
mockCommand1.Verify(x => x.Run(), Times.Once());
34+
mockCommand2.Verify(x => x.Run(), Times.Once());
35+
}
36+
37+
[Test]
38+
public void ShouldRunChainOfCommandsAndFail()
39+
{
40+
var mockCommand1 = new Mock<AlwaysFail>();
41+
var mockCommand2 = new Mock<AlwaysFail>();
42+
var alwaysFail = new AlwaysFail();
43+
var commands = new[] {
44+
mockCommand1.Object,
45+
new AlwaysFail(),
46+
mockCommand2.Object,
47+
};
48+
Subject.Commands = commands;
49+
50+
Assert.That(Subject.Run, Throws.TypeOf<CommandChainFailedException>());
51+
52+
mockCommand1.Verify(x => x.Run(), Times.Once());
53+
mockCommand2.Verify(x => x.Run(), Times.Never());
54+
}
55+
56+
[Test]
57+
public void ShouldRunChainOfDifferentCommands()
58+
{
59+
Subject.Commands = new List<AbstractCommand>() {
60+
new AlwaysSucceed(),
61+
new Echo() { VerbatimText = "Hello, world!"},
62+
};
63+
64+
Subject.Run();
65+
}
66+
67+
[Test]
68+
public void ShouldRunChainOfDifferentCommandsAsAbstractCommand()
69+
{
70+
Subject.Commands = new[] {
71+
new AlwaysSucceed() as AbstractCommand,
72+
new Echo() { VerbatimText = "Hello, world!"} as AbstractCommand,
73+
};
74+
75+
Subject.Run();
76+
}
77+
}
78+
}

src/InEngine.Core.Test/InEngine.Core.Test.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,9 @@
2828
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2929
</None>
3030
</ItemGroup>
31+
<ItemGroup>
32+
<Folder Include="Scheduling\" />
33+
<Folder Include="Commands\" />
34+
</ItemGroup>
3135
</Project>
3236

0 commit comments

Comments
 (0)