Giter Site home page Giter Site logo

rulio's Introduction

Build Status

Rulio is a rules engine

Overview

A rules engine. You write rules and send events. You can also write some facts that rules can use. When an event arrives, the system finds candidate rules. A candidate rule's condition is evaluated to find zero or more sets of variable bindings. For each set of variable bindings, the rule's actions are executed.

See the docs for more. In particular, see doc/Manual.md. There are lots of examples in examples/.

Also see Comcast/sheens.

License

This software is released under the Apache License, Version 2.0. See LICENSE in this repo.

Usage

Starting

To compile, you need Go. Then

(cd rulesys && go get . && go install)
bin/startengine.sh &
ENDPOINT=http://localhost:8001
LOCATION=here
curl -s $ENDPOINT/version

If you see some JSON, the engine is probably running. Check engine.log to see some logging.

A simple rule

Now let's use that engine. In these examples, we'll talk to the engine using its primitive HTTP API.

# Write a fact.
curl -s -d 'fact={"have":"tacos"}' "$ENDPOINT/api/loc/facts/add?location=$LOCATION"

# Query for the fun of it.
curl -s -d 'pattern={"have":"?x"}' "$ENDPOINT/api/loc/facts/search?location=$LOCATION" | \
  python -mjson.tool

# Write a simple rule.
cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"wants":"?x"}},
          "condition":{"pattern":{"have":"?x"}},
          "action":{"code":"var msg = 'eat ' + x; console.log(msg); msg;"}}}
EOF

# Send an event.
curl -d 'event={"wants":"tacos"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool

The events/ingest output is pretty big. This data contains sufficient information to enable you to reattempt/resume event processing in the case the engine encountered one or more errors during the previous processing.

Scheduled rule

Now let's write a little scheduled rule.

# First a quick check to see if a Javascript action can give us a timestamp.
curl -d 'code=new Date().toISOString()' $ENDPOINT/api/sys/util/js

# Write a scheduled rule.
cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"schedule":"+3s",
          "condition":{"pattern":{"have":"?x"}},
          "action":{"code":"console.log('eating ' + x + ' at ' + (new Date().toISOString()) + '.');"}}}
EOF

Look for a line starting with eating tacos in the engine output.

grep -F 'eating tacos' engine.log

That rule runs only once. Three seconds from when it was created. (We can also use full cron syntax to specify a repeating schedule.)

Action talking to an external service

Now let's make a rule with an action that talks to an external service. We'll start a dummy service that just prints out what it hears.

# Start our dummy service.  Use another window.
(cd examples && ./endpoint.py) &

# See if it works.
curl "http://localhost:6668/foo?likes=tacos"
# Should see some data in that service's window.

# Write the rule.  This rule has no condition.
cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"wants":"?x"}},
          "action":{"code":"Env.http('GET','http://localhost:6668/do?order=' + Env.encode(x))"}}}
EOF

# Send an event.  Should trigger that action.
curl -d 'event={"wants":"Higher Math"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool
# You should see an order in the `endpoint.py' output.

Rule condition querying an external service

The rules engine can query external services during rule condition evaluation. Such a service is called an "external fact service". We have a few example fact services in examples/. Here's one that can report the weather.

(cd examples && ./weatherfs.py) &

# Test it.
curl -d '{"locale":"Austin,TX","temp":"?x"}' 'http://localhost:6666/facts/search'

# Write a rule that uses that source of facts.
cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"visitor":"?who"}, "location":"here"},
          "condition":{"and":[{"pattern":{"locale":"Austin,TX","temp":"?temp"},
                               "locations":["http://localhost:6666/facts/search"]},
                              {"code":"console.log('temp: ' + temp); 0 < temp"}]},
          "action":{"code":"Env.http('GET','http://localhost:6668/report?weather=' + Env.encode('warm enough'))"}}}
EOF

# Send an event.  Should trigger that action.
curl -d 'event={"visitor":"Homer"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool

Javascript libraries

Let's use a library of Javascript code in a rule action.

# Start a library server.
(cd examples && ./libraries.py) &

# Check that we can get a library.
curl http://localhost:6669/libs/tester.js

curl "$ENDPOINT/api/loc/admin/clear?location=$LOCATION"

