From a2a5976b6c4d4d4e36dfdaf2b4cf72f7b7529813 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 4 Feb 2019 15:13:55 +0100 Subject: [PATCH 1/8] Add Text lint and Husky for Markdown --- .remarkrc.js | 3 + .textlintrc | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 2 + package.json | 16 ++++- 4 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 .remarkrc.js create mode 100644 .textlintrc diff --git a/.remarkrc.js b/.remarkrc.js new file mode 100644 index 00000000..53ec7dee --- /dev/null +++ b/.remarkrc.js @@ -0,0 +1,3 @@ +exports.settings = { bullet: '-', paddedTable: true }; + +exports.plugins = [require('./node_modules/remark-preset-lint-recommended')]; diff --git a/.textlintrc b/.textlintrc new file mode 100644 index 00000000..e6ca0282 --- /dev/null +++ b/.textlintrc @@ -0,0 +1,195 @@ +{ + "rules": { + "terminology": { + "defaultTerms": false, + "terms": [ + // Brands + "Airbnb", + "Android", + "AppleScript", + "AppVeyor", + "AVA", + "BrowserStack", + "Browsersync", + "Codecov", + "CodePen", + "CodeSandbox", + "DefinitelyTyped", + "EditorConfig", + "ESLint", + "FIWARE", + "GitHub", + "GraphQL", + "iOS", + "JavaScript", + "JetBrains", + "jQuery", + "LinkedIn", + "Lodash", + "MacBook", + "Markdown", + "OpenType", + "PayPal", + "PhpStorm", + "RubyMine", + "Sass", + "SemVer", + "TypeScript", + "UglifyJS", + "WebStorm", + "WordPress", + "YouTube", + ["JSDocs?", "JSDoc"], +// ["Node(?:js)?", "Node.js"], + ["React[ .]js", "React"], + ["SauceLabs", "Sauce Labs"], + ["StackOverflow", "Stack Overflow"], + ["styled ?components", "styled-components"], + ["HTTP[ /]2(?:\\.0)?", "HTTP/2"], + ["OS X", "macOS"], + ["Mac ?OS", "macOS"], + ["a npm", "an npm"], + + // ECMAScript + "ECMAScript", + ["ES2015", "ES6"], + ["ES7", "ES2016"], + + // Abbreviations + "3D", + ["3-D", "3D"], + "Ajax", + "API", + ["API['’]?s", "APIs"], + "CSS", + "GIF", + "HTML", + "HTTPS", + "IoT", + "I/O", + ["I-O", "I/O"], + "JPEG", + "MIME", + "OK", + "PaaS", + "PDF", + "PNG", + "SaaS", + "URL", + ["URL['’]?s", "URLs"], + ["an URL", "a URL"], + ["wi[- ]?fi", "Wi-Fi"], + + // Names + "McKenzie", + "McConnell", + + // Words and phrases + "ID", // http://stackoverflow.com/questions/1151338/id-or-id-on-user-interface + ["id['’]?s", "IDs"], + ["backwards compatible", "backward compatible"], + ["build system(s?)", "build tool$1"], + ["CLI tool(s?)", "command-line tool$1"], + ["he or she", "they"], + ["he/she", "they"], + ["\\(s\\)he", "they"], + ["repo\\b", "repository"], + ["smartphone(s?)", "mobile phone$1"], + ["web[- ]?site(s?)", "site$1"], + + // Single word + ["auto[- ]complete", "autocomplete"], + ["auto[- ]format", "autoformat"], + ["auto[- ]fix", "autofix"], + ["auto[- ]fixing", "autofixing"], + ["back[- ]end(\\w*)", "backend$1"], + ["bug[- ]fix(es?)", "bugfix$1"], + ["check[- ]box(es?)", "checkbox$1"], + ["code[- ]base(es?)", "codebase$1"], + ["co[- ]locate(d?)", "colocate$1"], + ["end[- ]point(s?)", "endpoint$1"], + ["e[- ]mail(s?)", "email$1"], + ["file[- ]name(s?)", "filename$1"], + ["front[- ]end(\\w*)", "frontend$1"], + ["hack[- ]a[- ]thon(s?)", "hackathon$1"], + ["host[- ]name(s?)", "hostname$1"], + ["hot[- ]key(s?)", "hotkey$1"], + ["life[- ]cycle", "lifecycle"], + ["life[- ]stream(s?)", "lifestream$1"], + ["lock[- ]file(s?)", "lockfile$1"], + ["mark-up", "markup"], // “mark up” as a verb is OK + ["meta[- ]data", "metadata"], + ["name[- ]space(s?)", "namespace$1"], + ["pre[- ]condition(s?)", "precondition$1"], + ["pre[- ]defined", "predefined"], + ["pre[- ]release(s?)", "prerelease$1"], + ["run[- ]time", "runtime"], + ["screen[- ]shot(s?)", "screenshot$1"], + ["screen[- ]?snap(s?)", "screenshot$1"], + ["sub[- ]class((?:es|ing)?)", "subclass$1"], + ["sub[- ]tree(s?)", "subtree$1"], + ["time[- ]stamp(s?)", "timestamp$1"], + ["touch[- ]screen(s?)", "touchscreen$1"], + ["user[- ]name(s?)", "username$1"], + ["walk[- ]through", "walkthrough"], + ["white[- ]space", "whitespace"], + ["wild[- ]card(s?)", "wildcard$1"], + + // Multiple words + ["change-?log(s?)", "change log$1"], + ["css-?in-?js", "CSS in JS"], + ["code-?review(s?)", "code review$1"], + ["code-?splitting", "code splitting"], + ["end-?user(s?)", "end user$1"], + ["file-?type(s?)", "file type$1"], + ["open-?source(ed?)", "open source$1"], + ["regexp?(s?)", "regular expression$1"], + ["style-?guide(s?)", "style guide$1"], + ["tree-?shaking", "tree shaking"], + ["source-?map(s?)", "source map$1"], + ["style-?sheet(s?)", "style sheet$1"], + ["user-?base", "user base"], + ["web-?page(s?)", "web page$1"], + + // Hyphenated + ["built ?in", "built-in"], + ["client ?side", "client-side"], + ["command ?line", "command-line"], + ["end ?to ?end", "end-to-end"], + ["error ?prone", "error-prone"], + ["higher ?order", "higher-order"], + ["key[/ ]?value", "key-value"], + ["server ?side", "server-side"], + ["two ?steps?", "two-step"], + ["2 ?steps?", "two-step"], + + // Starts from a lower case letter in the middle of a sentence + ["(\\w+[^.?!]\\)? )base64", "$1base64"], + ["(\\w+[^.?!]\\)? )stylelint", "$1stylelint"], + ["(\\w+[^.?!]\\)? )webpack", "$1webpack"], + ["(\\w+[^.?!]\\)? )npm", "$1npm"], + + // Typos + ["environemnt(s?)", "environment$1"], + ["pacakge(s?)", "package$1"], + ["tilda", "tilde"], + ["falsey", "falsy"] + ] + }, + "common-misspellings": true, + "write-good": { + "adverb": false, + "passive": false, + "tooWordy": false, + "weasel": false, + "so": false + }, + "no-dead-link": { + "ignoreRedirects": true, + "ignore": [ + "mailto:*", + "https://oauth.net" + ] + } + } +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 0ddbaed4..33e109e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,8 @@ before_install: before_script: - npm run lint +# - npm run lint:text + - npm run lint:md after_script: - npm run test:coveralls diff --git a/package.json b/package.json index 94893223..2cfe685a 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,10 @@ "test": "mocha --recursive 'test/**/*.js' --reporter spec --timeout 3000 --ui bdd --exit", "test:watch": "npm run test -- -w ./lib", "lint": "jshint lib/ --config .jshintrc && jshint test/ --config test/.jshintrc", + "lint:md": "remark 'README.md' documentation", + "lint:text": "textlint 'README.md' 'documentation/*.md' 'documentation/**/*.md'", "prettier": "prettier --config .prettierrc.json --write ./**/**/*.js **/*.js *.js", + "prettier:text": "prettier 'README.md' 'documentation/*.md' 'documentation/**/*.md' --no-config --tab-width 4 --print-width 120 --write --prose-wrap always", "test:coverage": "istanbul cover _mocha -- --recursive 'test/**/*.js' --reporter spec --exit", "test:coveralls": "npm run test:coverage && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage", "watch": "watch 'npm test && npm run lint' ./lib ./test" @@ -39,11 +42,18 @@ "chai": "~4.1.2", "proxyquire": "0.5.1", "prettier": "~1.14.2", + "remark-cli": "^6.0.1", + "remark-preset-lint-recommended": "^3.0.2", "rewire": "~4.0.1", "should": "8.2.2", "watch": "~1.0.2", "sinon": "~6.1.0", - "sinon-chai": "~3.2.0" + "sinon-chai": "~3.2.0", + "textlint": "^11.0.1", + "textlint-rule-common-misspellings": "^1.0.1", + "textlint-rule-no-dead-link": "^4.4.1", + "textlint-rule-terminology": "^1.1.30", + "textlint-rule-write-good": "^1.6.2" }, "keywords": [], "dependencies": { @@ -70,6 +80,10 @@ "*.js": [ "prettier --config .prettierrc.json --write", "git add" + ], + "*.md": [ + "prettier --no-config --tab-width 4 --print-width 120 --write --prose-wrap always", + "git add" ] } } From b02a5db1f8fd174a0ffd761d9be8a4ba225390df Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 4 Feb 2019 15:16:12 +0100 Subject: [PATCH 2/8] Run prettier on text and correct text lint errors. --- README.md | 34 +- documentation/admin.md | 121 +++--- documentation/api.md | 75 ++-- documentation/architecture.md | 353 +++++++++------- documentation/configuration.md | 153 +++---- documentation/deployment.md | 111 ++--- documentation/development.md | 17 +- documentation/errors.md | 78 ++-- documentation/logs.md | 140 ++++--- documentation/metrics_api.md | 148 ++++--- documentation/models.md | 198 ++++----- documentation/pep_actions.md | 35 +- documentation/plain_rules.md | 725 ++++++++++++++++++--------------- documentation/roadmap.md | 36 +- 14 files changed, 1222 insertions(+), 1002 deletions(-) diff --git a/README.md b/README.md index e7483ae7..d5023055 100644 --- a/README.md +++ b/README.md @@ -7,28 +7,30 @@ [![Coverage Status](https://coveralls.io/repos/github/telefonicaid/perseo-fe/badge.svg?branch=master)](https://coveralls.io/github/telefonicaid/perseo-fe?branch=master) ![Status](https://nexus.lab.fiware.org/static/badges/statuses/perseo.svg) - CEP implementation for IoT platform ## Index -* [Architecture](documentation/architecture.md) -* [Logs & Alarms](documentation/logs.md) -* O&M - * [Deployment](documentation/deployment.md) - * [Configuration](documentation/configuration.md) - * [Administration](documentation/admin.md) - * [Metrics API](documentation/metrics_api.md) - -* [API](documentation/api.md) - * [Plain rules](documentation/plain_rules.md) - * [Errors](documentation/errors.md) -* [Data model](documentation/models.md) -* [Available actions for PEP](documentation/pep_actions.md) -* [Documentation for developers of perseo](documentation/development.md) +- [Architecture](documentation/architecture.md) +- [Logs & Alarms](documentation/logs.md) +- O&M + + - [Deployment](documentation/deployment.md) + - [Configuration](documentation/configuration.md) + - [Administration](documentation/admin.md) + - [Metrics API](documentation/metrics_api.md) + +- [API](documentation/api.md) + - [Plain rules](documentation/plain_rules.md) + - [Errors](documentation/errors.md) +- [Data model](documentation/models.md) +- [Available actions for PEP](documentation/pep_actions.md) +- [Documentation for developers of perseo](documentation/development.md) | :dart: [Roadmap](documentation/roadmap.md) | -|---| +| ------------------------------------------ | + + ## License Perseo FE is licensed under Affero General Public License (GPL) version 3. diff --git a/documentation/admin.md b/documentation/admin.md index d52cb4ff..39f4d362 100644 --- a/documentation/admin.md +++ b/documentation/admin.md @@ -1,98 +1,116 @@ + ## Administration + ### Service operations -#### Start service -To start the service, use either the service command: -service perseo start +#### Start service + +To start the service, use either the service command: service perseo start Or just the launch script: + ``` /etc/init.d/perseo start ``` -For testing purposes it might be interesting to launch the process directly without the service. That can be done executing the following command from the project root directory: + +For testing purposes it might be interesting to launch the process directly without the service. That can be done +executing the following command from the project root directory: + ``` ./bin/perseo ``` -Take into account that when the process is executed manually the system configuration for the script is not loaded and the default configuration (in /opt/perseo/config.js) is used. +Take into account that when the process is executed manually the system configuration for the script is not loaded and +the default configuration (in /opt/perseo/config.js) is used. + +#### Stop service -#### Stop service To stop the service, use either the service command: + ``` service perseo stop ``` + Or just the launch script: + ``` /etc/init.d/perseo stop ``` -### How to check service status -#### Checking the process is running + +### How to check service status + +#### Checking the process is running + The status of the process can be retrieved using the service command: + ``` service perseo status ``` + It also can be checked with ps, using a filter with the command name: + ``` ps -ef | grep "bin/perseo" ``` + In both cases a result of 0 (echoing $?) indicates the process is supposed to be running, and an error otherwise. + #### Checking that the port is listening + The following command: + ``` netstat -ntpl | grep 9090 ``` -can be used to check the process is listening in the appropriate port (provided the port is the standard 9090). The result should resemble this line: + +can be used to check the process is listening in the appropriate port (provided the port is the standard 9090). The +result should resemble this line: + ``` tcp 0 0 0.0.0.0:1026 0.0.0.0:* LISTEN 12179/node ``` ### Database aspects -Perseo FE uses MongoDB for persistence (have a look to the [data models](models.md) -document for more detail regarding how the data in the DB is structured). -Perseo FE supports both standalone and replica set configurations for the DB. - -At startup time, Perseo tries to connect to CB. If this connection fails, -then Perseo will decline to run and log a `FATAL` error pointing out the problem -with DB. - -At runtime the connection to DB is managed by the driver. The driver keeps a -buffer of operations waiting for connection. There is a limit for that buffer, -established by the `checkDB.bufferMaxEntries` configuration parameter. Thus, -if a connection problem persist, then the buffer size will eventually overpass -the limit (all the operations waiting in the buffer will result in error in -this case). In addition, an DB alarm at `ERROR` level is traced in the logs. - -Moreover, in standalone mode (and *not* in replica set mode) the driver -also use a couple of parameters: `checkDB.reconnectTries` and -`checkDB.reconnectInterval` to manage DB connections retries in the case of connection -problems. If the connection to the server fails in this case, then the driver -will try to reconnect as many times as `reconnectTries`, waiting `reconnectInterval` -between attemps. Overpassed the limit, Perseo will end with `FATAL` error log -in the traces (as it does when connection fails at startup time). - -Finally, note that perseo does periodical pings to DB in order to check if it -is active. The pinging period is configured with the `checkDB.delay` parameter. -This is a measure to "stimulate" the connection with DB and early discover -possible connection problems. Even in the case of no other operations -(i.e. Perseo is idle) the ping will accumulate in the buffer, eventually -overpassing `checkDB.bufferMaxEntries` and raising the DB alarm. - -In a DB alarm situation, the ping operation also allows to release the alarm, -once the DB connection is ok again. - -The different configuration parameters introduced above are described -also in the [configuration document](configuration.md). +Perseo FE uses MongoDB for persistence (have a look to the [data models](models.md) document for more detail regarding +how the data in the DB is structured). Perseo FE supports both standalone and replica set configurations for the DB. + +At startup time, Perseo tries to connect to CB. If this connection fails, then Perseo will decline to run and log a +`FATAL` error pointing out the problem with DB. + +At runtime the connection to DB is managed by the driver. The driver keeps a buffer of operations waiting for +connection. There is a limit for that buffer, established by the `checkDB.bufferMaxEntries` configuration parameter. +Thus, if a connection problem persist, then the buffer size will eventually overpass the limit (all the operations +waiting in the buffer will result in error in this case). In addition, an DB alarm at `ERROR` level is traced in the +logs. + +Moreover, in standalone mode (and _not_ in replica set mode) the driver also use a couple of parameters: +`checkDB.reconnectTries` and `checkDB.reconnectInterval` to manage DB connections retries in the case of connection +problems. If the connection to the server fails in this case, then the driver will try to reconnect as many times as +`reconnectTries`, waiting `reconnectInterval` between attemps. Overpassed the limit, Perseo will end with `FATAL` error +log in the traces (as it does when connection fails at startup time). + +Finally, note that perseo does periodical pings to DB in order to check if it is active. The pinging period is +configured with the `checkDB.delay` parameter. This is a measure to "stimulate" the connection with DB and early +discover possible connection problems. Even in the case of no other operations (i.e. Perseo is idle) the ping will +accumulate in the buffer, eventually overpassing `checkDB.bufferMaxEntries` and raising the DB alarm. + +In a DB alarm situation, the ping operation also allows to release the alarm, once the DB connection is OK again. + +The different configuration parameters introduced above are described also in the +[configuration document](configuration.md). ### How to subscribe to Context Broker -([Orion](https://github.com/telefonicaid/fiware-orion) has detailed documentation) -In the example below, -* ‘reference’ should be the perseo's IP and port -* ‘condValues’ a list of the attributes of the entity used in rules. In a future release of Orion (Context Broker) will be possible to subscribe to changes in any attribute, but in the current version, they must be specified -* 'orion-machine:1026' should be substituted by the actual Context Broker's IP and port -* 'service' is the service associated to the subscription -* 'subservice' is the subservice associated to the subscription + +([Orion](https://github.com/telefonicaid/fiware-orion) has detailed documentation) In the example below, + +- ‘reference’ should be the perseo's IP and port +- ‘condValues’ a list of the attributes of the entity used in rules. In a future release of Orion (Context Broker) + will be possible to subscribe to changes in any attribute, but in the current version, they must be specified +- 'orion-machine:1026' should be substituted by the actual Context Broker's IP and port +- 'service' is the service associated to the subscription +- 'subservice' is the subservice associated to the subscription ``` (curl http://orion-machine:1026/v1/subscribeContext -s -S --header 'Content-Type: application/json' --header 'Accept: application/json' --header 'Fiware-Service: service' –header 'Fiware-ServicePath: subservice' -d @- | python -mjson.tool) < + # Architecture -* [Components](#Components) -* [Concepts](#Concepts) -* [Data flow](#Dataflow) -* [Scenarios](#Scenarios) + +- [Components](#Components) +- [Concepts](#Concepts) +- [Data flow](#Dataflow) +- [Scenarios](#Scenarios) + ## Components ### perseo -The CEP "front-end", it is in charge of processing incoming events and rules, storing rules and executing actions. Execution of actions are recorded for controlling frecuency of the actions performed. Also, it checks the no-update ("no-signal") rules which set a maximum time interval between events from a context broker entity. +The CEP "front-end", it is in charge of processing incoming events and rules, storing rules and executing actions. +Execution of actions are recorded for controlling frecuency of the actions performed. Also, it checks the no-update +("no-signal") rules which set a maximum time interval between events from a context broker entity. ### core (perseo-core) -The CEP "back-end", the "rule-engine". It checks incoming events against rules in EPL and invokes perseo if an action must be executed. It has no pesistent storage. Rules are kept in memory. The whole set of rules is refreshed periodically by perseo "FE". +The CEP "back-end", the "rule-engine". It checks incoming events against rules in EPL and invokes perseo if an action +must be executed. It has no pesistent storage. Rules are kept in memory. The whole set of rules is refreshed +periodically by perseo "FE". ### mongoDB -Database used by perseo for storing rules and executions of actions. +Database used by perseo for storing rules and executions of actions. ![Imagen](images/components.png) @@ -26,26 +33,31 @@ Database used by perseo for storing rules and executions of actions. ### Orion (Context Broker) -Source of events from the entities it manages. Also, some actions perform an update of the entity that fired the action. The Context Broker as a target of actions can be secured with a PEP proxy protecting +Source of events from the entities it manages. Also, some actions perform an update of the entity that fired the action. +The Context Broker as a target of actions can be secured with a PEP proxy protecting ### Portal -From point of view of CEP, the graphical user interface for creating rules (visual rules). It could be any user of the rules API. +From point of view of CEP, the graphical user interface for creating rules (visual rules). It could be any user of the +rules API. ### Orion Database -Perseo queries the Orion database periodically in order to check if the entities referred by any "non-update" ("no-signal") rule should trigger an action in case these entities have been "silent" for too long. - -*NOTE:* this is an interim mechanism in version 0.4.x. In the future, probably this will be done using the Orion API, so Perseo wouldn't interact any longer with the Orion database directly. +Perseo queries the Orion database periodically in order to check if the entities referred by any "non-update" +("no-signal") rule should trigger an action in case these entities have been "silent" for too long. +_NOTE:_ this is an interim mechanism in version 0.4.x. In the future, probably this will be done using the Orion API, so +Perseo wouldn't interact any longer with the Orion database directly. ### SMS gateway -Some actions send an SMS to a number set in the action parameters. This is done by an HTTP post to the SMS gateway (SMPP adapter), configured in perseo. +Some actions send an SMS to a number set in the action parameters. This is done by an HTTP post to the SMS gateway (SMPP +adapter), configured in perseo. ### SMPP server -Some actions send an SMS to a number set in the action parameters. Alternativaly instead of use a SMS gateway this can be done by and HTTP post to a SMPP server configured in perseo. +Some actions send an SMS to a number set in the action parameters. Alternativaly instead of use a SMS gateway this can +be done by and HTTP post to a SMPP server configured in perseo. ### SMTP server @@ -53,237 +65,266 @@ Some actions send an email. It is done using an SMTP server configured in perseo ### Generic HTTP server -Some actions consist on making an HTTP POST to an URL provided as parameter of the action. his URL can point to any host. +Some actions consist on making an HTTP POST to an URL provided as parameter of the action. his URL can point to any +host. ### Authorization server -The interaction with a Context Broker through a PEP proxy requires an access token that must be refreshed periodically. The "trust token" associated with a rule that executes an update-action must be exchanged by an "access token" at the Authorization Server, when the access token has expired. +The interaction with a Context Broker through a PEP proxy requires an access token that must be refreshed periodically. +The "trust token" associated with a rule that executes an update-action must be exchanged by an "access token" at the +Authorization Server, when the access token has expired. + ## Concepts ### Event/notice -Notifications or _notices_ from Context Broker are processed by perseo before being sent to perseo-core as *events*. They correspond to changes in the values of an attribute of an entity which perseo in susbcribed to. -The incoming notification is a JSON as the documentation of Orion specifies, for example +Notifications or _notices_ from Context Broker are processed by perseo before being sent to perseo-core as _events_. +They correspond to changes in the values of an attribute of an entity which perseo in susbcribed to. The incoming +notification is a JSON as the documentation of Orion specifies, for example ```json { - "subscriptionId" : "51c04a21d714fb3b37d7d5a7", - "originator" : "localhost", - "contextResponses" : [ - { - "contextElement" : { - "attributes" : [ - { - "name" : "BloodPressure", - "type" : "centigrade", - "value" : "2" - }, - { - "name" : "TimeInstant", - "type" : "urn:x-ogc:def:trs:IDAS:1.0:ISO8601", - "value" : "2014-04-29T13:18:05Z" - } - ], - "type" : "BloodMeter", - "isPattern" : "false", - "id" : "bloodm1" - }, - "statusCode" : { - "code" : "200", - "reasonPhrase" : "OK" - } - } - ] + "subscriptionId": "51c04a21d714fb3b37d7d5a7", + "originator": "localhost", + "contextResponses": [ + { + "contextElement": { + "attributes": [ + { + "name": "BloodPressure", + "type": "centigrade", + "value": "2" + }, + { + "name": "TimeInstant", + "type": "urn:x-ogc:def:trs:IDAS:1.0:ISO8601", + "value": "2014-04-29T13:18:05Z" + } + ], + "type": "BloodMeter", + "isPattern": "false", + "id": "bloodm1" + }, + "statusCode": { + "code": "200", + "reasonPhrase": "OK" + } + } + ] } ``` + After some processing that notification will be sent to perseo-core as _event_ + ```json { - "noticeId":"a64fa410-8aad-11e4-87e4-632f5115a641", - "id":"bloodm1", - "type":"BloodMeter", - "isPattern":"false", - "service":"/", - "tenant":"unknownt", - "BloodPressure":"2", - "BloodPressure__type":"centigrade", - "TimeInstant":"2014-04-29T13:18:05Z", - "TimeInstant__type":"urn:x-ogc:def:trs:IDAS:1.0:ISO8601" + "noticeId": "a64fa410-8aad-11e4-87e4-632f5115a641", + "id": "bloodm1", + "type": "BloodMeter", + "isPattern": "false", + "service": "/", + "tenant": "unknownt", + "BloodPressure": "2", + "BloodPressure__type": "centigrade", + "TimeInstant": "2014-04-29T13:18:05Z", + "TimeInstant__type": "urn:x-ogc:def:trs:IDAS:1.0:ISO8601" } ``` -Restriction: an attribute can not be named 'id' or 'type', which would cause confusion with the entity's id or entity's type. Notices with such attributes will be rejected. -(This is only for perseo, not for Orion - Context Broker) + +Restriction: an attribute can not be named 'id' or 'type', which would cause confusion with the entity's ID or entity's +type. Notices with such attributes will be rejected. (This is only for perseo, not for Orion - Context Broker) ### Rule -A rule is the way of generating 'complex' event from an incoming event. Typically it will consist on a condition to be fullfilled and an resulting event. In our case the resulting event is always an action chosen from a pre-established set of actions. +A rule is the way of generating 'complex' event from an incoming event. Typically it will consist on a condition to be +fulfilled and an resulting event. In our case the resulting event is always an action chosen from a pre-established set +of actions. #### Visual rules -The rule and action is specified by an old-compatible format used in the portal, heritage from old DCA. It is a rather complex JSON generated by the portal programatically and based on the interactions of the user with a graphical interface. An example -``` json +The rule and action is specified by an old-compatible format used in the portal, heritage from old DCA. It is a rather +complex JSON generated by the portal programatically and based on the interactions of the user with a graphical +interface. An example + +```json { - "name" : "prueba-test", - "active" : 1, - "cards" : [ - { - "type" : "ActionCard", - "actionData" : { - "userParams" : [ - { - "name" : "mail.from", - "value" : "dca_support@tid.es" - }, - { - "name" : "mail.to", - "value" : "MANDATORY" - }, - { - "name" : "mail.subject", - "value" : "DCA message" - }, - { - "name" : "mail.message", - "value" : "DCA message" - } - ], - "name" : "email", - "type" : "SendEmailAction" - }, - "id" : "card_42", - "connectedTo" : [ ] - }, - { - "id" : "card_43", - "type" : "SensorCard", - "sensorCardType" : "regexp", - "configData" : { - - }, - "sensorData" : { - "parameterValue" : "^asd.*" - }, - "conditionList" : [ - { - "scope" : "XPATH", - "parameterValue" : "asd", - "parameterName" : "id", - "not" : false, - "operator" : "MATCH", - "userProp" : "" - } - ], - "connectedTo" : [ - "card_42" - ] - } - ] - } + "name": "prueba-test", + "active": 1, + "cards": [ + { + "type": "ActionCard", + "actionData": { + "userParams": [ + { + "name": "mail.from", + "value": "dca_support@tid.es" + }, + { + "name": "mail.to", + "value": "MANDATORY" + }, + { + "name": "mail.subject", + "value": "DCA message" + }, + { + "name": "mail.message", + "value": "DCA message" + } + ], + "name": "email", + "type": "SendEmailAction" + }, + "id": "card_42", + "connectedTo": [] + }, + { + "id": "card_43", + "type": "SensorCard", + "sensorCardType": "regexp", + "configData": {}, + "sensorData": { + "parameterValue": "^asd.*" + }, + "conditionList": [ + { + "scope": "XPATH", + "parameterValue": "asd", + "parameterName": "id", + "not": false, + "operator": "MATCH", + "userProp": "" + } + ], + "connectedTo": ["card_42"] + } + ] +} ``` -Additional info about VisualRules: [DCA documentation](https://colabora.tid.es/dca/SitePages/Inicio.aspx) (RESTAPI-SBC_2.6, section 6.15) +Additional info about VisualRules: [DCA documentation](https://colabora.tid.es/dca/SitePages/Inicio.aspx) +(RESTAPI-SBC_2.6, section 6.15) #### "Plain" rules + A simplified format in JSON can be used to represent rules. The former visual rule would be represented so: ```json { - "name" : "prueba-test", - "text" : "select * from pattern [every ev=iotEvent((cast(id?, String) regexp \"asd\"))]", - "action" : { - "type" : "email", - "template" : "DCA message", - "parameters" : { - "to" : "MANDATORY", - "from" : "dca_support@tid.es", - "subject" : "DCA message" - } - }, + "name": "prueba-test", + "text": "select * from pattern [every ev=iotEvent((cast(id?, String) regexp \"asd\"))]", + "action": { + "type": "email", + "template": "DCA message", + "parameters": { + "to": "MANDATORY", + "from": "dca_support@tid.es", + "subject": "DCA message" + } + } } ``` -Actually, visual rules are translated to "plain" rules by perseo internally. This is transparent for the portal which only see visual rules (create, update, delete) -#### EPL +Actually, visual rules are translated to "plain" rules by perseo internally. This is transparent for the portal which +only see visual rules (create, update, delete) -The rule at perseo-core is expressed as an EPL sentence. EPL is a domain language of [Esper](http://esper.codehaus.org), the engine for processing events used in perseo-core. This EPL sentence matches an incoming event if satisfies the conditions and generates an "action-event" that will be sent back to perseo to execute the associated action. The EPL is generated by perseo for visual rules and must be included in "plain" rule, following several conditions to work in the interaction between perseo and perseo-core. +#### EPL +The rule at perseo-core is expressed as an EPL sentence. EPL is a domain language of [Esper](http://esper.codehaus.org), +the engine for processing events used in perseo-core. This EPL sentence matches an incoming event if satisfies the +conditions and generates an "action-event" that will be sent back to perseo to execute the associated action. The EPL is +generated by perseo for visual rules and must be included in "plain" rule, following several conditions to work in the +interaction between perseo and perseo-core. ### Action -The action included in rule allows sending an email, sending an SMS or updating an attribute of the entity which was the source of the firing event, etc. The message sent in SMS or email can be a template with parameters replaced with fields of the generated event when the message is about to be sent by perseo (through SMS gateway or SMTP server) +The action included in rule allows sending an email, sending an SMS or updating an attribute of the entity which was the +source of the firing event, etc. The message sent in SMS or email can be a template with parameters replaced with fields +of the generated event when the message is about to be sent by perseo (through SMS gateway or SMTP server) #### Loop detection -An infinite loop can be created by rules with update actions, each one making a change which triggers the other rule. It +An infinite loop can be created by rules with update actions, each one making a change which triggers the other rule. It is a symptom of a bad design or an error in writing the rules. For example, two rules like - IF temperature < 5 AND alarm == OFF THEN alarm = ON - IF temperature < 5 AND alarm == ON THEN alarm = OFF - -If the subscription in Orion is for every attribute or just for `temperature` and `alarm`, those rules will create -an infinite loop of triggers/updates. + IF temperature < 5 AND alarm == OFF THEN alarm = ON + IF temperature < 5 AND alarm == ON THEN alarm = OFF -In a best-effort to avoid this situation as much as possible, the feature of propagation of the header field -`Fiware-correlator` is used. This header field is taken from the incoming request (or created if not present) and sent -to every external system. The Context Broker follows the same tactic. In a loop, the same correlator will be sent between -perseo and orion, like a ping-pong game. So, if perseo-core sends an action to perseo-fe -and that action (for the same rule) has been executed with that correlator already, it declines executing it. It could -be part of an infinite loop. The fact is logged and the action is ignored. +If the subscription in Orion is for every attribute or just for `temperature` and `alarm`, those rules will create an +infinite loop of triggers/updates. +In a best-effort to avoid this situation as much as possible, the feature of propagation of the header field +`Fiware-correlator` is used. This header field is taken from the incoming request (or created if not present) and sent +to every external system. The Context Broker follows the same tactic. In a loop, the same correlator will be sent +between perseo and orion, like a ping-pong game. So, if perseo-core sends an action to perseo-fe and that action (for +the same rule) has been executed with that correlator already, it declines executing it. It could be part of an infinite +loop. The fact is logged and the action is ignored. + ## Data flow ### External data flow -![Imagen](images/dfd-0.png) - - +![Imagen](images/dfd-0.png) ### Internal data flow + ![Imagen](images/dfd-1.png) ## HA -The scheme for HA has two sets of perseo and perseo-core components, connected each other. Each set follows the "normal" flow mentioned before. Rules and events are processed by perseo and sent to its associated core. Additionally, rules and events are *propagated* to the "next" core. Only at the time of executing an action there is an asymmetry between the two sets. The "master" always executes the action. The slave (substitute could be a more appropriate name) executes the action only and only if it "thinks" that the master has not been able to do it. The slave waits a configurable time and then checks in mongoDB if an action for the original notice has been executed already. If not, it executes the action, else it forgets the action. +The scheme for HA has two sets of perseo and perseo-core components, connected each other. Each set follows the "normal" +flow mentioned before. Rules and events are processed by perseo and sent to its associated core. Additionally, rules and +events are _propagated_ to the "next" core. Only at the time of executing an action there is an asymmetry between the +two sets. The "master" always executes the action. The slave (substitute could be a more appropriate name) executes the +action only and only if it "thinks" that the master has not been able to do it. The slave waits a configurable time and +then checks in mongoDB if an action for the original notice has been executed already. If not, it executes the action, +else it forgets the action. -Events and rules can be sent to the perseo FEs by a load balancer in a round-robin fashion. Regardless which FE receives the event/rule, this one will arrive to both cores. Both will make the same inferences and each one will trigger an action if it is necessary. +Events and rules can be sent to the perseo FEs by a load balancer in a round-robin fashion. Regardless which FE receives +the event/rule, this one will arrive to both cores. Both will make the same inferences and each one will trigger an +action if it is necessary. ![Imagen](images/ha.png) - Only the master FE will execute the action in fact, unless the slave had seen the master as unavailable. +Only the master FE will execute the action in fact, unless the slave had seen the master as unavailable. ![Imagen](images/haaxn.png) + ## Scenarios -In the following diagramas, Portal is depicted as the component managing rules (assuming it will use the visual rules API). However, the same diagrams will aplly in the case of other actors, clients of the plain rules API. +In the following diagramas, Portal is depicted as the component managing rules (assuming it will use the visual rules +API). However, the same diagrams will aplly in the case of other actors, clients of the plain rules API. ### Adding a rule (without HA) -![Imagen](images/add_rule_sinHA.png) +![Imagen](images/add_rule_sinHA.png) ### Adding a rule (with HA) -![Imagen](images/add_rule_ha.png) +![Imagen](images/add_rule_ha.png) ### Notification from Context Broker -![Imagen](images/notify.png) +![Imagen](images/notify.png) ### Executing an action (master) -![Imagen](images/fire_action.png) +![Imagen](images/fire_action.png) ### Executing an action (slave) + ![Imagen](images/fire_action_slave.png) ### No-update action -![Imagen](images/nosignal.png) +![Imagen](images/nosignal.png) ### Update action with token renewal + ![Imagen](images/token_access.png) diff --git a/documentation/configuration.md b/documentation/configuration.md index 61b597d1..7d044753 100644 --- a/documentation/configuration.md +++ b/documentation/configuration.md @@ -1,86 +1,99 @@ + ## Configuration + There are two ways of configuring Perseo CEP: -* The default Basic Configuration is read from the `config.js` file in the root of the project folder. -* Some pieces of configuration can be overriden using environment variables, as it is explained in the following section. - + +- The default Basic Configuration is read from the `config.js` file in the root of the project folder. +- Some pieces of configuration can be overriden using environment variables, as it is explained in the following + section. + ### Environment Variables Configuration + The following table shows the environment variables available for Perseo configuration: -| Environment variable | Description | -|:------------------------- |:-------------------------------------- | -| PERSEO_ENDPOINT_HOST | Host where the CEP will listen. | -| PERSEO_ENDPOINT_PORT | Port where the CEP will listen. | -| PERSEO_MONGO_ENDPOINT | Endpoint (host[:port]) list for Mongo DB. | -| PERSEO_MONGO_REPLICASET | ReplicaSet name for Mongo DB. | -| PERSEO_MONGO_USER | User for Mongo DB. | -| PERSEO_MONGO_PASSWORD | Password for Mongo DB. g| -| PERSEO_CORE_URL | Full URL where Perseo Core is listening (e.g: http://63.34.124.1:8080). | -| PERSEO_NEXT_URL | Full URL where Perseo Core replicated node is listening. Same format as above. | -| PERSEO_ORION_URL | Full URL of the Orion Context Broker (e.g: http://64.124.28.15:1026). | -| PERSEO_LOG_LEVEL | Log level. | -| PERSEO_SMTP_HOST | Host of the SMTP server | -| PERSEO_SMTP_PORT | Port of the SMTP server | -| PERSEO_SMTP_SECURE | `true` if SSL should be used with the SMTP server | -| PERSEO_SMTP_AUTH_USER | Authentication data, the username | -| PERSEO_SMTP_AUTH_PASS | Authentication data, the password for the user | -| PERSEO_SMTP_TLS_REJECTUNAUTHORIZED | Reject if unauthorized security is found (i.e. selfsigned certificates). Default is false. | -| PERSEO_SMS_URL | URL for sending SMSs (SMPP Adapter) | -| PERSEO_SMS_API_KEY | API KEY for sending SMSs, if necessary. Only for the SMPP Adapter simulator | -| PERSEO_SMS_API_SECRET | API SECRET for sending SMSs, if necessary. Only for the SMPP Adapter simulator | -| PERSEO_SMS_FROM | Field `from` for the outgoing SMSs. Required by the SMPP Adapter | -| PERSEO_SMPP_HOST | Host of the SMPP server | -| PERSEO_SMPP_PORT | Port of the SMPP server | -| PERSEO_SMPP_SYSTEMID | SystemID for the user of the SMPP server | -| PERSEO_SMPP_PASSWORD | Password for the user of the SMPP server | -| PERSEO_SMPP_FROM | Number from SMS are sending by SMPP server | -| PERSEO_SMPP_ENABLED | SMPP is default method for SMS instead of use SMS gateway | -| PERSEO_NOTICES_PATH | Path for incoming notices, default value '/notices' | -| PERSEO_RULES_PATH | Path for incoming rules, default value '/rules' | +| Environment variable | Description | +| :--------------------------------- | :----------------------------------------------------------------------------------------- | +| PERSEO_ENDPOINT_HOST | Host where the CEP will listen. | +| PERSEO_ENDPOINT_PORT | Port where the CEP will listen. | +| PERSEO_MONGO_ENDPOINT | Endpoint (`host[:port]`) list for Mongo DB. | +| PERSEO_MONGO_REPLICASET | ReplicaSet name for Mongo DB. | +| PERSEO_MONGO_USER | User for Mongo DB. | +| PERSEO_MONGO_PASSWORD | Password for Mongo DB. g | +| PERSEO_CORE_URL | Full URL where Perseo Core is listening (e.g: `http://63.34.124.1:8080`). | +| PERSEO_NEXT_URL | Full URL where Perseo Core replicated node is listening. Same format as above. | +| PERSEO_ORION_URL | Full URL of the Orion Context Broker (e.g: `http://64.124.28.15:1026`). | +| PERSEO_LOG_LEVEL | Log level. | +| PERSEO_SMTP_HOST | Host of the SMTP server | +| PERSEO_SMTP_PORT | Port of the SMTP server | +| PERSEO_SMTP_SECURE | `true` if SSL should be used with the SMTP server | +| PERSEO_SMTP_AUTH_USER | Authentication data, the username | +| PERSEO_SMTP_AUTH_PASS | Authentication data, the password for the user | +| PERSEO_SMTP_TLS_REJECTUNAUTHORIZED | Reject if unauthorized security is found (i.e. selfsigned certificates). Default is false. | +| PERSEO_SMS_URL | URL for sending SMSs (SMPP Adapter) | +| PERSEO_SMS_API_KEY | API KEY for sending SMSs, if necessary. Only for the SMPP Adapter simulator | +| PERSEO_SMS_API_SECRET | API SECRET for sending SMSs, if necessary. Only for the SMPP Adapter simulator | +| PERSEO_SMS_FROM | Field `from` for the outgoing SMSs. Required by the SMPP Adapter | +| PERSEO_SMPP_HOST | Host of the SMPP server | +| PERSEO_SMPP_PORT | Port of the SMPP server | +| PERSEO_SMPP_SYSTEMID | SystemID for the user of the SMPP server | +| PERSEO_SMPP_PASSWORD | Password for the user of the SMPP server | +| PERSEO_SMPP_FROM | Number from SMS are sending by SMPP server | +| PERSEO_SMPP_ENABLED | SMPP is default method for SMS instead of use SMS gateway | +| PERSEO_NOTICES_PATH | Path for incoming notices, default value '/notices' | +| PERSEO_RULES_PATH | Path for incoming rules, default value '/rules' | ### Basic Configuration -In order to have perseo running, there are several basic pieces of information to fill: -* `config.logLevel`: level for log messages (`FATAL`, `ERROR`, `INFO` or `DEBUG`) -* `config.perseoCore.rulesURL`: URL for management of EPL rules at core. -* `config.perseoCore.noticesURL`: URL for processing events at core rule engine. -* `config.perseoCore.interval`: interval for refreshing rules at core rule engine (milliseconds). -* `config.smtp.port`: port for sending email. -* `config.smtp.host`: host for sending email. -* `config.smtp.secure`: defines if the connection should use SSL (if true) or not (if false). -* `config.smtp.auth.user`: authentication data, the username. -* `config.smtp.auth.pass`: authentication data, the password for the user. -* `config.smtp.tls.rejectUnauthorized`: reject if unauthorized security is found (i.e. selfsigned certificates). Default is false. -* `config.sms.URL`: URL for sending SMSs. -* `config.sms.from`: Field `from` for the outgoing SMSs. Required by the SMPP Adapter. -* `config.sms.API_KEY`: API KEY for sending SMSs, if necessary. Only for the SMPP Adapter simulator. -* `config.sms.API_SECRET`: API SECRET for sending SMSs, if necessary. Only for the SMPP Adapter simulator. -* `config.smpp.host`: Host of the SMPP server. -* `config.smpp.port`: Port of the SMPP server. -* `config.smpp.systemid`: SystemID for the user of the SMPP server -* `config.smpp.password`: Password for the user of the SMPP server -* `config.smpp.from`: Number from SMS are sending by SMPP server -* `config.smpp.enabled`: SMPP is default method for SMS instead of use SMS gateway. -* `config.orion.URL`: Context Broker base URL, e.g. https://orion.example.com:1026 -* `config.mongo.URL`: URL for connecting mongoDB. -* `config.executionsTTL`: Time-To-Live for documents of action executions (seconds). -* `config.checkDB.delay`: Number of milliseconds to check DB connection (see [database aspects](admin.md#database-aspects) documentation for mode detail). -* `config.checkDB.reconnectTries`: Number of of attempts to reconnect (see [database aspects](admin.md#database-aspects) documentation for mode detail). -* `config.checkDB.reconnectInterval`: Number of milliseconds to wait between attempts to reconnect (see [database aspects](admin.md#database-aspects) documentation for mode detail). -* `config.checkDB.bufferMaxEntries`: Number of operations buffered up before giving up on getting a working connection (see [database aspects](admin.md#database-aspects) documentation for mode detail). +In order to have perseo running, there are several basic pieces of information to fill: +- `config.logLevel`: level for log messages (`FATAL`, `ERROR`, `INFO` or `DEBUG`) +- `config.perseoCore.rulesURL`: URL for management of EPL rules at core. +- `config.perseoCore.noticesURL`: URL for processing events at core rule engine. +- `config.perseoCore.interval`: interval for refreshing rules at core rule engine (milliseconds). +- `config.smtp.port`: port for sending email. +- `config.smtp.host`: host for sending email. +- `config.smtp.secure`: defines if the connection should use SSL (if true) or not (if false). +- `config.smtp.auth.user`: authentication data, the username. +- `config.smtp.auth.pass`: authentication data, the password for the user. +- `config.smtp.tls.rejectUnauthorized`: reject if unauthorized security is found (i.e. selfsigned certificates). + Default is false. +- `config.sms.URL`: URL for sending SMSs. +- `config.sms.from`: Field `from` for the outgoing SMSs. Required by the SMPP Adapter. +- `config.sms.API_KEY`: API KEY for sending SMSs, if necessary. Only for the SMPP Adapter simulator. +- `config.sms.API_SECRET`: API SECRET for sending SMSs, if necessary. Only for the SMPP Adapter simulator. +- `config.smpp.host`: Host of the SMPP server. +- `config.smpp.port`: Port of the SMPP server. +- `config.smpp.systemid`: SystemID for the user of the SMPP server +- `config.smpp.password`: Password for the user of the SMPP server +- `config.smpp.from`: Number from SMS are sending by SMPP server +- `config.smpp.enabled`: SMPP is default method for SMS instead of use SMS gateway. +- `config.orion.URL`: Context Broker base URL, e.g. `https://orion.example.com:1026` +- `config.mongo.URL`: URL for connecting mongoDB. +- `config.executionsTTL`: Time-To-Live for documents of action executions (seconds). +- `config.checkDB.delay`: Number of milliseconds to check DB connection (see + [database aspects](admin.md#database-aspects) documentation for mode detail). +- `config.checkDB.reconnectTries`: Number of attempts to reconnect (see [database aspects](admin.md#database-aspects) + documentation for mode detail). +- `config.checkDB.reconnectInterval`: Number of milliseconds to wait between attempts to reconnect (see + [database aspects](admin.md#database-aspects) documentation for mode detail). +- `config.checkDB.bufferMaxEntries`: Number of operations buffered up before giving up on getting a working connection + (see [database aspects](admin.md#database-aspects) documentation for mode detail). Options for HA: -* `config.isMaster`: `true` if this one is the master or `false` it it is the slave. -* `config.slaveDelay`: Slave's delay to try to execute an action (milliseconds). -* `config.nextCore.rulesURL`: URL for management of EPL rules at *replicated* core. If set, the rules will be propagated to that one also. -* `config.nextCore.noticesURL`: URL for processing events at *replicated* core rule engine. If set, the events will be propagated to that one also. + +- `config.isMaster`: `true` if this one is the master or `false` it is the slave. +- `config.slaveDelay`: Slave's delay to try to execute an action (milliseconds). +- `config.nextCore.rulesURL`: URL for management of EPL rules at _replicated_ core. If set, the rules will be + propagated to that one also. +- `config.nextCore.noticesURL`: URL for processing events at _replicated_ core rule engine. If set, the events will be + propagated to that one also. Options for Authentication through PEP (for update action) -* `config.authentication.host`: host (keyStone) to exchange trust tokens for access tokens -* `config.authentication.port`: port, -* `config.authentication.user`: provisioned user for CEP in Keystone -* `config.authentication.password`: provisioned password for CEP in Keystone +- `config.authentication.host`: host (keyStone) to exchange trust tokens for access tokens +- `config.authentication.port`: port, +- `config.authentication.user`: provisioned user for CEP in Keystone +- `config.authentication.password`: provisioned password for CEP in Keystone -URL format for mongoDB could be found at http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html +URL format for mongoDB could be found at `http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html` diff --git a/documentation/deployment.md b/documentation/deployment.md index 7814ccc6..4417e9ab 100644 --- a/documentation/deployment.md +++ b/documentation/deployment.md @@ -1,24 +1,27 @@ + ## Deployment ### Dependencies -The CEP is a standard Node.js app and doesn't require more dependencies than the Node.js interpreter (0.10) and the NPM package utility. +The CEP is a standard Node.js app and doesn't require more dependencies than the Node.js interpreter (0.10) and the NPM +package utility. A mongoDB 3.2 database should be working and accesible before perseo can be started ### Build your own Docker image -There is also the possibility to build your own local Docker image of the Perseo-FE component. + +You can also build your own local Docker image of the Perseo-FE component. To do it, follow the next steps once you have installed Docker in your machine: -1. Navigate to the path where the component repository was cloned. -2. Launch a Docker build - * Using the default NodeJS version of the operating system used defined in FROM keyword of Dockerfile: +1. Navigate to the path where the component repository was cloned. +2. Launch a Docker build + - Using the default NodeJS version of the operating system used defined in FROM keyword of Dockerfile: ```bash sudo docker build -f Dockerfile -t perseo-fe . ``` - * Using an alternative NodeJS version: + - Using an alternative NodeJS version: ```bash sudo docker build --build-arg NODEJS_VERSION=0.10.46 -f Dockerfile -t perseo-fe . ``` @@ -28,76 +31,82 @@ To do it, follow the next steps once you have installed Docker in your machine: The last development version is uploaded as a Docker image to Docker Hub for every PR merged into the `master` branch. Perseo FE needs some components to be present event to be started. Those components can be configured using: -* Environment variables, as in the following example: -``` +- Environment variables, as in the following example: + +```bash docker run -e "PERSEO_MONGO_HOST=127.0.0.1" -e "PERSEO_CORE_URL=http://127.0.0.1:8080" telefonicaiot/perseo-fe ``` -* Or links to other docker images running in the same docker host, as in the following example: -``` +- Or links to other docker images running in the same docker host, as in the following example: + +```bash docker run --link corehost:corehost --link mongodb:mongodb fiwareiotplatform/perseocore ``` -In order to link other Docker images to the Perso FE image, take into account that it has two requirements: -` -* A **Mongo DB** instance: the image looks for a Mongo DB instance in the `mongodb` alias (port `27017`) when it starts. -* A **Perseo Core** instance: the instance expects a instance of Perseo Core running in the alias `corehost`, port 8080. +In order to link other Docker images to the Perso FE image, take into account that it has two requirements: ` + +- A **Mongo DB** instance: the image looks for a Mongo DB instance in the `mongodb` alias (port `27017`) when it + starts. +- A **Perseo Core** instance: the instance expects a instance of Perseo Core running in the alias `corehost`, + port 8080. For the full perseo stack to work, both instances should be linked to their appropriate alias. ### Running together with Perseo Core and Orion Context Broker -Below it is shown how to run together [Perseo Core](http://github.com/telefonicaid/perseo-core) and Perseo Front-End. +Below it is shown how to run together [Perseo Core](http://github.com/telefonicaid/perseo-core) and Perseo frontend. -Assuming there is a Mongo DB container already running (named `mongo`) and an [Orion Context Broker](http://github.com/telefonicaid/fiware.orion) one (named `orion`). +Assuming there is a Mongo DB container already running (named `mongo`) and an +[Orion Context Broker](https://github.com/telefonicaid/fiware-orion) one (named `orion`). -First a container running Perseo Core has to be instantiated and run (hostname of this container will be `perseocore` and will be listening on port `8080`): +First a container running Perseo Core has to be instantiated and run (hostname of this container will be `perseocore` +and will be listening on port `8080`): -``` +```bash docker run -d --name perseo_core -h perseocore -p 8080:8080 telefonicaiot/perseo-core:master -perseo_fe_url :9090 ``` -where must be the host name or IP address of the *machine hosting the Perseo FE Container*. Please note that it is a good idea to -expose port `8080` to the host so that it can be verified that Perseo Core is up and running. +where must be the hostname or IP address of the \_machine hosting the Perseo FE Container*. Please note +that it is a good idea to expose port `8080` to the host so that it can be verified that Perseo Core is up and running. -Then a container running Perseo Front-End has to be instantiated and run: +Then a container running Perseo frontend has to be instantiated and run: -``` +```bash docker run -d -p 9090:9090 --name perseo_fe -h perseo --link perseo_core --link mongo --link orion -e "PERSEO_MONGO_HOST=mongo" -e "PERSEO_CORE_URL=http://perseocore:8080" -e "PERSEO_LOG_LEVEL=debug" -e "PERSEO_ORION_URL=http://orion:1026/v1/updateContext" telefonicaiot/perseo-fe:master ``` -Please note that we use the name `perseocore` to refer to the container where Perseo Core is running (previously linked). Similarly we use use the names `orion` and -`mongo` to refer to the containers where Mongo DB and Orion Context Broker are running. +Please note that we use the name `perseocore` to refer to the container where Perseo Core is running (previously +linked). Similarly we use the names `orion` and `mongo` to refer to the containers where Mongo DB and Orion Context +Broker are running. -To check that Perseo Front-End has been instantiated properly you can run: +To check that Perseo frontend has been instantiated properly you can run: -``` +```bash curl http://localhost:9090/version ``` or -``` +```bash curl http://localhost:9090/rules ``` To check that Perseo Core has been instantiated properly you can run: -``` +```bash curl http://localhost:8080/perseo-core/version ``` You can get access to the logs generated by both components: -``` +```bash docker logs perseo_fe ``` -``` +```bash docker exec perseo_core tail -f /var/log/perseo/perseo-core.log ``` - ### Installation from RPM This project provides the specs to create the RPM Package for the project, that may (in the future) be installed in a @@ -106,30 +115,30 @@ package repository. To generate the RPM, checkout the project to a machine with the RPM Build Tools installed, and, from the `rpm/` folder, execute the following command: -``` +```bash ./create-rpm.sh 0.1 1 ``` The create-rpm.sh script uses the following parameters: -* CEP version (0.1 in the example above), which is the base version of the software -* CEP release (1 in the example above), tipically set with the commit number corresponding to the RPM. +- CEP version (0.1 in the example above), which is the base version of the software +- CEP release (1 in the example above), tipically set with the commit number corresponding to the RPM. This command will generate some folders, including one called RPMS, holding the RPM created for every architecture (x86_64 is currently generated). In order to install the generated RPM from the local file, use the following command: -``` +```bash yum --nogpgcheck localinstall perseo-cep-0.1-1.x86_64.rpm ``` -It should automatically download all the dependencies provided they are available (Node.js and NPM may require the -EPEL repositories to be added). +It should automatically download all the dependencies provided they are available (Node.js and npm may require the EPEL +repositories to be added). The RPM package can also be deployed in a artifact repository and the installed using: -``` +```bash yum install perseo-cep ``` @@ -137,31 +146,39 @@ NOTE: Perseo CEP Core is not installed as part of the dependencies in the RPM, s must be provided and configured for Perseo to work properly. #### Activate service + The perseo service is disabled once its installed. In order to enable it, use the following command: -``` + +```bash service perseo start ``` ### Installation from Sources + #### Installation Just checkout this directory and install the Node.js dependencies using: -``` +```bash npm install --production ``` The CEP should be then ready to be configured and used. #### Undeployment -In order to undeploy the proxy just kill the process and remove the directory. +In order to undeploy the proxy just kill the process and remove the directory. ### Log Rotation -Independently of how the service is installed, the log files will need an external rotation (e.g.: the logrotate command) to avoid disk full problems. -Logrotate is installed as RPM dependency along with perseo. The system is configured to rotate every day and whenever the log file size is greater than 100MB (checked very 30 minutes by default): -* For daily rotation: /etc/logrotate.d/logrotate-perseo-daily: which enables daily log rotation -* For size-based rotation: - * /etc/sysconfig/logrotate-perseo-size: in addition to the previous rotation, this file ensures log rotation if the log file grows beyond a given threshold (100 MB by default) - * /etc/cron.d/cron-logrotate-perseo-size: which ensures the execution of etc/sysconfig/logrotate-perseo-size at a regular frecuency (default is 30 minutes) +Independently of how the service is installed, the log files will need an external rotation (e.g.: the logrotate +command) to avoid disk full problems. + +Logrotate is installed as RPM dependency along with perseo. The system is configured to rotate every day and whenever +the log file size is greater than 100MB (checked very 30 minutes by default): + +- For daily rotation: `/etc/logrotate.d/logrotate-perseo-daily` : which enables daily log rotation +- For size-based rotation: `/etc/sysconfig/logrotate-perseo-size`: in addition to the previous rotation, this file + ensures log rotation if the log file grows beyond a given threshold (100 MB by default) + ``/etc/cron.d/cron-logrotate-perseo-size`: which ensures the execution of`etc/sysconfig/logrotate-perseo-size` at a + regular frecuency (default is 30 minutes) diff --git a/documentation/development.md b/documentation/development.md index 4c0d2b6b..93174481 100644 --- a/documentation/development.md +++ b/documentation/development.md @@ -1,10 +1,11 @@ - ## Development documentation ### Project build + The project is managed using npm. For a list of available task, type + ```bash npm run ``` @@ -12,7 +13,8 @@ npm run The following sections show the available options in detail. ### Testing -[Mocha](http://visionmedia.github.io/mocha/) Test Runner + [Should.js](https://shouldjs.github.io/) Assertion Library. + +[Mocha](https://mochajs.org/) Test Runner + [Should.js](https://shouldjs.github.io/) Assertion Library. The test environment is preconfigured to run BDD testing style. @@ -25,10 +27,10 @@ npm test ``` ### Coding guidelines + jshint -Uses provided .jshintrc flag file. -To check source code style, type +Uses provided .jshintrc flag file. To check source code style, type ```bash npm run lint @@ -36,8 +38,7 @@ npm run lint ### Continuous testing -Support for continuous testing by modifying a src file or a test. -For continuous testing, type +Support for continuous testing by modifying a src file or a test. For continuous testing, type ```bash npm run test:watch @@ -50,6 +51,7 @@ npm run watch ``` ### Code Coverage + Istanbul Analizes the code coverage of your tests. @@ -63,7 +65,8 @@ npm run test:coverage ### Clean -Removes `node_modules` and `coverage` folders, and `package-lock.json` file so that a fresh copy of the project is restored. +Removes `node_modules` and `coverage` folders, and `package-lock.json` file so that a fresh copy of the project is +restored. ```bash # Use git-bash on Windows diff --git a/documentation/errors.md b/documentation/errors.md index 138bd95c..d51072dd 100644 --- a/documentation/errors.md +++ b/documentation/errors.md @@ -4,57 +4,51 @@ A list of errors returned by perseo-fe ## Notices -| label | HTTP code | message | description | -| ----- | --------- | ------- | ----------- | -| INVALID_NOTICE | 400 | invalid notice format | there is a generic problem with the notice content. Missing fields | -| ID_ATTRIBUTE | 400 | id as attribute | an attribute has "id" as name | -| TYPE_ATTRIBUTE | 400 |type as attribute | an attribute has "type" as name | -| INVALID_LOCATION | 400 | invalid location | a field for location has an invalid format | -| INVALID_LONGITUDE | 400 | longitude is not valid | the longitude component is not valid | -| INVALID_LATITUDE | 400 | latitude is not valid | the latitude component is not valid | -| INVALID_DATETIME | 400 | datetime is not valid | a field for date is not valid | +| label | HTTP code | message | description | +| ----------------- | --------- | ---------------------- | --------------------------------------------------------- | +| INVALID_NOTICE | 400 | invalid notice format | a generic problem with the notice content. Missing fields | +| ID_ATTRIBUTE | 400 | ID as attribute | an attribute has "id" as name | +| TYPE_ATTRIBUTE | 400 | type as attribute | an attribute has "type" as name | +| INVALID_LOCATION | 400 | invalid location | a field for location has an invalid format | +| INVALID_LONGITUDE | 400 | longitude is not valid | the longitude component is not valid | +| INVALID_LATITUDE | 400 | latitude is not valid | the latitude component is not valid | +| INVALID_DATETIME | 400 | datetime is not valid | a field for date is not valid | ## Paths -| label | HTTP code | message | description | -| ----- | --------- | ------- | ----------- | -| EMPTY | 400 | service/subservice: empty component| the servicepath has an empty component | -| INVALID_CHAR | 400 | service/subservice: invalid character | invalid character in service/servicepath | -| ABS_PATH | 400 | subservice: must be absolute | the servicepath must be absolute, i.e must begin with /| -| TOO_LONG | 400 | service/subservice: too long string | service, servicepath or an indivual component is too long| -| TOO_MANY | 400 | service/subservice: too many components | the service path has too many components | - +| label | HTTP code | message | description | +| ------------ | --------- | --------------------------------------- | --------------------------------------------------------- | +| EMPTY | 400 | service/subservice: empty component | the servicepath has an empty component | +| INVALID_CHAR | 400 | service/subservice: invalid character | invalid character in service/servicepath | +| ABS_PATH | 400 | subservice: must be absolute | the servicepath must be absolute, i.e must begin with / | +| TOO_LONG | 400 | service/subservice: too long string | service, servicepath or an indivual component is too long | +| TOO_MANY | 400 | service/subservice: too many components | the service path has too many components | ## Rules -| label | HTTP code | message | description | -| ----- | --------- | ------- | ----------- | -| MISSING_RULE_NAME | 400 | missing rule name | the name of the rule is missing | -| EMPTY_RULE | 400 | empty rule, missing text or nosignal | the text and nosignal fields are missing | -| EXISTING_RULE | 400 | rule exists | the rule exits already and cannot be created | -| MUST_BE_STRING_RULE_NAME | 400 | name must be a string | the rule name field is not a string | -| TOO_LONG_RULE_NAME | 400 | rule name too long | the name of the rule exceeds the max length | -| INVALID_RULE_NAME | 400 | invalid char in name | the name of the rule contains an invalid character | -| INVALID_CHECK_INTERVAL | 400 | invalid check interval | the check interval for a nosignal rule is not valid | -| RULE_NOTFOUND | 400 | rule not found | the rule does not exits (for retrieving or updating) | - +| label | HTTP code | message | description | +| ------------------------ | --------- | ------------------------------------ | ---------------------------------------------------- | +| MISSING_RULE_NAME | 400 | missing rule name | the name of the rule is missing | +| EMPTY_RULE | 400 | empty rule, missing text or nosignal | the text and nosignal fields are missing | +| EXISTING_RULE | 400 | rule exists | the rule exits already and cannot be created | +| MUST_BE_STRING_RULE_NAME | 400 | name must be a string | the rule name field is not a string | +| TOO_LONG_RULE_NAME | 400 | rule name too long | the name of the rule exceeds the max length | +| INVALID_RULE_NAME | 400 | invalid char in name | the name of the rule contains an invalid character | +| INVALID_CHECK_INTERVAL | 400 | invalid check interval | the check interval for a nosignal rule is not valid | +| RULE_NOTFOUND | 400 | rule not found | the rule does not exits (for retrieving or updating) | ## Log level -| label | HTTP code | message | description | -| ----- | --------- | ------- | ----------- | -| INVALID_LOG_LEVEL | 400 | invalid log level | The log level sent is not valid value| - +| label | HTTP code | message | description | +| ----------------- | --------- | ----------------- | ------------------------------------- | +| INVALID_LOG_LEVEL | 400 | invalid log level | The log level sent is not valid value | ## Actions -| label | HTTP code | message | description | -| ----- | --------- | ------- | ----------- | -| MISSING_ACTION_TYPE | 400 | missing type in action | the field type for the action is missing | -| UNKNOWN_ACTION_TYPE | 400 | unknown action type | the field type has an invalid value | -| MISSING_ACTION_RULE_NAME | 400 | missing rule name for action | the field for the rule name is missing | -| ID_ATTRIBUTE | 400 | id as attribute | an attribute has 'id' as name | -| TYPE_ATTRIBUTE | 400 | type as attribute | an attribute has 'type' as name | - - - +| label | HTTP code | message | description | +| ------------------------ | --------- | ---------------------------- | ---------------------------------------- | +| MISSING_ACTION_TYPE | 400 | missing type in action | the field type for the action is missing | +| UNKNOWN_ACTION_TYPE | 400 | unknown action type | the field type has an invalid value | +| MISSING_ACTION_RULE_NAME | 400 | missing rule name for action | the field for the rule name is missing | +| ID_ATTRIBUTE | 400 | ID as attribute | an attribute has 'id' as name | +| TYPE_ATTRIBUTE | 400 | type as attribute | an attribute has 'type' as name | diff --git a/documentation/logs.md b/documentation/logs.md index 1af8b054..6c6f5878 100644 --- a/documentation/logs.md +++ b/documentation/logs.md @@ -7,80 +7,84 @@ Logs have levels `FATAL`, `ERROR`, `INFO` and `DEBUG`. The log level must be set * Default log level. Can be one of: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL' * @type {string} */ -config.logLevel = 'INFO'; +config.logLevel = "INFO"; ``` - In order to have logs that can enable alarms being raised and ceased, `ERROR` level (or one with more detail) should be -set in the configuration file. Generally, `ERROR` level should be used at least, as some important information can be +In order to have logs that can enable alarms being raised and ceased, `ERROR` level (or one with more detail) should be +set in the configuration file. Generally, `ERROR` level should be used at least, as some important information can be lost in other case (e.g. when set to `FATAL`) +Each log line contains several fields of the form _name_`=` _value_, separated by `|` -Each log line contains several fields of the form *name*`=` *value*, separated by `|` -* `time` time of the log -* `lvl` log level -* `from` IP from X-Real-IP header field or client's IP if missing -* `corr` correlator from the incoming request that caused the current transaction. It allows trace a global operation through several systems. If not present as field header in the incoming request, the internal transaction id will be used. -* `trans` internal transaction id -* `op` description of the operation being done. Generally, the path of the URL invoked. -* `msg` message +- `time` time of the log +- `lvl` log level +- `from` IP from X-Real-IP header field or client's IP if missing +- `corr` correlator from the incoming request that caused the current transaction. It allows trace a global operation + through several systems. If not present as field header in the incoming request, the internal transaction ID will be + used. +- `trans` internal transaction ID +- `op` description of the operation being done. Generally, the path of the URL invoked. +- `msg` message ``` time=2014-12-16T12:01:46.487Z | lvl=ERROR | corr=62d2f662-37de-4dcf-ba02-013642501a2d | trans=62d2f662-37de-4dcf-ba02-013642501a2d | op=/actions/do | msg=emailAction.SendMail connect ECONNREFUSED ``` -Logs for errors can show additional info in the message, giving a hint of the root cause of the problem (`ECONNREFUSED`,`ENOTFOUND`, `ECONNRESET`, ...) +Logs for errors can show additional info in the message, giving a hint of the root cause of the problem +(`ECONNREFUSED`,`ENOTFOUND`, `ECONNRESET`, ...) The log level can be changed at run-time, with an HTTP PUT request ``` curl --request PUT :/admin/log?level= - ``` +``` The log level can be retrieved at run-time, with an HTTP GET request ``` curl --request GET :/admin/log - ``` - +``` # Alarms Alarm levels -* **Critical** - The system is not working -* **Major** - The system has a problem that degrades the service and must be addressed -* **Warning** - It is happening something that must be notified +- **Critical** - The system is not working +- **Major** - The system has a problem that degrades the service and must be addressed +- **Warning** - It is happening something that must be notified + +Alarms will be inferred from logs typically. For each alarm, a 'detection strategy' and a 'stop condition' is provided +(note that the stop condition is not shown in the next table, but it is included in the detailed description for each +alarm below). The conditions are used for detecting logs that should raise the alarm and cease it respectively. The log +level for alarms is `ERROR` if no other level is said. The message in a condition should be taken as a **prefix** of the +possible message in the log. We recommend you to ignore starting spaces in each field in order to avoid missing a log +that should meet the condition in other case. -Alarms will be inferred from logs typically. For each alarm, a 'detection strategy' and a 'stop condition' is provided -(note that the stop condition is not shown in the next table, but it is included in the detailed description for each -alarm below). The conditions are used for detecting logs that should raise the alarm and cease it respectively. The log -level for alarms is `ERROR` if no other level is said. The message in a condition should be taken as a **prefix** of the -possible message in the log. We recommend you to ignore starting spaces in each field in order to avoid missing a log -that should meet the condition in other case. +Some errors cause perseo to fail to start up. They have `FATAL` level and are caused by: -Some errors avoid perseo to start up. They have `FATAL` level and are caused by -* There is no connection to database -* There is no connection to perseo-core +- Lack of connection to database +- Lack of connection to perseo-core They should be solved in order to get perseo running. ## Alarm conditions -| Alarm ID | Severity | Description -| :------- | :------- | :----------- -| [START](#start) | Critical | Impossible to start perseo -| [CORE](#core) | Major | Refreshing of rules at core is failing. -| [POST_EVENT](#post_event) | Critical | Sending an event to core is failing. -| [EMAIL](#email) | Critical | Trying to execute an email action is failing. -| [SMS](#sms) | Critical | Trying to execute an SMS action is failing. -| [SMPP](#smpp) | Critical | Trying to execute an SMPP action is failing. -| [ORION](#orion) | Critical | Trying to execute an update action is failing -| [DATABASE](#database) | Critical | There is a problem in connection to DB. -| [DATABASE_ORION](#database_orion) | Critical | There is a problem in connection to Orion DB (accessed by no-signal checker) -| [AUTH](#auth) | Major | There is a problem in connection to Keystone. Update-actions to Orion through PEP are not working -| [LOOP](#loop) | Major | Some rules can be provoking an infinite loop of triggered actions +| Alarm ID | Severity | Description | +| :-------------------------------- | :------- | :--------------------------------------------------------------------------------------- | +| [START](#start) | Critical | Impossible to start perseo | +| [CORE](#core) | Major | Refreshing of rules at core is failing. | +| [POST_EVENT](#post_event) | Critical | Sending an event to core is failing. | +| [EMAIL](#email) | Critical | Trying to execute an email action is failing. | +| [SMS](#sms) | Critical | Trying to execute an SMS action is failing. | +| [SMPP](#smpp) | Critical | Trying to execute an SMPP action is failing. | +| [ORION](#orion) | Critical | Trying to execute an update action is failing | +| [DATABASE](#database) | Critical | A problem in connection to DB. | +| [DATABASE_ORION](#database_orion) | Critical | A problem in connection to Orion DB (accessed by no-signal checker) | +| [AUTH](#auth) | Major | A problem in connection to Keystone. Update-actions to Orion through PEP are not working | +| [LOOP](#loop) | Major | Some rules can be provoking an infinite loop of triggered actions | + ### Alarm START **Severity**: Critical @@ -91,10 +95,13 @@ They should be solved in order to get perseo running. **Description**: Starting perseo is failing. -**Action**: Check HTTP connectivity to perseo-core from perseo and connectivity to the mongoDB, as set in the config file. +**Action**: Check HTTP connectivity to perseo-core from perseo and connectivity to the mongoDB, as set in the config +file. + +--- -____ + ### Alarm CORE **Severity**: Major @@ -105,11 +112,13 @@ ____ **Description**: Communication with core is failing. -**Action**: Check HTTP connectivity to perseo-core from perseo. Also check deployment of perseo-core at the right -URL path +**Action**: Check HTTP connectivity to perseo-core from perseo. Also check deployment of perseo-core at the right URL +path + +--- -____ + ### Alarm POST_EVENT **Severity**: Critical @@ -120,10 +129,13 @@ ____ **Description**: Sending an event to core is failing. -**Action**: Check HTTP connectivity to perseo-core from perseo. Also check deployment of perseo-core at the right URL path +**Action**: Check HTTP connectivity to perseo-core from perseo. Also check deployment of perseo-core at the right URL +path + +--- -____ + ### Alarm EMAIL **Severity**: Critical @@ -135,8 +147,11 @@ ____ **Description**: Trying to execute an email action is failing. **Action**: Check the configured SMTP Server is accessible and working properly -____ + +--- + + ### Alarm SMS **Severity**: Critical @@ -149,8 +164,10 @@ ____ **Action**: Check the configured SMPP Adapter Server is accessible and working properly -____ +--- + + ### Alarm SMPP **Severity**: Critical @@ -163,8 +180,10 @@ ____ **Action**: Check the configured SMPP Server is accessible and working properly -____ +--- + + ### Alarm ORION **Severity**: Critical @@ -177,8 +196,10 @@ ____ **Action**: Check the configured Orion path for updating is accessible and working properly -____ +--- + + ### Alarm DATABASE **Severity**: Critical @@ -192,8 +213,11 @@ ____ **Action**: Check configured mongoDB is up and running and is accessible from perseo. Check that databases exist. You can find more information about DB dynamics in the [database aspects](admin.md#database-aspects) documentation. -____ + +--- + + ### Alarm DATABASE_ORION **Severity**: Critical @@ -206,9 +230,10 @@ ____ **Action**: Check configured mongoDB is up and running and is accessible from perseo. Check that databases exist. -____ +--- + ### Alarm AUTH **Severity**: Major @@ -221,8 +246,10 @@ ____ **Action**: Check HTTP connectivity to Keystone. Check provisioned user and roles/grants. -____ +--- + + ### Alarm LOOP **Severity**: Major @@ -233,6 +260,5 @@ ____ **Description**: Some rules can be provoking an infinite loop of triggered actions. -**Action**: Report to client/product about possible loop with the pointed rule. Check log for the correlator in the log message - - +**Action**: Report to client/product about possible loop with the pointed rule. Check log for the correlator in the log +message diff --git a/documentation/metrics_api.md b/documentation/metrics_api.md index cbf0a19c..5ee559f7 100644 --- a/documentation/metrics_api.md +++ b/documentation/metrics_api.md @@ -1,15 +1,15 @@ #Metrics API -* [Introduction](#introduction) -* [Operations](#operations) - * [Get metrics](#get-metrics) - * [Reset metrics](#reset-metrics) - * [Get and reset](#get-and-reset) -* [Metrics](#metrics) +- [Introduction](#introduction) +- [Operations](#operations) + - [Get metrics](#get-metrics) + - [Reset metrics](#reset-metrics) + - [Get and reset](#get-and-reset) +- [Metrics](#metrics) ## Introduction -Perseo implements a REST-based API that can be used to get relevant operational metrics. +Perseo implements a REST-based API that can be used to get relevant operational metrics. [Top](#top) @@ -21,14 +21,13 @@ Perseo implements a REST-based API that can be used to get relevant operational GET /admin/metrics ``` -The response payload is a multi-level JSON tree storing the information in an structured way. This -structure is based on service and subservice (sometimesrefered to as "service path"). At any point -of the tree, the value of a key could be `{}` to mean that there isn't actual information associated -to that key. +The response payload is a multi-level JSON tree storing the information in an structured way. This structure is based on +service and subservice (sometimesrefered to as "service path"). At any point of the tree, the value of a key could be +`{}` to mean that there isn't actual information associated to that key. -At the first level there are two keys: **services** and **sum**. In sequence, **services** value is -an object whose keys are service names and whose values are objects with information about the corresponding -service. The **sum** value is an object with information for the aggregated information for all services. +At the first level there are two keys: **services** and **sum**. In sequence, **services** value is an object whose keys +are service names and whose values are objects with information about the corresponding service. The **sum** value is an +object with information for the aggregated information for all services. ``` { @@ -42,10 +41,10 @@ service. The **sum** value is an object with information for the aggregated info } ``` -Regarding service information objects, they use two keys: **subservs** and **sum**. In sequence, **subservs** -value is an object whose keys are subservice names and whose values are objects with information about -the corresponding subservice. The **sum** value is an object with information for the aggregated information -for all subservices in the given services. +Regarding service information objects, they use two keys: **subservs** and **sum**. In sequence, **subservs** value is +an object whose keys are subservice names and whose values are objects with information about the corresponding +subservice. The **sum** value is an object with information for the aggregated information for all subservices in the +given services. ``` { @@ -59,10 +58,9 @@ for all subservices in the given services. } ``` -Subservice names in the above structure are shown without the initial slash. E.g. if the subservice -name is (as used in the `Fiware-ServicePath` header) `/gardens` then the key used for it would be -`gardens` (without `/`). - +Subservice names in the above structure are shown without the initial slash. E.g. if the subservice name is (as used in +the `Fiware-ServicePath` header) `/gardens` then the key used for it would be `gardens` (without `/`). + Regarding subservice information object, keys are the name of the different metrics. ``` @@ -78,8 +76,8 @@ The list of metrics is provided in [metrics section](#metrics). Some additional remarks: -* Requests corresponding to invalid services or subservices are not included in the - payload (i.e. their associated metrics are just ignored). +- Requests corresponding to invalid services or subservices are not included in the payload (i.e. their associated + metrics are just ignored). [Top](#top) @@ -99,8 +97,8 @@ This operation resets all metrics, as if Perseo would had just been started. GET /admin/metrics?reset=true ``` -This operation (in fact, a variant of [get metrics](#get-metrics)) get results and, at the same time -in an atomical way, resets metrics. +This operation (in fact, a variant of [get metrics](#get-metrics)) get results and, at the same time in an atomical way, +resets metrics. [Top](#top) @@ -108,57 +106,57 @@ in an atomical way, resets metrics. The following metrics are common with other IoT platform componentes (e.g. Orion Contex Broker): -* **incomingTransactions**: number of requests consumed by Perseo. All kind of transactions - (no matter if they are ok transactions or error transactions) count for this metric. -* **incomingTransactionRequestSize**: total size (bytes) in requests associated to incoming transactions - ("in" from the point of view of Perseo). All kind of transactions (no matter if they are ok transactions - or error transactions) count for this metric. -* **incomingTransactionResponseSize**: total size (bytes) in responses associated to incoming transactions - ("out" from the point of view of Perseo). All kind of transactions (no matter if they are ok transactions - or error transactions) count for this metric. -* **incomingTransactionErrors**: number of incoming transactions resulting in error. -* **serviceTime**: average time to serve a transaction. All kind of transactions (no matter if they are ok - transactions or error transactions) count for this metric. -* **outgoingTransactions**: number of requests sent by Perseo (both notifications and forward requests to CPrs). - All kind of transactions (no matter if they are ok transactions or error transactions) count for this metric. -* **outgoingTransactionRequestSize**: total size (bytes) in requests associated to outgoing transactions - ("out" from the point of view of Perseo). All kind of transactions (no matter if they are ok transactions - or error transactions) count for this metric. -* **outgoingTransactionResponseSize**: total size (bytes) in responses associated to outgoing transactions - ("in" from the point of view of Perseo). All kind of transactions (no matter if they are ok transactions - or error transactions) count for this metric. -* **outgoingTransactionErrors**: number of outgoing transactions resulting in error. +- **incomingTransactions**: number of requests consumed by Perseo. All kind of transactions (no matter if they are ok + transactions or error transactions) count for this metric. +- **incomingTransactionRequestSize**: total size (bytes) in requests associated to incoming transactions ("in" from + the point of view of Perseo). All kind of transactions (no matter if they are OK transactions or error transactions) + count for this metric. +- **incomingTransactionResponseSize**: total size (bytes) in responses associated to incoming transactions ("out" from + the point of view of Perseo). All kind of transactions (no matter if they are OK transactions or error transactions) + count for this metric. +- **incomingTransactionErrors**: number of incoming transactions resulting in error. +- **serviceTime**: average time to serve a transaction. All kind of transactions (no matter if they are ok + transactions or error transactions) count for this metric. +- **outgoingTransactions**: number of requests sent by Perseo (both notifications and forward requests to CPrs). All + kind of transactions (no matter if they are OK transactions or error transactions) count for this metric. +- **outgoingTransactionRequestSize**: total size (bytes) in requests associated to outgoing transactions ("out" from + the point of view of Perseo). All kind of transactions (no matter if they are OK transactions or error transactions) + count for this metric. +- **outgoingTransactionResponseSize**: total size (bytes) in responses associated to outgoing transactions ("in" from + the point of view of Perseo). All kind of transactions (no matter if they are OK transactions or error transactions) + count for this metric. +- **outgoingTransactionErrors**: number of outgoing transactions resulting in error. The following metrics are used only by Perseo: -* Notifications received from CB - * **notifications**: total notifications - * **okNotifications**: invalid notifications - * **failedNotifications**: valid notifications -* rules creation operation - * **ruleCreation**: number of rule creation operations - * **okRuleCreation**: number of successful rule creation operations - * **failedRuleCreation**: number of unsuccessful rule creation operations -* rules deletion operation - * **ruleDeletion**: number of rule deletion operations - * **okRuleDeletion**: number of successful rule deletion operations - * **failedRuleDeletion**: number of unsuccessful rule deletion operations -* rules update operation - * **ruleUpdate**: number of rule update operations - * **okRuleUpdate**: number of successful rule update operations - * **failedRuleUpdate**: number of unsuccessful rule update operations -* **firedRules**: rules fired: number of fired rules -* actions executed (per action type and total) successfully, that is: - * **okActionEntityUpdate** - * **okActionSms** - * **okActionEmail** - * **okActionHttpPost** - * **okActionTwitter** -* actions executed (per action type and total) with failure, that is: - * **failedActionEntityUpdate** - * **failedActionSms** - * **failedActionEmail** - * **failedActionHttpPost** - * **failedActionTwitter** +- Notifications received from CB + - **notifications**: total notifications + - **okNotifications**: invalid notifications + - **failedNotifications**: valid notifications +- rules creation operation + - **ruleCreation**: number of rule creation operations + - **okRuleCreation**: number of successful rule creation operations + - **failedRuleCreation**: number of unsuccessful rule creation operations +- rules deletion operation + - **ruleDeletion**: number of rule deletion operations + - **okRuleDeletion**: number of successful rule deletion operations + - **failedRuleDeletion**: number of unsuccessful rule deletion operations +- rules update operation + - **ruleUpdate**: number of rule update operations + - **okRuleUpdate**: number of successful rule update operations + - **failedRuleUpdate**: number of unsuccessful rule update operations +- **firedRules**: rules fired: number of fired rules +- actions executed (per action type and total) successfully, that is: + - **okActionEntityUpdate** + - **okActionSms** + - **okActionEmail** + - **okActionHttpPost** + - **okActionTwitter** +- actions executed (per action type and total) with failure, that is: + - **failedActionEntityUpdate** + - **failedActionSms** + - **failedActionEmail** + - **failedActionHttpPost** + - **failedActionTwitter** [Top](#top) diff --git a/documentation/models.md b/documentation/models.md index e62f8ed5..5424aa79 100644 --- a/documentation/models.md +++ b/documentation/models.md @@ -1,138 +1,146 @@ + ## Collections + ### Rules -The collection 'rules' stores information about the rules to be sent to the core. It includes the VisualRule passed in + +The collection 'rules' stores information about the rules to be sent to the core. It includes the VisualRule passed in by the Portal for giving it back when the Portal requests it Fields: -* **_id** *ObjectId*: unique object id used by mongoDB -* **name** *string*: name of the rule -* **service** *string* : service which the rule belongs to. -* **subservice** *string*: subservice which the rule belongs to. -* **tex** *string*: EPL sentence for the rule, to be propagated to core -* **action** *object*: action to be executed when the rule is fired. Each action type has different field set as described in [Plain rules](plain_rules.md#actions) - * **type** ( *string* ): type of action. - * Other subfields, depending on the rule type. -* **VR** *object*: VisualRule object passed in by the Portal -* **nosignal** *object*: no signal condition for nosignal rules - * **checkInterval** *string*: time in _minutes_ for checking the attribute - * **attribute** *string*: attribute for watch - * **reportInterval** *string*: time in seconds to see an entity as silent - * **id** *string*: id of the entitiy to watch - * **idRegexp** *string*: regexp to match entities by id - * **type** *string*: type of entities to watch - -Only rules with EPL have a field *text* and only rules for no-signal detection has a field _nosignal_. + +- **\_ID** _ObjectId_: unique object ID used by mongoDB +- **name** _string_: name of the rule +- **service** _string_ : service which the rule belongs to. +- **subservice** _string_: subservice which the rule belongs to. +- **tex** _string_: EPL sentence for the rule, to be propagated to core +- **action** _object_: action to be executed when the rule is fired. Each action type has different field set as + described in [Plain rules](plain_rules.md#actions) + - **type** ( _string_ ): type of action. + - Other subfields, depending on the rule type. +- **VR** _object_: VisualRule object passed in by the Portal +- **nosignal** _object_: no signal condition for nosignal rules + - **checkInterval** _string_: time in _minutes_ for checking the attribute + - **attribute** _string_: attribute for watch + - **reportInterval** _string_: time in seconds to see an entity as silent + - **ID** _string_: ID of the entity to watch + - **idRegexp** _string_: regular expression to match entities by ID + - **type** _string_: type of entities to watch + +Only rules with EPL have a field _text_ and only rules for no-signal detection has a field _nosignal_. Example: + ```json { - "_id" : ObjectId("5530d83e38f4962d13479c47"), - "VR" : { - "name" : "ReglaId", - "active" : 1, - "cards" : [ - ], - "updateTime" : "2014-05-06T10:39:47.696Z", - "subservice" : "/", - "service" : "unknownt" - }, - "name" : "ReglaId", - "action" : { - "type" : "email", - "template" : "${device.asset.name} ${measure.value}", - "parameters" : { - "to" : "brox@tid.es", - "from" : "dca_support@tid.es", - "subject" : "calor" - } - }, - "subservice" : "/", - "service" : "unknownt", - "text" : "select * from pattern [every ev=iotEvent((cast(`id`?, String) regexp \"^value.*\"))]" + "_id": ObjectId("5530d83e38f4962d13479c47"), + "VR": { + "name": "ReglaId", + "active": 1, + "cards": [], + "updateTime": "2014-05-06T10:39:47.696Z", + "subservice": "/", + "service": "unknownt" + }, + "name": "ReglaId", + "action": { + "type": "email", + "template": "${device.asset.name} ${measure.value}", + "parameters": { + "to": "brox@tid.es", + "from": "dca_support@tid.es", + "subject": "calor" + } + }, + "subservice": "/", + "service": "unknownt", + "text": "select * from pattern [every ev=iotEvent((cast(`id`?, String) regexp \"^value.*\"))]" } - ``` + Example no-signal rule + ```json { - "_id" : ObjectId("5530d8dd38f4962d13479c48"), - "VR" : { - "name" : "VR_nonsignal_complex", - "active" : 1, - "cards" : [ - - ], - "updateTime" : "2014-03-26T14:29:40.561Z", - "subservice" : "/", - "service" : "unknownt" - }, - "name" : "VR_nonsignal_complex", - "action" : { - "type" : "sms", - "template" : "${device.asset.name}", - "parameters" : { - "to" : "123456789" - } - }, - "subservice" : "/", - "service" : "unknownt", - "nosignal" : { - "checkInterval" : "1", - "attribute" : "temperature", - "reportInterval" : "5", - "id" : null, - "idRegexp" : "^value.*", - "type" : null - } + "_id": ObjectId("5530d8dd38f4962d13479c48"), + "VR": { + "name": "VR_nonsignal_complex", + "active": 1, + "cards": [], + "updateTime": "2014-03-26T14:29:40.561Z", + "subservice": "/", + "service": "unknownt" + }, + "name": "VR_nonsignal_complex", + "action": { + "type": "sms", + "template": "${device.asset.name}", + "parameters": { + "to": "123456789" + } + }, + "subservice": "/", + "service": "unknownt", + "nosignal": { + "checkInterval": "1", + "attribute": "temperature", + "reportInterval": "5", + "id": null, + "idRegexp": "^value.*", + "type": null + } } ``` - ### Executions -The collection 'executions' stores information about the sucessfully executed actions. A TTL index deletes exectutions older than ine day + +The collection 'executions' stores information about the successfully executed actions. A TTL index deletes exectutions +older than ine day Fields: -* **_id** *ObjectId*: unique object id used by mongoDB -* **id** *string*: entity id that fired the rule -* **name** *string*: name fo the fired rule -* **notice** *string*: notice id that fired the rule -* **service** *string* : service which the fired rule belongs to. -* **subservice** *string*: subservice which the fired rule belongs to. -* **lastTime** *Date object*: timestamp of the execution + +- **\_ID** _ObjectId_: unique object ID used by mongoDB +- **ID** _string_: entity ID that fired the rule +- **name** _string_: name fo the fired rule +- **notice** _string_: notice ID that fired the rule +- **service** _string_ : service which the fired rule belongs to. +- **subservice** _string_: subservice which the fired rule belongs to. +- **lastTime** _Date object_: timestamp of the execution Example + ```json { - "_id" : ObjectId("552fc1997fa3e7b1d5e97ab2"), - "id" : "bloodm1", - "name" : "blood_rule_email", - "notice" : "9a140d10-e441-11e4-86f7-179be4a75b65", - "service" : "tenant2", - "subservice" : "/subservicio2", - "lastTime" : ISODate("2015-04-16T14:05:13.922Z") + "_id": ObjectId("552fc1997fa3e7b1d5e97ab2"), + "id": "bloodm1", + "name": "blood_rule_email", + "notice": "9a140d10-e441-11e4-86f7-179be4a75b65", + "service": "tenant2", + "subservice": "/subservicio2", + "lastTime": ISODate("2015-04-16T14:05:13.922Z") } ``` - - - + ## Indexes ### Rules + An index guarantees that every rule is identified by the tuple (name, service, subservice) -The index is created/ensured when perseo-fe starts, but it can be created from a mongoDB shell with +The index is created/ensured when perseo-fe starts, but it can be created from a mongoDB shell with + ``` db.rules.ensureIndex({name: 1, subservice: 1, service: 1}, {unique: true}) ``` ### Executions -A TTL index deletes documents with a life longer than one day -The index is created/ensured when perseo-fe starts, but it can be created from a mongoDB shell with +A TTL index deletes documents with a life longer than one day + +The index is created/ensured when perseo-fe starts, but it can be created from a mongoDB shell with + ``` db.executions.ensureIndex({lastTime: 1},{expireAfterSeconds: 86400 } ) ``` - diff --git a/documentation/pep_actions.md b/documentation/pep_actions.md index dbda0546..bae6ae3a 100644 --- a/documentation/pep_actions.md +++ b/documentation/pep_actions.md @@ -1,31 +1,36 @@ + ## Available actions The available actions are: -* **readRule**: to get working rules in CEP -* **writeRule**: to modify rules in CEP (create, delete, update) -* **notify**: to fire rules (if appropiate) with an event notification + +- **readRule**: to get working rules in CEP +- **writeRule**: to modify rules in CEP (create, delete, update) +- **notify**: to fire rules (if appropriate) with an event notification The following tables show the map from method and path of the request to the action. ### Notifications -| Method | Path | Action | -| ------ |:-----|:------------| -| POST | /notices | notify| + +| Method | Path | Action | +| ------ | :------- | :----- | +| POST | /notices | notify | ### Rules -| Method | Path | Action| -| ------ |:-------------|:-----------| + +| Method | Path | Action | +| ------ | :---------- | :-------- | | GET | /rules | readRule | | GET | /rules/{id} | readRule | | POST | /rules | writeRule | | DELETE | /rules/{id} | writeRule | ### Visual Rules -| Method | Path | Action | -| ------ |:--------|:------------| -| GET | /m2m/vrules | readRule | -| GET | /m2m/vrules/{id} | readRule | -| POST | /m2m/vrules | writeRule | -| DELETE | /m2m/vrules/{id} | writeRule | -| PUT | /m2m/vrules/{id} | writeRule | + +| Method | Path | Action | +| ------ | :--------------- | :-------- | +| GET | /m2m/vrules | readRule | +| GET | /m2m/vrules/{id} | readRule | +| POST | /m2m/vrules | writeRule | +| DELETE | /m2m/vrules/{id} | writeRule | +| PUT | /m2m/vrules/{id} | writeRule | diff --git a/documentation/plain_rules.md b/documentation/plain_rules.md index 853b1d18..7bb25cdd 100644 --- a/documentation/plain_rules.md +++ b/documentation/plain_rules.md @@ -1,69 +1,85 @@ # Plain rules -Plain rules allow a full customization of a rule with specific needs by means of setting the final EPL statement used by the Esper engine inside perseo-core. In order to work with perseo (front-end) properly, the EPL statement must fulfill several conventions for the rule to be able to operate on the incoming events and trigger adequate actions. +Plain rules allow a full customization of a rule with specific needs by means of setting the final EPL statement used by +the Esper engine inside perseo-core. In order to work with perseo (front-end) properly, the EPL statement must fulfill +several conventions for the rule to be able to operate on the incoming events and trigger adequate actions. The “anatomy” of a rule is as follows ```json { - "name":"blood_rule_update", - "text":"select *, *, ev.BloodPressure? as Pressure, ev.id? as Meter from pattern [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and type=\"BloodMeter\")]", - "action":{ - "type":"update", - "parameters":{ - "name":"abnormal", - "value":"true", - "type":"boolean" - } - } + "name": "blood_rule_update", + "text": "select *, *, ev.BloodPressure? as Pressure, ev.id? as Meter from pattern [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and type=\"BloodMeter\")]", + "action": { + "type": "update", + "parameters": { + "name": "abnormal", + "value": "true", + "type": "boolean" + } + } } ``` + The fields (all must be present) are -* **name**: name of the rule, used as identifier -* **text**: EPL statment for the rule engine in perseo-core -* **action**: action to be performed by perseo if the rule is fired from the core +- **name**: name of the rule, used as identifier +- **text**: EPL statement for the rule engine in perseo-core +- **action**: action to be performed by perseo if the rule is fired from the core -The rule name must consist of the ASCII characters from A to Z, from a to z, digits (0-9), underscore (_) and dash (-). It can have a maximum length of 50 characters. +The rule name must consist of the ASCII characters from A to Z, from a to z, digits (0-9), underscore (\_) and dash (-). +It can have a maximum length of 50 characters. -The field `action` can be also an array of "actions", objects with the same structure than the single action described in the rest of the documentation. Each of those actions will be executed when the rule is fired, avoiding to duplicate a rule only for getting several actions executed. For practical purposes, it is the same result that would be obtained with multiple rules with the same condition. +The field `action` can be also an array of "actions", objects with the same structure than the single action described +in the rest of the documentation. Each of those actions will be executed when the rule is fired, avoiding to duplicate a +rule only for getting several actions executed. For practical purposes, it is the same result that would be obtained +with multiple rules with the same condition. ## EPL text -The field ```text``` of the rule must be a valid EPL statement and additionally must honor several restrictions to match expectations of perseo and perseo-core. -EPL is documented in [Esper website](http://www.espertech.com/esper/esper-documentation), in particular [version 6.1.0](http://esper.espertech.com/release-6.1.0/esper-reference/html/index.html). +The field `text` of the rule must be a valid EPL statement and additionally must honor several restrictions to match +expectations of perseo and perseo-core. + +EPL is documented in [Esper site](http://www.espertech.com/esper/esper-documentation), in particular +[version 6.1.0](http://esper.espertech.com/release-6.1.0/esper-reference/html/index.html). A EPL statement to use with perseo could be: -``` +```sql select *, ev.BloodPressure? as Pressure, ev.id? as Meter from pattern [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and type="BloodMeter")] ``` -* The *from* pattern must name the event as **ev** and the event stream from which take events must be **iotEvent** -* A *type=* condition must be concatenated for avoiding mixing different kinds of entities -* The variable 'ruleName' in automatically added to the action, even if it is not present in the EPL text. The ruleName automatically added this way is retrieved as part of the EPL text when the rule is recovered using GET /rules or GET /rules/{name}. +- The _from_ pattern must name the event as **ev** and the event stream from which take events must be **iotEvent** +- A _type=_ condition must be concatenated for avoiding mixing different kinds of entities +- The variable 'ruleName' in automatically added to the action, even if it is not present in the EPL text. The + ruleName automatically added this way is retrieved as part of the EPL text when the rule is recovered using GET + /rules or GET /rules/{name}. -**Backward compatibility note:** since version 1.8.0 it is not mandatory to specify the name of the rule as part of the EPL text. In fact, it is not recommendable to do that. However, for backward compatibility, it can be present as *ruleName* alias (`e.g: select *, "blood_rule_update" as ruleName...`) in the select clause. If present, it must be equal to the ‘name’ field of the rule object. - -The used entity's attributes must be cast to `float` in case of being numeric (like in the example). Alphanumeric -values must be cast to `String`. Nested cast to string and to float is something we are analyzing, and could be -unnecessary in a future version. Use it by now. All the attributes in the notification from Orion are available in the -event object, **ev**, like *ev.BlodPressure?* and *ev.id?*. A question mark is *necessary* for EPL referring ‘dynamic’ -values. Metadata is also available as explained in [Metadata and object values](#metadata-and-object-values). +**Backward compatibility note:** since version 1.8.0 it is not mandatory to specify the name of the rule as part of the +EPL text. In fact, it is not recommendable to do that. However, for backward compatibility, it can be present as +_ruleName_ alias (`e.g: select *, "blood_rule_update" as ruleName...`) in the select clause. If present, it must be +equal to the ‘name’ field of the rule object. +The used entity's attributes must be cast to `float` in case of being numeric (like in the example). Alphanumeric values +must be cast to `String`. Nested cast to string and to float is something we are analyzing, and could be unnecessary in +a future version. Use it by now. All the attributes in the notification from Orion are available in the event object, +**ev**, like _ev.BlodPressure?_ and _ev.id?_. A question mark is _necessary_ for EPL referring ‘dynamic’ values. +Metadata is also available as explained in [Metadata and object values](#metadata-and-object-values). -## Actions -When a rule is fired, the "complex event" generated by the select clause is sent to perseo (front-end) which executes the action using the generated event as parameter to the action. +## Actions -There are a predefined set of actions: sending a SMS, sending a email, updating an attribute of the entity that fired the rule and making a HTTP POST to a provided URL. +When a rule is fired, the "complex event" generated by the select clause is sent to perseo (front-end) which executes +the action using the generated event as parameter to the action. +There are a predefined set of actions: sending a SMS, sending a email, updating an attribute of the entity that fired +the rule and making a HTTP POST to a provided URL. +The action must be provided in the `action` field of rule. An example: -The action must be provided in the ```action``` field of rule. An example: ```json "action":{ "type":"update", @@ -74,52 +90,51 @@ The action must be provided in the ```action``` field of rule. An example: }, "interval" : "30e3" } - ``` The `type` field is mandatory and must be one of -* `update`: update an entity's attribute -* `sms`: send a SMS -* `email`: send an email -* `post`: make an HTTP POST -* `twitter`: send a twitter - -An action can *optionally* have a field `interval` for limiting the frequency of the action execution (for the rule and -entity which fired it). The value is expressed in milliseconds and is the minimum period between executions. Once the action -is executed _successfully_, it won't be executed again until that period has elapsed. All the request from core to execute -it are silently discarded. This way, the rate of executions for an entity and rule can be limited. (Strictly, the minimum -time between executions) -### String substitution syntax +- `update`: update an entity's attribute +- `sms`: send a SMS +- `email`: send an email +- `post`: make an HTTP POST +- `twitter`: send a twitter -Some of the fields of an `action` (see detailed list below) can include a reference to one of the fields of the -notification/event. This allows include information as the received "pressure" value, the id of the device, etc. For -example, the actions `sms`, `email`, `post` include a field `template` used to build the body of message/request. This -text can include placeholders for attributes of the generated event. That placeholder has the form `${X}` where `X` -may be: +An action can _optionally_ have a field `interval` for limiting the frequency of the action execution (for the rule and +entity which fired it). The value is expressed in milliseconds and is the minimum period between executions. Once the +action is executed _successfully_, it won't be executed again until that period has elapsed. All the request from core +to execute it are silently discarded. This way, the rate of executions for an entity and rule can be limited. (Strictly, +the minimum time between executions) -* `id` for the id of the entity that triggers the rule. -* `type` for the type of the entity that triggers the rule -* Any other value is interpreted as the name of an attribute in the entity which triggers the rule and the placeholder -is substituted by the value of that attribute. +### String substitution syntax +Some of the fields of an `action` (see detailed list below) can include a reference to one of the fields of the +notification/event. This allows include information as the received "pressure" value, the ID of the device, etc. For +example, the actions `sms`, `email`, `post` include a field `template` used to build the body of message/request. This +text can include placeholders for attributes of the generated event. That placeholder has the form `${X}` where `X` may +be: +- `id` for the ID of the entity that triggers the rule. +- `type` for the type of the entity that triggers the rule +- Any other value is interpreted as the name of an attribute in the entity which triggers the rule and the placeholder + is substituted by the value of that attribute. -All alias for simple event attributes or "complex" calculated values can be directly used in the placeholder with their -name. And any of the original event attributes (with the special cases for `id` and `type` meaning entity ID and type, +All alias for simple event attributes or "complex" calculated values can be directly used in the placeholder with their +name. And any of the original event attributes (with the special cases for `id` and `type` meaning entity ID and type, respectively) can be referred too. -This substitution can be used in the the following fields: -* `template`, `from`, `to` and `subject` for `email` action -* `template`, `url`, `qs`, `headers`, `json` for `post` action -* `template` for `sms` action -* `template` for `twitter` action -* `id`, `type`, `name`, `value`, `ìsPattern` for `update` action +This substitution can be used in the following fields: +- `template`, `from`, `to` and `subject` for `email` action +- `template`, `url`, `qs`, `headers`, `json` for `post` action +- `template` for `sms` action +- `template` for `twitter` action +- `id`, `type`, `name`, `value`, `ìsPattern` for `update` action ### SMS action Sends a SMS to a number set as an action parameter with the body of the message built from the template + ```json "action": { "type": "sms", @@ -129,13 +144,16 @@ Sends a SMS to a number set as an action parameter with the body of the message } } ``` + The field `parameters` include a field `to` with the number to send the message to. The `template` and `to` fields perform [attribute substitution](#string-substitution-syntax). ### email action -Sends an email to the recipient set in the action parameters, with the body mail build from the `template` field. A field `to` in `parameters` sets the recipient and a field `from`sets the sender's email address. Also the subject of the email can be set in the field `subject` in `parameters`. +Sends an email to the recipient set in the action parameters, with the body mail build from the `template` field. A +field `to` in `parameters` sets the recipient and a field `from`sets the sender's email address. Also the subject of the +email can be set in the field `subject` in `parameters`. ```json "action": { @@ -152,21 +170,28 @@ Sends an email to the recipient set in the action parameters, with the body mail The `template`, `from`, `to` and `subject` fields perform [string substitution](#string-substitution-syntax). ### update attribute action -Updates one or more attributes of a given entity (in the Context Broker instance specified in the Perseo configuration). + +Updates one or more attributes of a given entity (in the Context Broker instance specified in the Perseo configuration). The `parameters` map includes the following fields: -* id: optional, the id of the entity which attribute is to be updated (by default the id of the entity that triggers the rule is used, i.e. `${id}`) -* type: optional, the type of the entity which attribute is to be updated (by default the type of the entity that triggers the rule is usedi.e. `${type}`) -* version: optional, The NGSI version for the update action. Set this attribute to `2` or `"2"` if you want to use NGSv2 format. `1` by default -* isPattern: optional, `false` by default. (Only for NGSIv1. If `version` is set to 2, this attribute will be ignored) -* attributes: *mandatory*, array of target attributes to update. Each element of the array must contain the fields - * **name**: *mandatory*, attribute name to set - * **value**: *mandatory*, attribute value to set - * type: optional, type of the attribute to set. By default, not set (in which case, only the attribute value is changed). -* actionType: optional, type of CB action: APPEND or UPDATE. By default is APPEND. -* trust: optional, trust token for getting an access token from Auth Server which can be used to get to a Context Broker behind a PEP. +- id: optional, the ID of the entity which attribute is to be updated (by default the ID of the entity that triggers + the rule is used, i.e. `${id}`) +- type: optional, the type of the entity which attribute is to be updated (by default the type of the entity that + triggers the rule is usedi.e. `${type}`) +- version: optional, The NGSI version for the update action. Set this attribute to `2` or `"2"` if you want to use + NGSv2 format. `1` by default +- isPattern: optional, `false` by default. (Only for NGSIv1. If `version` is set to 2, this attribute will be ignored) +- attributes: _mandatory_, array of target attributes to update. Each element of the array must contain the fields + - **name**: _mandatory_, attribute name to set + - **value**: _mandatory_, attribute value to set + - type: optional, type of the attribute to set. By default, not set (in which case, only the attribute value is + changed). +- actionType: optional, type of CB action: APPEND or UPDATE. By default is APPEND. +- trust: optional, trust token for getting an access token from Auth Server which can be used to get to a Context + Broker behind a PEP. NGSIv1 example: + ```json "action":{ "type":"update", @@ -183,15 +208,26 @@ NGSIv1 example: } } ``` -The `name` parameter cannot take `id` or `type` as a value, as that would refer to the entity's id and the entity's type (which are not updatable) and not to an attribute with any of those names. Trying to create such action will return an error. + +The `name` parameter cannot take `id` or `type` as a value, as that would refer to the entity's ID and the entity's type +(which are not updatable) and not to an attribute with any of those names. Trying to create such action will return an +error. The `id`, `type`, `name`, `value`, `ìsPattern` fields perform [string substitution](#string-substitution-syntax). -First time an update action using trust token is triggered, Perseo interacts with Keystone to get the temporal auth token corresponding to that trust token. This auth token is cached and used in every new update associated to the same action. Eventually, Perseo can receive a 401 Not Authorized due to auth token expiration. In that case, Perseo interacts again with Keystone to get a fresh auth token, then retries the update that causes the 401 (and the cache is refreshed with the new auth token for next updates). +First time an update action using trust token is triggered, Perseo interacts with Keystone to get the temporal auth +token corresponding to that trust token. This auth token is cached and used in every new update associated to the same +action. Eventually, Perseo can receive a 401 Not Authorized due to auth token expiration. In that case, Perseo interacts +again with Keystone to get a fresh auth token, then retries the update that causes the 401 (and the cache is refreshed +with the new auth token for next updates). -It could happen (in theory) that a just got auth token also produce a 401 Not authorized, however this would be an abnormal situation: Perseo logs the problem with the update but doesn't try to get a new one from Keystone. Next time Perseo triggers the action, the process may repeat, i.e. first update attemp fails with 401, Perseo requests a fresh auth token to Keystone, the second update attemp fails with 401, Perseo logs the problem and doesn't retry again. +It could happen (in theory) that a just got auth token also produce a 401 Not authorized, however this would be an +abnormal situation: Perseo logs the problem with the update but doesn't try to get a new one from Keystone. Next time +Perseo triggers the action, the process may repeat, i.e. first update attempt fails with 401, Perseo requests a fresh +auth token to Keystone, the second update attempt fails with 401, Perseo logs the problem and doesn't retry again. NGSIv2 example: + ```json "action":{ "type":"update", @@ -211,187 +247,218 @@ NGSIv2 example: **Note:** NGSIv2 update actions ignore the trust token for now. -When using NGSIv2 in the update actions, the value field perform [string substitution](#string-substitution-syntax). If `value` is a String, Perseo will parse the value taking into account the `type` field, this only applies to *`Number`*, *`Boolean`* and *`None`* types. +When using NGSIv2 in the update actions, the value field perform [string substitution](#string-substitution-syntax). If +`value` is a String, Perseo will parse the value taking into account the `type` field, this only applies to _`Number`_, +_`Boolean`_ and _`None`_ types. **Data Types for NGSIv2:** With `Number` type attributes, Perseo can be able to manage a int/float number or a String to parse in value field. -- Number from variable: + +- Number from variable: + ```json { - "name":"numberFromValue", + "name": "numberFromValue", "type": "Number", "value": "${NumberValue}" } ``` + If `NumberValue` value is for example `32.12`, this attribute will take `32.12` as value. -- Literal Number: +- Literal Number: + ```json { - "name":"numberLiteral", + "name": "numberLiteral", "type": "Number", "value": 12 } ``` + This attribute will take `12` as value. -- Number as String from variable: +- Number as String from variable: + ```json { - "name":"numberFromStringValue", + "name": "numberFromStringValue", "type": "Number", "value": "${NumberValueAsString}" } ``` + If `NumberValueAsString` value is for example `"4.67"`, this attribute will take `4.67` as value. -- Number as String: +- Number as String: + ```json { - "name":"numberStringLiteral", + "name": "numberStringLiteral", "type": "Number", "value": "67.8" } ``` -This attribute will take `67.8` as value. - +This attribute will take `67.8` as value. With `Text` type attributes, Perseo will put the value field parsed as string. -- Text as variable: +- Text as variable: + ```json { - "name":"textFromValue", + "name": "textFromValue", "type": "Text", "value": "${varValue}" } ``` + If `varValue` value is for example `"Good morning"`, this attribute will take `"Good morning"` as value. If `varValue` value is for example `1234`, this attribute will take `"1234"` as value. -- Literal Text: +- Literal Text: + ```json { - "name":"textLiteral", + "name": "textLiteral", "type": "Text", "value": "Hello world" } ``` + This attribute will take `"Hello world"` as value. -- Literal Number: +- Literal Number: + ```json { - "name":"textNumberLiteral", + "name": "textNumberLiteral", "type": "Text", "value": 67.8 } ``` + This attribute will take `"67.8"` as value. -- Literal Boolean: +- Literal Boolean: + ```json { - "name":"textBoolLiteral", + "name": "textBoolLiteral", "type": "Text", "value": true } ``` + This attribute will take `"true"` as value. With `DateTime` type attributes, Perseo will try to parse the value to DateTime format. Date as String: + ```json { - "name":"dateString", + "name": "dateString", "type": "DateTime", "value": "2018-12-05T11:31:39.00Z" } ``` + This attribute will take `"2018-12-05T11:31:39.000Z"` as value. Date as Number in milliseconds: + ```json { - "name":"dateString", + "name": "dateString", "type": "DateTime", "value": 1548843229832 } ``` + This attribute will take `"2019-01-30T10:13:49.832Z"` as value. Date from variable. + ```json { - "name":"dateString", + "name": "dateString", "type": "DateTime", "value": "${dateVar}" } ``` -If `dateVar` value is for example `1548843229832` (as Number or String), this attribute will take `"2019-01-30T10:13:49.832Z"` as value. -You can use the `__ts` field of a Perseo DateTime attribute to fill a DateTime attribute value without using any `cast()`. For example, if the var are defined as follow in the rule text, `ev.timestamp__ts? as dateVar`, `dateVar` will be a String with the Date in milliseconds, for example `"1548843060657"` and Perseo will parse this String with to a valid DateTime as `2019-01-30T10:11:00.657Z`. +If `dateVar` value is for example `1548843229832` (as Number or String), this attribute will take +`"2019-01-30T10:13:49.832Z"` as value. + +You can use the `__ts` field of a Perseo DateTime attribute to fill a DateTime attribute value without using any +`cast()`. For example, if the var are defined as follow in the rule text, `ev.timestamp__ts? as dateVar`, `dateVar` will +be a String with the Date in milliseconds, for example `"1548843060657"` and Perseo will parse this String with to a +valid DateTime as `2019-01-30T10:11:00.657Z`. With `None` type attributes, Perseo will set the value to `null` in all cases. None Attribute: + ```json { - "name":"nullAttribute", + "name": "nullAttribute", "type": "None", "value": "It does not matter what you put here" } ``` + This attribute will take `null` as value. ```json { - "name":"nullAttribute2", + "name": "nullAttribute2", "type": "None", "value": null } ``` -This attribute will take `null` as value. +This attribute will take `null` as value. **Complete example using NGSv2 update action in a rule:** ```json { - "name":"blood_rule_update", - "text":"select *,\"blood_rule_update\" as ruleName, *, ev.BloodPressure? as Pressure from pattern [every ev=iotEvent(BloodPressure? > 1.5 and type=\"BloodMeter\")]", - "action":{ - "type":"update", - "parameters":{ - "id":"${id}_example", - "version": 2, - "attributes": [ - { - "name":"pressure", - "type":"Number", - "value": "${Pressure}" - } - ] - } - } + "name": "blood_rule_update", + "text": "select *,\"blood_rule_update\" as ruleName, *, ev.BloodPressure? as Pressure from pattern [every ev=iotEvent(BloodPressure? > 1.5 and type=\"BloodMeter\")]", + "action": { + "type": "update", + "parameters": { + "id": "${id}_example", + "version": 2, + "attributes": [ + { + "name": "pressure", + "type": "Number", + "value": "${Pressure}" + } + ] + } + } } ``` Note that using NGSIv2 the BloodPressure attribute is a Number and therefore it is not necessary to use `cast()`. ### HTTP request action -Makes an HTTP request to an URL specified in `url` inside `parameters`, sending a body built from `template`. -The `parameters` field can specify -* method: *optional*, HTTP method to use, POST by default -* **url**: *mandatory*, URL target of the HTTP method -* headers: *optional*, an object with fields and values for the HTTP header -* qs: *optional*, an object with fields and values to build the query string of the URL -* json: *optional*, an object that will be sent as JSON. String substitution will be performed in the keys and -values of the object's fields. If present, it overrides `template` from `action` + +Makes an HTTP request to an URL specified in `url` inside `parameters`, sending a body built from `template`. The +`parameters` field can specify + +- method: _optional_, HTTP method to use, POST by default +- **URL**: _mandatory_, URL target of the HTTP method +- headers: _optional_, an object with fields and values for the HTTP header +- qs: _optional_, an object with fields and values to build the query string of the URL +- json: _optional_, an object that will be sent as JSON. String substitution will be performed in the keys and values + of the object's fields. If present, it overrides `template` from `action` ```json "action":{ @@ -429,6 +496,7 @@ Note that you can encode a JSON in the `template` field: } } ``` + or use the `json` parameter ```json @@ -452,12 +520,14 @@ or use the `json` parameter } ``` -The `template` and `url` fields and both the field names and the field values of `qs` and `headers` and `json` -perform [string substitution](#string-substitution-syntax). +The `template` and `url` fields and both the field names and the field values of `qs` and `headers` and `json` perform +[string substitution](#string-substitution-syntax). ### twitter action -Updates the status of a twitter account, with the text build from the `template` field. The field `parameters` must contain the values for the consumer key and secret and the access token key and access token secret of the pre-provisioned application associated to the twitter user. +Updates the status of a twitter account, with the text build from the `template` field. The field `parameters` must +contain the values for the consumer key and secret and the access token key and access token secret of the +pre-provisioned application associated to the twitter user. ```json "action": { @@ -476,14 +546,13 @@ The `template` field performs [string substitution](#string-substitution-syntax) ## Metadata and object values -Metadata values can be accessed by adding the suffix `__metadata__x` to the attribute name, being `x` the name of the -metadata attribute. This name can be used in the EPL text of the rule and in the parameters of the action which accept -string substitution. If the value of the metadata item is an object itself, nested fields can be referred by additional +Metadata values can be accessed by adding the suffix `__metadata__x` to the attribute name, being `x` the name of the +metadata attribute. This name can be used in the EPL text of the rule and in the parameters of the action which accept +string substitution. If the value of the metadata item is an object itself, nested fields can be referred by additional suffixes beginning with double underscore and the hierarchy can be walked down by adding more suffixes, like - `__metadata__x__subf1__subf12`. +`__metadata__x__subf1__subf12`. -For example: -The metadata in an event/notice like +For example: The metadata in an event/notice like ``` { @@ -540,77 +609,77 @@ could be used by a rule so } ``` -Generally, fields of attribute values which are objects themselves are accessible by adding to the name of the field a -double underscore prefix, so an attribute `x` with fields `a`, `b`, `c`, will allow these fields to be referred as +Generally, fields of attribute values which are objects themselves are accessible by adding to the name of the field a +double underscore prefix, so an attribute `x` with fields `a`, `b`, `c`, will allow these fields to be referred as `x__a`, `x__b` and `x__c`. -Note: be aware of the difference between the key `metadatas` used in the context broker notificacions (v1), ending in `s` - and the infix `metadata`, without the final `s`, used to access fields from EPL and actions. - +Note: be aware of the difference between the key `metadatas` used in the context broker notificacions (v1), ending in +`s` and the infix `metadata`, without the final `s`, used to access fields from EPL and actions. + ## Location fields -Fields with geolocation info with the formats recognized by NGSI v1, are parsed and generate two pairs of -pseudo-attributes, the first pair is for the latitude and the longitude and the second pair is for the x and y -UTMC coordinates for the point. These pseudo-attributes ease the use of the position in the EPL sentence of the rule. -These derived attributes have the same name of the attribute with a suffix of `__lat` and `__lon` , and `__x` and -`__y` respectively. +Fields with geolocation info with the formats recognized by NGSI v1, are parsed and generate two pairs of +pseudo-attributes, the first pair is for the latitude and the longitude and the second pair is for the x and y UTMC +coordinates for the point. These pseudo-attributes ease the use of the position in the EPL sentence of the rule. These +derived attributes have the same name of the attribute with a suffix of `__lat` and `__lon` , and `__x` and `__y` +respectively. -The formats are -* [NGSV1 deprecated format](https://forge.fiware.org/plugins/mediawiki/wiki/fiware/index.php/Publish/Subscribe_Broker_-_Orion_Context_Broker_-_User_and_Programmers_Guide_R3#Defining_location_attribute) -* [NGSIV1 current format](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/user/geolocation.md#defining-location-attribute) +The formats are +- [NGSV1 deprecated format](https://forge.fiware.org/plugins/mediawiki/wiki/fiware/index.php/Publish/Subscribe_Broker_-_Orion_Context_Broker_-_User_and_Programmers_Guide_R3#Defining_location_attribute) +- [NGSIV1 current format](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/user/geolocation.md#defining-location-attribute) So, a notification in the deprecated format like - - ```json - { - "subscriptionId":"57f73930e0e2c975a712b8fd", - "originator":"localhost", - "contextResponses":[ - { - "contextElement":{ - "type":"Vehicle", - "isPattern":"false", - "id":"Car1", - "attributes":[ - { - "name":"position", - "type":"coords", - "value":"40.418889, -3.691944", - "metadatas":[ - { - "name":"location", - "type":"string", - "value":"WGS84" - } - ] - } - ] - } - } + +```json +{ + "subscriptionId": "57f73930e0e2c975a712b8fd", + "originator": "localhost", + "contextResponses": [ + { + "contextElement": { + "type": "Vehicle", + "isPattern": "false", + "id": "Car1", + "attributes": [ + { + "name": "position", + "type": "coords", + "value": "40.418889, -3.691944", + "metadatas": [ + { + "name": "location", + "type": "string", + "value": "WGS84" + } + ] + } + ] + } + } ] - } - ``` - +} +``` + will propagate to the core, (and so making available to the EPL sentence) the fields `position__lat`, `position__lon` , `position__x`, `position__y` ```json -{ - "noticeId":"169b0920-8edb-11e6-838d-0b633312661c", - "id":"Car1", - "type":"Vehicle", - "isPattern":"false", - "subservice":"/", - "service":"unknownt", - "position":"40.418889, -3.691944", - "position__type":"coords", - "position__metadata__location":"WGS84", - "position__metadata__location__type":"string", - "position__lat":40.418889, - "position__lon":-3.691944, - "position__x":657577.4234800448, - "position__y":9591797.935076647 +{ + "noticeId": "169b0920-8edb-11e6-838d-0b633312661c", + "id": "Car1", + "type": "Vehicle", + "isPattern": "false", + "subservice": "/", + "service": "unknownt", + "position": "40.418889, -3.691944", + "position__type": "coords", + "position__metadata__location": "WGS84", + "position__metadata__location__type": "string", + "position__lat": 40.418889, + "position__lon": -3.691944, + "position__x": 657577.4234800448, + "position__y": 9591797.935076647 } ``` @@ -618,35 +687,35 @@ Analogously, a notification in "geopoint" format, like ```json { - "subscriptionId":"57f73930e0e2c975a712b8fd", - "originator":"localhost", - "contextResponses":[ - { - "contextElement":{ - "type":"Vehicle", - "isPattern":"false", - "id":"Car1", - "attributes":[ - { - "name":"position", - "type":"geo:point", - "value":"40.418889, -3.691944" - } - ] - }, - "statusCode":{ - "code":"200", - "reasonPhrase":"OK" - } - } - ] + "subscriptionId": "57f73930e0e2c975a712b8fd", + "originator": "localhost", + "contextResponses": [ + { + "contextElement": { + "type": "Vehicle", + "isPattern": "false", + "id": "Car1", + "attributes": [ + { + "name": "position", + "type": "geo:point", + "value": "40.418889, -3.691944" + } + ] + }, + "statusCode": { + "code": "200", + "reasonPhrase": "OK" + } + } + ] } ``` will send to core an event with the fields `position__lat`, `position__lon`, `position__x`, `position__y` also ```json -{ +{ "noticeId":"7b8f1c50-8eda-11e6-838d-0b633312661c", "id":"Car1", "type":"Vehicle", @@ -679,39 +748,40 @@ An example of rule taking advantage of these derived attributes could be: } ``` -that will send an email when the entity with attribute `position` is less than 5 km far away from Cuenca. It uses the -circle equation, `(x - a)^2 + (y - b)^2 = d^2`, being `(a, b)` 618618.8286057833 and 9764160.736945232 the UTMC coordinates -of Cuenca and `d` the distance of 5 000 m. +that will send an email when the entity with attribute `position` is less than 5 km far away from Cuenca. It uses the +circle equation, `(x - a)^2 + (y - b)^2 = d^2`, being `(a, b)` 618618.8286057833 and 9764160.736945232 the UTMC +coordinates of Cuenca and `d` the distance of 5 000 m. -Note: for long distances the precision of the computations and the distortion of the projection can introduce some degree -of inaccuracy. +Note: for long distances the precision of the computations and the distortion of the projection can introduce some +degree of inaccuracy. ## Time fields -Some attributes and metadata, supposed to contain a time in ISO8601 format, will generate a pseudo-attribute with the -same name as the attribute (or metadata field) and a suffix "__ts", with the parsed value as milliseconds for Unix epoch. -This value makes easier to write the EPL text which involves time comparisons. The fields (attribute or metadata) supposed -to convey time information are -* Fields named `TimeInstant` -* Fields of type `DateTime` -* Fields of type `urn:x-ogc:def:trs:IDAS:1.0:ISO8601` +Some attributes and metadata, supposed to contain a time in ISO8601 format, will generate a pseudo-attribute with the +same name as the attribute (or metadata field) and a suffix "\_\_ts", with the parsed value as milliseconds for Unix +epoch. This value makes easier to write the EPL text which involves time comparisons. The fields (attribute or metadata) +supposed to convey time information are + +- Fields named `TimeInstant` +- Fields of type `DateTime` +- Fields of type `urn:x-ogc:def:trs:IDAS:1.0:ISO8601` Additionally, some derived pseudo-attributes are included also -* `x__day`: the day of the month (1-31) for the specified date according to local time. -* `x__month`: the month (1-12) in the specified date according to local time. -* `x__year`: the year (4 digits) of the specified date according to local time. -* `x__hour`: the hour (0-23) in the specified date according to local time. -* `x__minute`: the minutes (0-59) in the specified date according to local time. -* `x__second`: the seconds (0-59) in the specified date according to local time. -* `x__millisecond`: the milliseconds (0-999) in the specified date according to local time. -* `x__dayUTC`: the day of the month (1-31) in the specified date according to universal time. -* `x__monthUTC`: the month (1-12) in the specified date according to universal time. -* `x__yearUTC`: the year (4 digits) of the specified date according to universal time. -* `x__hourUTC`: the hour (0-23) in the specified date according to universal time. -* `x__minuteUTC`: the minutes (0-59) in the specified date according to universal time. -* `x__secondUTC`: the seconds (0-59) in the specified date according to universal time. -* `x__millisecondUTC`: the milliseconds (0-999) in the specified date according to universal time. +- `x__day`: the day of the month (1-31) for the specified date according to local time. +- `x__month`: the month (1-12) in the specified date according to local time. +- `x__year`: the year (4 digits) of the specified date according to local time. +- `x__hour`: the hour (0-23) in the specified date according to local time. +- `x__minute`: the minutes (0-59) in the specified date according to local time. +- `x__second`: the seconds (0-59) in the specified date according to local time. +- `x__millisecond`: the milliseconds (0-999) in the specified date according to local time. +- `x__dayUTC`: the day of the month (1-31) in the specified date according to universal time. +- `x__monthUTC`: the month (1-12) in the specified date according to universal time. +- `x__yearUTC`: the year (4 digits) of the specified date according to universal time. +- `x__hourUTC`: the hour (0-23) in the specified date according to universal time. +- `x__minuteUTC`: the minutes (0-59) in the specified date according to universal time. +- `x__secondUTC`: the seconds (0-59) in the specified date according to universal time. +- `x__millisecondUTC`: the milliseconds (0-999) in the specified date according to universal time. So, an incoming notification like @@ -740,11 +810,13 @@ So, an incoming notification like { "name": "role", "value": "benevolent dictator for life", - "metadatas": [{ - "name": "when", - "value": "2014-04-29T13:18:05Z", - "type": "DateTime" - }] + "metadatas": [ + { + "name": "when", + "value": "2014-04-29T13:18:05Z", + "type": "DateTime" + } + ] } ], "type": "employee", @@ -761,83 +833,84 @@ So, an incoming notification like ``` will send to core the "event" + ```json -{ - "noticeId":"799635b0-914f-11e6-836b-bf1691c99768", - "noticeTS":1476368120971, - "id":"John Doe", - "type":"employee", - "isPattern":"false", - "subservice":"/", - "service":"unknownt", - "TimeInstant":"2014-04-29T13:18:05Z", - "TimeInstant__ts":1398777485000, - "TimeInstant__day":29, - "TimeInstant__month":4, - "TimeInstant__year":2014, - "TimeInstant__hour":15, - "TimeInstant__minute":18, - "TimeInstant__second":5, - "TimeInstant__millisecond":0, - "TimeInstant__dayUTC":29, - "TimeInstant__monthUTC":4, - "TimeInstant__yearUTC":2014, - "TimeInstant__hourUTC":13, - "TimeInstant__minuteUTC":18, - "TimeInstant__secondUTC":5, - "TimeInstant__millisecondUTC":0, - "birthdate":"2014-04-29T13:18:05Z", - "birthdate__type":"urn:x-ogc:def:trs:IDAS:1.0:ISO8601", - "birthdate__ts":1398777485000, - "birthdate__day":29, - "birthdate__month":4, - "birthdate__year":2014, - "birthdate__hour":15, - "birthdate__minute":18, - "birthdate__second":5, - "birthdate__millisecond":0, - "birthdate__dayUTC":29, - "birthdate__monthUTC":4, - "birthdate__yearUTC":2014, - "birthdate__hourUTC":13, - "birthdate__minuteUTC":18, - "birthdate__secondUTC":5, - "birthdate__millisecondUTC":0, - "hire":"2014-04-29T13:18:05Z", - "hire__type":"DateTime", - "hire__ts":1398777485000, - "hire__day":29, - "hire__month":4, - "hire__year":2014, - "hire__hour":15, - "hire__minute":18, - "hire__second":5, - "hire__millisecond":0, - "hire__dayUTC":29, - "hire__monthUTC":4, - "hire__yearUTC":2014, - "hire__hourUTC":13, - "hire__minuteUTC":18, - "hire__secondUTC":5, - "hire__millisecondUTC":0, - "role":"benevolent dictator for life", - "role__metadata__when":"2014-04-29T13:18:05Z", - "role__metadata__when__type":"DateTime", - "role__metadata__when__ts":1398777485000, - "role__metadata__when__day":29, - "role__metadata__when__month":4, - "role__metadata__when__year":2014, - "role__metadata__when__hour":15, - "role__metadata__when__minute":18, - "role__metadata__when__second":5, - "role__metadata__when__millisecond":0, - "role__metadata__when__dayUTC":29, - "role__metadata__when__monthUTC":4, - "role__metadata__when__yearUTC":2014, - "role__metadata__when__hourUTC":13, - "role__metadata__when__minuteUTC":18, - "role__metadata__when__secondUTC":5, - "role__metadata__when__millisecondUTC":0 +{ + "noticeId": "799635b0-914f-11e6-836b-bf1691c99768", + "noticeTS": 1476368120971, + "id": "John Doe", + "type": "employee", + "isPattern": "false", + "subservice": "/", + "service": "unknownt", + "TimeInstant": "2014-04-29T13:18:05Z", + "TimeInstant__ts": 1398777485000, + "TimeInstant__day": 29, + "TimeInstant__month": 4, + "TimeInstant__year": 2014, + "TimeInstant__hour": 15, + "TimeInstant__minute": 18, + "TimeInstant__second": 5, + "TimeInstant__millisecond": 0, + "TimeInstant__dayUTC": 29, + "TimeInstant__monthUTC": 4, + "TimeInstant__yearUTC": 2014, + "TimeInstant__hourUTC": 13, + "TimeInstant__minuteUTC": 18, + "TimeInstant__secondUTC": 5, + "TimeInstant__millisecondUTC": 0, + "birthdate": "2014-04-29T13:18:05Z", + "birthdate__type": "urn:x-ogc:def:trs:IDAS:1.0:ISO8601", + "birthdate__ts": 1398777485000, + "birthdate__day": 29, + "birthdate__month": 4, + "birthdate__year": 2014, + "birthdate__hour": 15, + "birthdate__minute": 18, + "birthdate__second": 5, + "birthdate__millisecond": 0, + "birthdate__dayUTC": 29, + "birthdate__monthUTC": 4, + "birthdate__yearUTC": 2014, + "birthdate__hourUTC": 13, + "birthdate__minuteUTC": 18, + "birthdate__secondUTC": 5, + "birthdate__millisecondUTC": 0, + "hire": "2014-04-29T13:18:05Z", + "hire__type": "DateTime", + "hire__ts": 1398777485000, + "hire__day": 29, + "hire__month": 4, + "hire__year": 2014, + "hire__hour": 15, + "hire__minute": 18, + "hire__second": 5, + "hire__millisecond": 0, + "hire__dayUTC": 29, + "hire__monthUTC": 4, + "hire__yearUTC": 2014, + "hire__hourUTC": 13, + "hire__minuteUTC": 18, + "hire__secondUTC": 5, + "hire__millisecondUTC": 0, + "role": "benevolent dictator for life", + "role__metadata__when": "2014-04-29T13:18:05Z", + "role__metadata__when__type": "DateTime", + "role__metadata__when__ts": 1398777485000, + "role__metadata__when__day": 29, + "role__metadata__when__month": 4, + "role__metadata__when__year": 2014, + "role__metadata__when__hour": 15, + "role__metadata__when__minute": 18, + "role__metadata__when__second": 5, + "role__metadata__when__millisecond": 0, + "role__metadata__when__dayUTC": 29, + "role__metadata__when__monthUTC": 4, + "role__metadata__when__yearUTC": 2014, + "role__metadata__when__hourUTC": 13, + "role__metadata__when__minuteUTC": 18, + "role__metadata__when__secondUTC": 5, + "role__metadata__when__millisecondUTC": 0 } ``` diff --git a/documentation/roadmap.md b/documentation/roadmap.md index 384dc395..884b3b73 100644 --- a/documentation/roadmap.md +++ b/documentation/roadmap.md @@ -1,26 +1,36 @@ # Perseo CEP Roadmap -This product is an Incubated FIWARE Generic Enabler. If you would like to learn about the overall Roadmap of FIWARE, please check section "Roadmap" on the FIWARE Catalogue. +This product is an Incubated FIWARE Generic Enabler. If you would like to learn about the overall Roadmap of FIWARE, +please check section "Roadmap" on the FIWARE Catalogue. ## Introduction -This section elaborates on proposed new features or tasks which are expected to be added to the product in the foreseeable future. There should be no assumption of a commitment to deliver these features on specific dates or in the order given. The development team will be doing their best to follow the proposed dates and priorities, but please bear in mind that plans to work on a given feature or task may be revised. All information is provided as a general guidelines only, and this section may be revised to provide newer information at any time. +This section elaborates on proposed new features or tasks which are expected to be added to the product in the +foreseeable future. There should be no assumption of a commitment to deliver these features on specific dates or in the +order given. The development team will be doing their best to follow the proposed dates and priorities, but please bear +in mind that plans to work on a given feature or task may be revised. All information is provided as a general +guidelines only, and this section may be revised to provide newer information at any time. Disclaimer: - This section has been last updated in January 2019. Please take into account its content could be obsolete. -- Note we develop this software in Agile way, so development plan is continuously under review. Thus, this roadmap has to be understood as rough plan of features to be done along time which is fully valid only at the time of writing it. This roadmap has not be understood as a commitment on features and/or dates. -- Some of the roadmap items may be implemented by external community developers, out of the scope of GEi owners. Thus, the moment in which these features will be finalized cannot be assured. +- Note we develop this software in Agile way, so development plan is continuously under review. Thus, this roadmap has + to be understood as rough plan of features to be done along time which is fully valid only at the time of writing + it. This roadmap has not be understood as a commitment on features and/or dates. +- Some of the roadmap items may be implemented by external community developers, out of the scope of GEi owners. Thus, + the moment in which these features will be finalized cannot be assured. ## Short term The following list of features are planned to be addressed in the short term, and incorporated in the next release: -- Initial NGSIv2 support imported from the Ficodes repo. +- Initial NGSIv2 support imported from the Ficodes repository. - Simplify rule creation by not requiring manually adding rule name inside the EPL code. -- Support for dealing with several Context Brokers from the same Perseo instance. To do so, update actions will support to configure target context broker, if not provided such configuration, the default context broker will be used. +- Support for dealing with several Context Brokers from the same Perseo instance. To do so, update actions will + support to configure target context broker, if not provided such configuration, the default context broker will be + used. - Improve template processing, extending it to StructuredValue/JSON attributes. @@ -28,19 +38,23 @@ The following list of features are planned to be addressed in the short term, an ## Medium term -The following list of features are planned to be addressed in the medium term, typically within the subsequent release(s) generated in the next **9 months** after next planned release: +The following list of features are planned to be addressed in the medium term, typically within the subsequent +release(s) generated in the next **9 months** after next planned release: - Include a new endpoint for manage the Context Broker subscriptions. -- Support more Context Broker actions (create, append, appendStrict, update, delete, replace, etc) and analyse other possible operations: batch operations, operations over subscriptions, operations over registrations. - +- Support more Context Broker actions (create, append, appendStrict, update, delete, replace, etc) and analyse other + possible operations: batch operations, operations over subscriptions, operations over registrations. ## Long term -The following list of features are proposals regarding the longer-term evolution of the product even though development of these features has not yet been scheduled for a release in the near future. Please feel free to contact us if you wish to get involved in the implementation or influence the roadmap +The following list of features are proposals regarding the longer-term evolution of the product even though development +of these features has not yet been scheduled for a release in the near future. Please feel free to contact us if you +wish to get involved in the implementation or influence the roadmap - Support for geospatial analysis (geo-fencing, point-based actions, movement-based actions) - Enhanced multi-tenancy support. Leverage different "Fiware-Service" and "Fiware-ServicePath" in rules -- Rule templates (i.e. having a rule template that can be instantiated to create particular instances of the rule). The idea is be able to create complex rules only providing some parameters. \ No newline at end of file +- Rule templates (i.e. having a rule template that can be instantiated to create particular instances of the rule). + The idea is be able to create complex rules only providing some parameters. From e0144ab3aafcac6e5f9bd33ae14dd2c6484ce8b5 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 4 Feb 2019 18:12:35 +0100 Subject: [PATCH 3/8] Add missing code highlights --- documentation/admin.md | 18 +++++++++--------- documentation/logs.md | 6 +++--- documentation/metrics_api.md | 12 ++++++------ documentation/models.md | 8 ++++---- documentation/plain_rules.md | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/documentation/admin.md b/documentation/admin.md index 39f4d362..96697d62 100644 --- a/documentation/admin.md +++ b/documentation/admin.md @@ -10,14 +10,14 @@ To start the service, use either the service command: service perseo start Or just the launch script: -``` +```bash /etc/init.d/perseo start ``` For testing purposes it might be interesting to launch the process directly without the service. That can be done executing the following command from the project root directory: -``` +```bash ./bin/perseo ``` @@ -28,13 +28,13 @@ the default configuration (in /opt/perseo/config.js) is used. To stop the service, use either the service command: -``` +```bash service perseo stop ``` Or just the launch script: -``` +```bash /etc/init.d/perseo stop ``` @@ -44,13 +44,13 @@ Or just the launch script: The status of the process can be retrieved using the service command: -``` +```bash service perseo status ``` It also can be checked with ps, using a filter with the command name: -``` +```bash ps -ef | grep "bin/perseo" ``` @@ -60,14 +60,14 @@ In both cases a result of 0 (echoing $?) indicates the process is supposed to be The following command: -``` +```bash netstat -ntpl | grep 9090 ``` can be used to check the process is listening in the appropriate port (provided the port is the standard 9090). The result should resemble this line: -``` +```text tcp 0 0 0.0.0.0:1026 0.0.0.0:* LISTEN 12179/node ``` @@ -112,7 +112,7 @@ The different configuration parameters introduced above are described also in th - 'service' is the service associated to the subscription - 'subservice' is the subservice associated to the subscription -``` +```bash (curl http://orion-machine:1026/v1/subscribeContext -s -S --header 'Content-Type: application/json' --header 'Accept: application/json' --header 'Fiware-Service: service' –header 'Fiware-ServicePath: subservice' -d @- | python -mjson.tool) <:/admin/log?level= ``` The log level can be retrieved at run-time, with an HTTP GET request -``` +```bash curl --request GET :/admin/log ``` diff --git a/documentation/metrics_api.md b/documentation/metrics_api.md index 5ee559f7..b5beda00 100644 --- a/documentation/metrics_api.md +++ b/documentation/metrics_api.md @@ -17,7 +17,7 @@ Perseo implements a REST-based API that can be used to get relevant operational #### Get metrics -``` +```text GET /admin/metrics ``` @@ -29,7 +29,7 @@ At the first level there are two keys: **services** and **sum**. In sequence, ** are service names and whose values are objects with information about the corresponding service. The **sum** value is an object with information for the aggregated information for all services. -``` +```json { "services": { "service1": , @@ -46,7 +46,7 @@ an object whose keys are subservice names and whose values are objects with info subservice. The **sum** value is an object with information for the aggregated information for all subservices in the given services. -``` +```json { "subservs": { "subservice1": , @@ -63,7 +63,7 @@ the `Fiware-ServicePath` header) `/gardens` then the key used for it would be `g Regarding subservice information object, keys are the name of the different metrics. -``` +```json { "metric1": , "metric2": , @@ -83,7 +83,7 @@ Some additional remarks: ### Reset metrics -``` +```text DELETE /admin/metrics ``` @@ -93,7 +93,7 @@ This operation resets all metrics, as if Perseo would had just been started. ### Get and reset -``` +```text GET /admin/metrics?reset=true ``` diff --git a/documentation/models.md b/documentation/models.md index 5424aa79..c8839901 100644 --- a/documentation/models.md +++ b/documentation/models.md @@ -131,8 +131,8 @@ An index guarantees that every rule is identified by the tuple (name, service, s The index is created/ensured when perseo-fe starts, but it can be created from a mongoDB shell with -``` -db.rules.ensureIndex({name: 1, subservice: 1, service: 1}, {unique: true}) +```javascript +db.rules.ensureIndex({ name: 1, subservice: 1, service: 1 }, { unique: true }); ``` ### Executions @@ -141,6 +141,6 @@ A TTL index deletes documents with a life longer than one day The index is created/ensured when perseo-fe starts, but it can be created from a mongoDB shell with -``` -db.executions.ensureIndex({lastTime: 1},{expireAfterSeconds: 86400 } ) +```javascript +db.executions.ensureIndex({ lastTime: 1 }, { expireAfterSeconds: 86400 }); ``` diff --git a/documentation/plain_rules.md b/documentation/plain_rules.md index 7bb25cdd..5759eb45 100644 --- a/documentation/plain_rules.md +++ b/documentation/plain_rules.md @@ -554,7 +554,7 @@ suffixes beginning with double underscore and the hierarchy can be walked down b For example: The metadata in an event/notice like -``` +```json { "subscriptionId" : "51c04a21d714fb3b37d7d5a7", "originator" : "localhost", From e92eb90c5f1ef4ade0dcb331fcc031985af6f65d Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Thu, 7 Feb 2019 21:37:23 +0100 Subject: [PATCH 4/8] Random drive-by formatting fixes * Missing and inconsistent markup --- documentation/admin.md | 6 +++++- documentation/configuration.md | 2 +- documentation/deployment.md | 9 +++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/documentation/admin.md b/documentation/admin.md index 96697d62..1c7afef2 100644 --- a/documentation/admin.md +++ b/documentation/admin.md @@ -6,7 +6,11 @@ #### Start service -To start the service, use either the service command: service perseo start +To start the service, use either the service command: + +```bash +service perseo start +``` Or just the launch script: diff --git a/documentation/configuration.md b/documentation/configuration.md index 7d044753..88c59f15 100644 --- a/documentation/configuration.md +++ b/documentation/configuration.md @@ -19,7 +19,7 @@ The following table shows the environment variables available for Perseo configu | PERSEO_MONGO_ENDPOINT | Endpoint (`host[:port]`) list for Mongo DB. | | PERSEO_MONGO_REPLICASET | ReplicaSet name for Mongo DB. | | PERSEO_MONGO_USER | User for Mongo DB. | -| PERSEO_MONGO_PASSWORD | Password for Mongo DB. g | +| PERSEO_MONGO_PASSWORD | Password for Mongo DB. | | PERSEO_CORE_URL | Full URL where Perseo Core is listening (e.g: `http://63.34.124.1:8080`). | | PERSEO_NEXT_URL | Full URL where Perseo Core replicated node is listening. Same format as above. | | PERSEO_ORION_URL | Full URL of the Orion Context Broker (e.g: `http://64.124.28.15:1026`). | diff --git a/documentation/deployment.md b/documentation/deployment.md index 4417e9ab..6bd3905b 100644 --- a/documentation/deployment.md +++ b/documentation/deployment.md @@ -66,8 +66,9 @@ and will be listening on port `8080`): docker run -d --name perseo_core -h perseocore -p 8080:8080 telefonicaiot/perseo-core:master -perseo_fe_url :9090 ``` -where must be the hostname or IP address of the \_machine hosting the Perseo FE Container*. Please note -that it is a good idea to expose port `8080` to the host so that it can be verified that Perseo Core is up and running. +where `` must be the hostname or IP address of the _machine hosting the Perseo FE Container_. Please +note that it is a good idea to expose port `8080` to the host so that it can be verified that Perseo Core is up and +running. Then a container running Perseo frontend has to be instantiated and run: @@ -171,10 +172,10 @@ In order to undeploy the proxy just kill the process and remove the directory. ### Log Rotation -Independently of how the service is installed, the log files will need an external rotation (e.g.: the logrotate +Independently of how the service is installed, the log files will need an external rotation (e.g.: the `logrotate` command) to avoid disk full problems. -Logrotate is installed as RPM dependency along with perseo. The system is configured to rotate every day and whenever +`logrotate` is installed as RPM dependency along with perseo. The system is configured to rotate every day and whenever the log file size is greater than 100MB (checked very 30 minutes by default): - For daily rotation: `/etc/logrotate.d/logrotate-perseo-daily` : which enables daily log rotation From b4923d7dbf4b7d63bcca2e13c7ecc4e16cc786dd Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Thu, 7 Feb 2019 21:37:52 +0100 Subject: [PATCH 5/8] Add missing commands --- documentation/development.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/documentation/development.md b/documentation/development.md index 93174481..eb3464d7 100644 --- a/documentation/development.md +++ b/documentation/development.md @@ -30,12 +30,31 @@ npm test jshint -Uses provided .jshintrc flag file. To check source code style, type +Uses the provided `.jshintrc` flag file. To check source code style, type ```bash npm run lint ``` +### Documentation guidelines + +remark + +Uses the provided `.remarkrc.js` flag file. To check consistency of the Markdown markup, type + +```bash +npm run lint:md +``` + +textlint + +Uses the provided `.textlintrc` flag file. To check for spelling and grammar errors, dead links and keyword consistency, +type + +```bash +npm run lint:text +``` + ### Continuous testing Support for continuous testing by modifying a src file or a test. For continuous testing, type @@ -72,3 +91,13 @@ restored. # Use git-bash on Windows npm run clean ``` + +### Prettify Code + +Runs the [prettier](https://prettier.io) code formatter to ensure consistent code style (whitespacing, parameter +placement and breakup of long lines etc.) within the codebase. + +```bash +# Use git-bash on Windows +npm run prettier +``` From 266b2787bf07ec6dfc3d9f0b3a8ac14ced531d4c Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Thu, 7 Feb 2019 21:41:24 +0100 Subject: [PATCH 6/8] Replace ^ with ~ in dependencies --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 2cfe685a..e9159d4f 100644 --- a/package.json +++ b/package.json @@ -42,18 +42,18 @@ "chai": "~4.1.2", "proxyquire": "0.5.1", "prettier": "~1.14.2", - "remark-cli": "^6.0.1", - "remark-preset-lint-recommended": "^3.0.2", + "remark-cli": "~6.0.1", + "remark-preset-lint-recommended": "~3.0.2", "rewire": "~4.0.1", "should": "8.2.2", "watch": "~1.0.2", "sinon": "~6.1.0", "sinon-chai": "~3.2.0", - "textlint": "^11.0.1", - "textlint-rule-common-misspellings": "^1.0.1", - "textlint-rule-no-dead-link": "^4.4.1", - "textlint-rule-terminology": "^1.1.30", - "textlint-rule-write-good": "^1.6.2" + "textlint": "~11.0.1", + "textlint-rule-common-misspellings": "~1.0.1", + "textlint-rule-no-dead-link": "~4.4.1", + "textlint-rule-terminology": "~1.1.30", + "textlint-rule-write-good": "~1.6.2" }, "keywords": [], "dependencies": { From fe0ba3ca6240cba7a255fd7d6d5086adab19c15d Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Thu, 7 Feb 2019 21:56:48 +0100 Subject: [PATCH 7/8] Add command --- documentation/development.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/documentation/development.md b/documentation/development.md index eb3464d7..69fed1c7 100644 --- a/documentation/development.md +++ b/documentation/development.md @@ -101,3 +101,10 @@ placement and breakup of long lines etc.) within the codebase. # Use git-bash on Windows npm run prettier ``` + +To ensure consistent markdown formatting run the following: + +```bash +# Use git-bash on Windows +npm run prettier:text +``` From 76f4d2ce8bb3683ba0cde1735abf8432f7885d73 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Fri, 8 Feb 2019 07:42:09 +0100 Subject: [PATCH 8/8] Capitalize Markdown --- documentation/development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/development.md b/documentation/development.md index 69fed1c7..a276c816 100644 --- a/documentation/development.md +++ b/documentation/development.md @@ -102,7 +102,7 @@ placement and breakup of long lines etc.) within the codebase. npm run prettier ``` -To ensure consistent markdown formatting run the following: +To ensure consistent Markdown formatting run the following: ```bash # Use git-bash on Windows