# Write the rule.
cat <<EOF | curl -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"wants":"?x"}},
          "condition":{"code":"isGood(x)",
                       "libraries":["http://localhost:6669/libs/tester.js"]},
          "action":{"code":"var msg = 'Serve ' + x + ' ('+ isGood(x) + ')'; console.log(msg); msg;",
                    "opts":{"libraries":["http://localhost:6669/libs/tester.js"]}}}}
EOF

# Send an event.  Should trigger that action.
curl -d 'event={"wants":"Higher Math"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool

# Send another event.  Should not trigger that action.
curl -d 'event={"wants":"Duff Light"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool

You can also use libraries in Javascript rule actions.

Your libraries can be pretty fancy (see example/libs/haversine.js), but be cautious about efficiency and robustness. If you find yourself wanting to do a lot of work in action Javascript, think about writing an action executor instead.

Action executors

If you don't want to write your actions in Javascript, which runs inside the rules engine, you can use action executors. An action executor is an external service that is given rule actions to execute. In a serious deployment, an action executor endpoint would probably just queue those actions for a pool of workers to process.

An action executor can do or execute anything in any language or specification. Up to the author of the executor.

We have an example action executor in Python in examples/executor.py.

# Run the toy action executor.
(cd examples && ./executor.py) &

cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule":{"when":{"pattern":{"drinks":"?x"}},
         "action":{"endpoint":"http://localhost:8081/execbash",
                   "code":{"order":"?x"}}}}
EOF

# Send an event.
curl -d 'event={"drinks":"milk"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
  python -mjson.tool

That event should generate a request to the example action executor, which doesn't actually do anything.

You can also write your own action interpreters. For example, you could write an interpreter (shim) for Bash script actions and really cause some trouble.

Getting some statistics

Finally, let's get some statistics.

curl -s "$ENDPOINT/api/loc/admin/stats?location=$LOCATION" | python -mjson.tool
curl -s "$ENDPOINT/api/sys/stats" | python -mjson.tool

APIs

The engine is designed in packages. The core is in core/, and sys/ provides something like a container for locations.

The network API used above and provided by service/ is rendered in HTTP, but it was originally designed for message-oriented transports. For example, all requests are handled by service.ProcessRequest(), which is (mostly) independent of transport.

Persistence is mostly pluggable. See storage/ for examples.

Conclusion

Take a look at the doc/Manual.md for more information.

rulio's People

Contributors

afmahmuda avatar decibelcooper avatar efronlicht avatar henryleduc avatar iamjarvo avatar jeis2497052 avatar jhedlund79 avatar jsccast avatar raidancampbell avatar rakesh-roshan avatar trentontrees avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rulio's Issues

"nil condition" disposition after ProcessEvent() from Javascript

A user reports:

event = { foo: bar, zip: blah }; Env.ProcessEvent(event); from a rule action gives

{
  "op": "core.RunJavascript",
  "error": "Error: nil condition",
  "when": "runtime.Run",
  "corelev": "error",
  "origin": "sys",
  "comp": "misc",
  "ctxLocation": "mcf.xh",
  "log.id": "6c0252e3-c98a-48ca-ba56-f66c4546d387",
  "log.timestamp": "2017-04-22T03:37:06.809827867Z",
  "log.level": "error",  "tx.traceId": "f7fa0d4b-e6c6-4ba7-bc8c-bf095e39690b",
  "tx.spanId": "78c37e1b-7b0a-4c9d-ab60-949b50ce30e5",
  "tx.id": "f7fa0d4b-e6c6-4ba7-bc8c-bf095e39690b",
  "app.id": "csv-rules-rest",
  "env.name": "dev"
}

The "nil condition" part is distasteful since the ProcessEvent call works fine.

Support base64 Javascript rule action encoding

It would be very helpful to be able to upload the code section of a rule as base64 and then have it decode before running.

Example:

cat <<EOF | curl -s -d "@-" $ENDPOINT/api/loc/rules/add
{"location":"$LOCATION",
"rule": { "schedule" : "+1s" ,"action": {"code" : ["ZW52LmNvbnNvbGUoJ0hlbGxvJyk7"] } }}}
EOF

When run, this will result in env.console('Hello');

I think this would be an all or nothing as an endpoint option or with a flag.

Inject special variables earlier so that condition code can use them

Currently some special variables like event and location are injected (if there's no conflict) into an action's Javascript environment. Should inject these bindings earlier so that a code condition can use these bindings.

Fix should be easy. See EvalRuleCondition.Do() in core/events.go. Move the binding code up a little.

Migrate some issues from previous repo

We have a few old issues that should be re-created here. The candidate issues probably don't have corresponding references in commit messages, so numbering shouldn't be an issue.

Provide a copy of the event to actions

Since an action (e.g., a Javascript action) can actually modify the event and since in general action executions for a single event could occur concurrently, then action should receive a copy of the event.

(It's a little strange to modify the event object itself in a rule action, but that approach might be convenient for, say, forwarding the event with some modification.)

Want a Rule API action interpreter

Say an action mostly just wants to call a Rules API (like Env.AddRule). Writing a bunch of Javascript that mostly just offers an ugly, embedded JSON object and associated API call is distasteful. Instead, provide an action interpreter that can call the core APIs with an explicit JSON object representation that uses the (silly) notation "??..." to request a logic variable interpolation for bindings given to the action. Also support an optional code property with a Javascript value that can expand the action's bindings.

For example

{
    "when": {"at":"?there"},
    "action": {
        "language":"rulesapi",
        "code": {
            "code": "{'?id':'bart' + '_' + 'simpson'}",
            "addRule": {
                "pattern": {"wants":"?wanted","at":"??there"},
                "condition": {"has":"?wanted"},
                "action": "Env.http('GET', 'https://localhost:1234/' id + '/' + wanted)"
            }
        }
    }
}

Want headers in response returned by Env.httpx

See core.HTTPResult, which does not convey the response headers.

curl -s --data-urlencode "code=Env.httpx({method: 'GET', uri: 'https://www.google.com'})" \
  $ENDPOINT/sys/util/js | \
  python -mjson.tool

should show the response headers from the GET request.

Default binding syntax: a path to optional fields

My team often comes across the scenario where we receive an event, or even a fact to pattern match against, that has optional, omitempty fields. In these cases, we are forced to bind the parent map and dive into it in a javascript code block. This often becomes very cumbersome and greatly increases the rule configuration complexity.

We would like to propose a syntax for bindings in patterns for specifying default values to fall back to in the event that the field is missing, and we seek feedback from @jsccast. This is work that we can carry out, provided that you agree with this kind of approach, and you do not see a major technical barrier to implementing this.

Default binding syntax

The idea here is to introduce another special character that signals the start of a default JSON value: |.

example:

{
    "pattern": {
        "data": {
            "code": "?code|\"ABC\"",
            "amount": "?amount|0"
        }
    }
}

In the above example, if code is omitted, the binding will be a string with the value "ABC". If amount is omitted, the binding will be a number with the value 0.

Default value propagation

Additionally, we propose that if data is omitted in example 2, the pattern will still match with the default bindings for code and amount, since they both have defaults. If either code or amount did not have a default, then a missing data field would result in no match.

Bring back fact CAS

Previously we had a fact compare and [set]. Basically:

func SetFactConditional(id Id, fact Fact, pattern Pattern) (Id,bool,error)

Get the fact with the given id. If the fact matches the pattern, delete that fact and assert the given fact at the given id (and return true). If the fact doesn't match the given pattern, do nothing (and return false).

The implementation at this level is -- I think -- still straightforward. Lock the index, get the fact, pattern match, delete the old match, write the new fact, unlock the index. The underlying Storage can optionally protest using its standard mechanism (if any) that checks for changed data (either specifically or location-wide) (e.g., using DynamoDB conditional writes that check a location timestamp or sequence number or whatever).

POST response status code is not acted on for external fact requests

The following function is used to perform external fact POST requests:

rulio/core/http.go

Lines 387 to 395 in 54befe4

// Post issues a POST to the specified URL.
func Post(ctx *Context, uri string, contentType string, body string) (string, error) {
req := NewHTTPRequest(ctx, "POST", uri, body)
req.ContentType = contentType
res, err := req.Do(ctx)
got := res.Body
return got, err
}

Since the result status code is not used anywhere within this function or the functions it calls, and since it is not propagated to the calling function, there is no information available to event processing about unexpected/unwanted status codes.

I propose defining a fixed set of status codes for external fact requests, and handling them either in the above function or in the calling function:

rulio/core/query.go

Lines 376 to 404 in 54befe4

func (s *FactServiceFromURI) Search(ctx *Context, pattern Map) (*SearchResults, error) {
op := "core.FactServiceFromURI.Search"
Log(DEBUG, ctx, op, "pattern", pattern)
js, err := json.Marshal(&pattern)
if err != nil {
return nil, err
}
then := Now()
body, err := Post(ctx, s.URI, "application/json", string(js))
if err != nil {
Log(ERROR, ctx, op, "error", err, "when", "Post")
return nil, err
}
Log(DEBUG, ctx, op, "response", body)
srs := new(SearchResults)
err = json.Unmarshal([]byte(body), srs)
// Overwrite .Elapsed? FS should use another key.
srs.Elapsed = Now() - then
if err != nil {
Log(ERROR, ctx, op, "error", err, "when", "unmarshall", "json", string(body))
return nil, err
}
return srs, nil
}

This could be as simple as (pseudocode) status == 200 ? ๐Ÿ‘ : ๐Ÿ‘Ž

Network API loccontrol should also handle a JSON body request

As rules/add does. In other words, the following should work:

cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/sys/loccontrol"
{"Libraries":{"jokes":"function funny() { return 42; }"}}
EOF

cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"wants":"?x"}},
          "action":{"opts": {"libraries":["jokes"]},
                    "code": "funny()+1"}}}
EOF

curl -s -d 'event={"wants":"tacos"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   jq '.result.values[0]'

Currently does not. If you do

curl -s --data-urlencode 'control={"Libraries":{"jokes":"function funny() { return 42; }"}}' "$ENDPOINT/api/sys/loccontrol"

instead of the first curl above, things do work.

Document deleteWith

A fact can have a property deleteWith, which should have a string as a value. That string should be the id for another fact. When that second fact is deleted, the first fact will also be deleted.

Since a rule is a fact (usually indexed specially for rule dispatch), a rule can also have a deleteWith property.

Put the above in the manual.

Clarify how condition code "returns" a value

The manual currently says

If the value returned by block is truthy, then the condition evaluation continues.

Should elaborate.

The code is executed as expressions in an implicit block, so the value of the last expression is the value of the block. (So no explicit return is required or even permitted.)

Pluggable action interpreters

Currently an action is either Javascript (with some environment that includes various custom functions) or an action executor request specification. A rule's action endpoint property (which is poorly named) determines whether the action is interpreted as Javascript (endpoint: "javascript") or as an action executor request (targeting the given endpoint).

We should generalize things to allow for pluggable, custom action interpreters. Say we support an interpreter property for an action. If the interpreter for an action is "javascript", then the action is interpreted as if endpoint: "javascript" in the current implementation. If the intepreter is "actionExecutorRequest", then the action is interpreter as such. If the interpreter is something else, then the system looks up an application-provided interpreter with that name. As a crazy example: interpreter: "bash". Write your own DSLs.

(Maybe also pay attention to an optional interpreterVersion property?

An interpreter might look something like

type ActionInterpreter interface {
        Interpret(ctx *Context, spec interface{}, bs Bindings, env map[string]interface{}, ... ) (interface{}, error)
}

Also provide a registry for interpreters so that the runtime could attempt to find the right interpreter for a given rule action.

Then applications could provide any sort of wild action language they desired.

Modularize action interpreters

Currently an action can be interpreted either as Javascript (otto) (with some data/functions added to the environment) or as an "action executor" requests. In the old days, there were a couple of other options.

This issue is to make the action interpreter modular (along the lines of core.Storage) so that a user of the package core can provide a custom interpreter. The interface will be something along the lines of

type Interpreter interface {
    Interpret(ctx core.Context, input interface{}, bss core.Bindings[], props map[string]interface{}) (interface{}, error)
}

Not exactly that, but that's the gist of it.

A rule can use the existing (but poorly named) "language" action property to specify the name of the interpreter to use.

The dictionary of interpreters could be location-specific, so perhaps that dictionary lives in core.Location via core.Control, which itself can be provided by a sys.System.

DefaultJavascriptTimeout ignored for rule actions

To reproduce:

cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=simpsons"
{"rule":
 {"schedule": "+1s",
  "action": {
      "code": [
          "for (var i = 0; true; i++ ) {", 
          "   console.log('tick ' + i);", 
          "   Env.sleep(1000*1000*1000);", 
          "}" 
      ]}}}
EOF

Then watch the engine's log. The Javascript execution should be interrupted at DefaultJavascriptTimeout but isn't.

Very high cpu usage

I did the load testing for rulio service with around 200 concurrent connections. The machine is 2 core 2.66 GHz and 8 GB RAM. With just 200 concurrent requests, the CPU Load touches 100%. The average rate is 16 Req per seconds. That seems very slow. Is there any way to improve the performance?

Log noise: INFO level is swamped!

Hi Maintainers,

I would very much like to find a way to quiet down the INFO log level while keeping some useful logs in there. I see some extreme offenders that tend to spam this log level for no apparent reason. A good example would be this line:

https://github.com/Comcast/rulio/blob/master/sys/system.go#L101

I am wondering if you would be receptive to a PR that moves all of these "hi, the function is starting" logs in sys/system.go to the DEBUG level?

lacking accurate details for connecting to cassandra db

I have been trying to implement rulio with a database, but I am facing problem due to lack of documentation of actual connection to the database.
I followed the documentation and changed the storage to "cassandra" and storage-config to "127.0.0.1", yet I receive an error:
go run main.go storage > engine.log
panic: bad type for nodes [127.0.0.1] (string); should be []interface{}.
How do I put the address as an interface?

Get this to work on ARM

ARM Processor: The Raspberry Pi 3 uses a Broadcom BCM2837 SoC with a 1.2 GHz 64-bit quad-core ARM Cortex-A53 processor, with 512 KB shared L2 cache.

compile like so env GOOS=linux GOARCH=arm go build -ldflags "-s"
added fact via curl -s -d 'fact={"have":"tacos"}' "$ENDPOINT/api/loc/facts/add?location=$LOCATION"

Panic:

2016/09/21 05:10:03.633048 server.go:2161: http: panic serving 192.168.1.107:52556: runtime error: invalid memory address or nil pointer dereference
goroutine 38 [running]:
net/http.(*conn).serve.func1(0x10e813c0)
    /Users/zeushammer/go/src/net/http/server.go:1389 +0x9c
panic(0x6da970, 0x10d24030)
    /Users/zeushammer/go/src/runtime/panic.go:443 +0x448
sync/atomic.addUint64(0x10f6ca84, 0x1, 0x0, 0x1, 0x0)
    /Users/zeushammer/go/src/sync/atomic/64bit_arm.go:31 +0x68
github.com/Comcast/rulio/core.Inc(0x10f6ca84, 0x1, 0x0)
    /Users/zeushammer/gocode/src/github.com/Comcast/rulio/core/util.go:236 +0x2c
github.com/Comcast/rulio/core.(*Location).addFact(0x10f6ca50, 0x10e21300, 0x0, 0x0, 0x10f08ae0, 0x0, 0x0, 0x0, 0x0)
    /Users/zeushammer/gocode/src/github.com/Comcast/rulio/core/location.go:485 +0x53c
github.com/Comcast/rulio/core.(*Location).AddFact(0x10f6ca50, 0x10e21300, 0x0, 0x0, 0x10f08ae0, 0x0, 0x0, 0x0, 0x0)
    /Users/zeushammer/gocode/src/github.com/Comcast/rulio/core/location.go:469 +0x944
github.com/Comcast/rulio/sys.(*System).AddFact(0x10e04c30, 0x10e21300, 0x10ec8fe1, 0x4, 0x0, 0x0, 0x10ecb8d0, 0x10, 0x0, 0x0, ...)
    /Users/zeushammer/gocode/src/github.com/Comcast/rulio/sys/system.go:919 +0x11ec
github.com/Comcast/rulio/service.(*Service).ProcessRequest(0x10ecaa80, 0x10e21300, 0x10f08680, 0x76ee2708, 0x10e21280, 0x0, 0x0, 0x0)
    /Users/zeushammer/gocode/src/github.com/Comcast/rulio/service/service.go:895 +0x353c
github.com/Comcast/rulio/service.(*HTTPService).ServeHTTP(0x10ebf7c0, 0x76ee2690, 0x10e21280, 0x10dff880)
    /Users/zeushammer/gocode/src/github.com/Comcast/rulio/service/httpd.go:453 +0x948
net/http.serverHandler.ServeHTTP(0x10e30820, 0x76ee2690, 0x10e21280, 0x10dff880)
    /Users/zeushammer/go/src/net/http/server.go:2081 +0x190
net/http.(*conn).serve(0x10e813c0)
    /Users/zeushammer/go/src/net/http/server.go:1472 +0xee4
created by net/http.(*Server).Serve
    /Users/zeushammer/go/src/net/http/server.go:2137 +0x3bc
{"_at":"/Users/zeushammer/gocode/src/github.com/Comcast/rulio/service/httpd.go:503","app.id":"rulesys","appId":"main","comp":"misc","conn":{},"corelev":"info","ctxLocation":"none","level":65824,"local":{"IP":"192.168.1.123","Port":8001,"Zone":""},"op":"service.Server","origin":"sys","remote":{"IP":"192.168.1.107","Port":52556,"Zone":""},"state":4}```

Don't require /proc

--- FAIL: TestCPULoad (0.00s)
    breaker_test.go:64: open /proc/loadavg: no such file or directory
--- FAIL: TestCPULoadProbe (0.00s)
    breaker_test.go:72: open /proc/loadavg: no such file or directory
--- FAIL: TestComboBreaker (0.00s)
    breaker_test.go:98: open /proc/loadavg: no such file or directory

Sorting maps for more flexible rule matching

Hey there @jsccast

I'm more and more frequently bumping up against limitations in the rule matching due to arrays of maps not being sortable. My thought is that it is worth implementing some sort of sorting procedure for maps, even if it isn't super fast. Do you have any thoughts on this?

Thanks!

"bind" query

Hey @jsccast I hope all is well. I've got another thought to run past you.

I don't want to bloat rulio unnecessarily with a huge feature set, but I am on a minor mission to deemphasize the use of javascript blocks in rules. The next suggestion I have along this line of thought is to introduce a new query type called bind. This would be a query that simply takes a JSON map and produces bindings from it, while replacing ?existing_binding with the existing binding value.

Very often we have the need to "transform" a data structure, or bind things conditionally. Today, we jump into JS to do that. With this bind query, hopefully we can avoid the JS and make things more readable.

What do you think?

Libraries not supported in Javascript actions?

To reproduce:

(cd examples && ./libraries.py) &

curl http://localhost:6669/libs/tester.js

export LOCATION=simpsons

curl -s "$ENDPOINT/api/loc/admin/clear?location=$LOCATION"

cat <<EOF | curl -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"wants":"?x"}},
          "action":{"code":"var msg = x; if (isGood(x)) { msg += ' is good.'; } console.log(msg); msg;",
                    "langauge":"javascript",
                    "libraries":["http://localhost:6669/libs/tester.js"]}}}
EOF

curl -d 'event={"wants":"Higher Math"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool

That'll say "ReferenceError: 'isGood' is not defined", which suggests that the library isn't getting loaded.

Rule with dynamic parameter

This is my rule

{
    "location": "here",
    "rule": {
        "action": {
            "code": "var msg ='Name :' + who + ' age : '+x; console.log(msg); msg;"
        },
        "condition": {
            "and": [
                {
                    "pattern": {
                        "age": "?x",
                        "name": "?who"
                    }
                },
                {
                    "code": "x >6"
                }
            ]
        },
        "when": {
            "pattern": {
                "age": "?x",
                "name": "?who"
            }
        }
    }
}

In the above JSON, I am checking with constant 6 where age> 6 in my condition, but is there a way where I can pass a variable instead of fix values from the uri: /api/loc/events/ingest
For example
uri: /api/loc/events/ingest
request

{
    "location": "here",
    "event": {
        "age": "8",
        "name": "sonu"
    }
}

The age passed here i.e. 8 should be used in the rule condition and the result should be according to that?

Bad log line emitted from sys/system.go

Looks like

map[name:homer origin:sys comp:misc ctxLocation:none appId:main corelev:info op:CachedLocations.Open expires:146138514283-06-18 23:45:04 -0800 PST app.id:rulesys level:65824 _at:.../go/src/github.com/Comcast/rulio/sys/system.go:143]

To reproduce:

curl "$ENDPOINT/api/loc/admin/clear?location=homer"`

Better Javascript HTTP client requests

The current Env.http is weak. Doesn't allow much control (e.g., custom timeouts), and doesn't return status. Add a function that supports more request control as well as returns the HTTP response status.

Apparent bad alignment on 32bit platforms

2017/09/15 15:22:48.853330 server.go:2848: http: panic serving 127.0.0.1:60538: runtime error: invalid memory address or nil pointer dereference
goroutine 10 [running]:
net/http.(*conn).serve.func1(0x1aad7c80)
	/usr/local/go/src/net/http/server.go:1697 +0x9f
panic(0x8592860, 0x894a690)
	/usr/local/go/src/runtime/panic.go:491 +0x1d0
sync/atomic.AddUint64(0x1aaef984, 0x1, 0x0, 0x0, 0x0)
	/usr/local/go/src/sync/atomic/asm_386.s:112 +0xc
github.com/Comcast/rulio/core.Inc(0x1aaef984, 0x1, 0x0)
	/home/tlea/go/src/github.com/Comcast/rulio/core/util.go:236 +0x31
github.com/Comcast/rulio/core.(*Location).addFact(0x1aaef950, 0x1aa9ce80, 0x0, 0x0, 0x1ab91be0, 0x1e, 0x85ffeff, 0x7, 0x85fd371)
	/home/tlea/go/src/github.com/Comcast/rulio/core/location.go:485 +0x383
github.com/Comcast/rulio/core.(*Location).AddFact(0x1aaef950, 0x1aa9ce80, 0x0, 0x0, 0x1ab91be0, 0x4, 0x0, 0x0, 0x1ab9ed30)
	/home/tlea/go/src/github.com/Comcast/rulio/core/location.go:469 +0x5f3
github.com/Comcast/rulio/sys.(*System).AddFact(0x1aaa6680, 0x1aa9ce80, 0x1aa16fe1, 0x4, 0x0, 0x0, 0x1aa11800, 0x10, 0x0, 0x0, ...)
	/home/tlea/go/src/github.com/Comcast/rulio/sys/system.go:919 +0xc80
github.com/Comcast/rulio/service.(*Service).ProcessRequest(0x1ab94670, 0x1aa9ce80, 0x1ab91780, 0x891a1f0, 0x1aa90bd0, 0x0, 0x0, 0x0)
	/home/tlea/go/src/github.com/Comcast/rulio/service/service.go:895 +0x45e7
github.com/Comcast/rulio/service.(*HTTPService).ServeHTTP(0x1ab90fa0, 0x891d2e0, 0x1aa90bd0, 0x1aa9ce00)
	/home/tlea/go/src/github.com/Comcast/rulio/service/httpd.go:453 +0x2de
net/http.serverHandler.ServeHTTP(0x1aa9cd00, 0x891d2e0, 0x1aa90bd0, 0x1aa9ce00)
	/usr/local/go/src/net/http/server.go:2619 +0x8e
net/http.(*conn).serve(0x1aad7c80, 0x891db20, 0x1ab913e0)
	/usr/local/go/src/net/http/server.go:1801 +0x5d1
created by net/http.(*Server).Serve
	/usr/local/go/src/net/http/server.go:2720 +0x1f6

User-provided tracing

Hey Maintainers,

I'm looking for a place to add user-provided tracing to rulio. Since I don't see a great solution for it with the current state of the code, let me please propose one.

The proposed solution introduces

  1. user-provided Tracer field to core.Context
  2. context.Context embedding in core.Context
  3. span start/stop operations judiciously placed in sys and core packages

I am working on a PR for this, but feel free to stop me if you think that this is the wrong direction.

init() call in `core/random` breaks non-unix operating systems

in util.go, we have the following code:

func init() {
	f, err := os.Open("/dev/urandom")
	if err != nil {
		log.Fatal(err)
	}
	Random = f
}

this immediately breaksany package that imports rulio on any operating system which doesn't have this file (notably, Windows).

As far as I can tell, this just duplicates the behavior of crypto/rand.Reader in a less secure, less portable way.

Possible solutions.

  • Do nothing. This is intended behavior.
  • initialize in a platform dependent way, similar to crypto/rand, but returning an os.File. I'm not sure if you can easily get *os.File that provides the desired behavior in Windows.
  • Remove Random entirely, using crypto/random/Random instead. This may be a breaking change to libraries that rely on it, though mostly in satisfying the typechecker (most of *os.File's API is meaningless for dev/urandom
  • Change Random from an *os.File to an io.Reader, then initialize in a platform-dependent similar to crypto/rand. This is a breaking change in the same way as the above.
  • Change init() to be more tolerant by throwing a warning rather than a fatal error on windows systems. This will throw a nil pointer exception the first time anything uses Random, so it's probably not a good idea.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.