1
0
Fork 0

Merge branch 'devel' of https://github.com/triAGENS/ArangoDB into aql2

Conflicts:
	arangod/V8Server/v8-vocbase.cpp
This commit is contained in:
Jan Steemann 2014-08-15 18:47:18 +02:00
commit f8cf5dc26d
35 changed files with 1069 additions and 117 deletions

View File

@ -32,11 +32,11 @@ Subqueries may also include other subqueries themselves.
!SUBSECTION Variable expansion
In order to access a named attribute from all elements in a list easily, AQL
offers the shortcut operator *[\*]* for variable expansion.
offers the shortcut operator [*] for variable expansion.
Using the *[\*]* operator with a variable will iterate over all elements in the
Using the [*] operator with a variable will iterate over all elements in the
variable thus allowing to access a particular attribute of each element. It is
required that the expanded variable is a list. The result of the *[\*]*
required that the expanded variable is a list. The result of the [*]
operator is again a list.
FOR u IN users

View File

@ -0,0 +1,274 @@
!CHAPTER Foxx Job Queues
Foxx allows defining job queues that let you perform slow or expensive actions asynchronously. These queues can be used to send e-mails, call external APIs or perform other actions that you do not want to perform directly or want to retry on failure.
For the low-level functionality see the section *Task Management* in the chapter *JavaScript Modules*.
*Examples*
The following Foxx route handler will enqueue a job whenever the *"/log"* route is accessed that prints "Hello World!" to the server log.
```js
var Foxx = require("org/arangodb/foxx");
var ctrl = new Foxx.Controller(applicationContext);
var queue = Foxx.queues.create("my-queue");
Foxx.queues.registerJobType("log", function (data) {
print(data);
});
ctrl.get("/log", function () {
queue.push("log", "Hello World!");
});
```
!SECTION Creating or updating a queue
Creates a queue with the given name and maximum number of workers.
`Foxx.queues.create(name, [maxWorkers])`
Returns the *Queue* instance for the given *name*. If the queue does not exist, a new queue with the given *name* will be created. If a queue with the given *name* already exists and *maxWorkers* is set, the queue's maximum number of workers will be updated.
*Parameter*
* *name*: the name of the queue to create.
* *maxWorkers* (optional): the maximum number of workers. Default: *1*.
*Examples*
```js
// Create a queue with the default number of workers (i.e. one)
var queue1 = Foxx.queues.create("my-queue");
// Create a queue with a given number of workers
var queue2 = Foxx.queues.create("another-queue", 2);
// Update the number of workers of an existing queue
var queue3 = Foxx.queues.create("my-queue", 10);
// queue1 and queue3 refer to the same queue
assertEqual(queue1, queue3);
```
!SECTION Fetching an existing queue
Fetches a queue with the given name.
`Foxx.queues.get(name)`
Returns the *Queue* instance for the given *name*. If the queue does not exist, an exception is thrown instead.
*Parameter*
* *name*: the name of the queue to fetch.
*Examples*
If the queue does not yet exist, an exception is thrown:
```js
Foxx.queues.get("some-queue");
// Error: Queue does not exist: some-queue
// at ...
```
Otherwise, the *Queue* instance will be returned:
```js
var queue1 = Foxx.queues.create("some-queue");
var queue2 = Foxx.queues.get("some-queue");
assertEqual(queue1, queue2);
```
!SECTION Deleting a queue
Deletes the queue with the given name from the database.
`Foxx.queues.delete(name)`
Returns *true* if the queue was deleted successfully. If the queue did not exist, it returns *false* instead.
When a queue is deleted, jobs on that queue will no longer be executed.
Deleting a queue will not delete any jobs on that queue.
*Parameter*
* *name*: the name of the queue to delete.
*Examples*
```js
var queue = Foxx.queues.create("my-queue");
Foxx.queues.delete("my-queue"); // true
Foxx.queues.delete("my-queue"); // false
```
!SECTION Registering a job type
Registers a job type with the queue manager.
`Foxx.queues.registerJobType(name, opts)`
If *opts* is a function, it will be treated as the *execute* function.
*Parameter*
* *name*: the name of the job type to register.
* *opts*: an object with the following properties:
* *execute*: a function to pass the job data to when a job is executed.
* *maxFailures* (optional): the number of times a job will be re-tried before it is marked as *"failed"*. A negative value or *Infinity* means that the job will be re-tried on failure indefinitely. Default: *0*.
* *schema* (optional): a [Joi](https://github.com/hapijs/joi) schema to validate a job's data against before accepting it.
* *backOff* (optional): either a function that takes the number of times the job has failed before as input and returns the number of milliseconds to wait before trying the job again, or the delay to be used to calculate an [exponential back-off](https://en.wikipedia.org/wiki/Exponential_backoff), or *0* for no delay. Default: *1000*.
*Examples*
```js
var Foxx = require("org/arangodb/foxx");
Foxx.queues.registerJobType("log", function (data) {
print(data);
});
```
!SECTION Adding a job to a queue
Adds a job of the given type to the given queue.
`Queue::push(name, data, [opts])`
Returns the job id.
*Parameter*
* *name*: the name of the job's job type.
* *data*: the job data of the job; must be serializable to JSON.
* *opts* (optional): an object with any of the following properties:
* *success* (optional): a function to be called after the job has been completed successfully.
* *failure* (optional): a function to be called after the job has failed too many times.
* *delayUntil* (optional): a timestamp in milliseconds until which the execution of the job should be delayed. Default: *Date.now()*.
* *backOff* (optional): either a function that takes the number of times the job has failed before as input and returns the number of milliseconds to wait before trying the job again, or the delay to be used to calculate an [exponential back-off](https://en.wikipedia.org/wiki/Exponential_backoff), or *0* for no delay. See [Registering a job type](#registering-a-job-type).
* *allowUnknown* (optional): whether the job should be queued even if the job type name does not match a currently registered job type. Default: *false*.
* *maxFailures* (optional): the number of times the job will be re-tried before it is marked as *"failed"*. A negative value or *Infinity* means that the job will be re-tried on failure indefinitely. See [Registering a job type](#registering-a-job-type).
Note that if you pass a function for the *backOff* calculation, *success* callback or *failure* callback options the function must not rely on any external scope or external variables.
*Examples*
Basic usage example:
```js
var Foxx = require("org/arangodb/foxx");
var queue = Foxx.queues.create("my-queue");
queue.push("log", "Hello World!");
```
This will **not** work, because *console* was defined outside the callback function:
```js
var Foxx = require("org/arangodb/foxx");
var queue = Foxx.queues.create("my-queue");
var console = require("console"); // outside the callback's function scope
queue.push("log", "Hello World!", {
success: function () {
console.log("Yay!"); // throws "console is not defined"
}
});
```
!SECTION Fetching a job from the queue
Creates a proxy object representing a job with the given job id.
`Queue::get(jobId)`
Returns the *Job* instance for the given *jobId*. Properties of the job object will be fetched whenever they are referenced and can not be modified.
*Parameter*
* *jobId*: the id of the job to create a proxy object for.
*Examples*
```js
var jobId = queue.push("log", "Hello World!");
var job = queue.get(jobId);
assertEqual(job.id, jobId);
```
!SECTION Deleting a job from the queue
Deletes a job with the given job id.
`Queue::delete(jobId)`
Returns *true* if the job was deleted successfully. If the job did not exist, it returns *false* instead.
!SECTION Fetching a list of jobs in a queue
*Examples*
```js
queue.push("log", "Hello World!", {delayUntil: Date.now() + 50});
assertEqual(queue.pending("log").length, 1);
// 50 ms later...
assertEqual(queue.pending("log").length, 0);
assertEqual(queue.progress("log").length, 1);
// even later...
assertEqual(queue.progress("log").length, 0);
assertEqual(queue.complete("log").length, 1);
```
!SUBSECTION Fetching a list of pending jobs in a queue
`Queue::pending([type])`
Returns an array of job ids of jobs in the given queue with the status *"pending"*, optionally filtered by the given job type.
*Parameter*
* *type* (optional): the name of the job type to filter the results.
!SUBSECTION Fetching a list of jobs that are currently in progress
`Queue::progress([type])`
Returns an array of job ids of jobs in the given queue with the status *"progress"*, optionally filtered by the given job type.
*Parameter*
* *type* (optional): the name of the job type to filter the results.
!SUBSECTION Fetching a list of completed jobs in a queue
`Queue::complete([type])`
Returns an array of job ids of jobs in the given queue with the status *"complete"*, optionally filtered by the given job type.
*Parameter*
* *type* (optional): the name of the job type to filter the results.
!SUBSECTION Fetching a list of failed jobs in a queue
`Queue::failed([type])`
Returns an array of job ids of jobs in the given queue with the status *"failed"*, optionally filtered by the given job type.
*Parameter*
* *type* (optional): the name of the job type to filter the results.
!SUBSECTION Fetching a list of all jobs in a queue
`Queue::all([type])`
Returns an array of job ids of all jobs in the given queue, optionally filtered by the given job type.
*Parameter*
* *type* (optional): the name of the job type to filter the results.
!SECTION Aborting a job
Aborts a non-completed job.
`Job::abort()`
Sets a job's status to *"failed"* if it is not already *"complete"*, without calling the job's *onFailure* callback.

View File

@ -95,6 +95,7 @@
* [Developing Applications](Foxx/DevelopingAnApplication.md)
* [Dependency Injection](Foxx/FoxxInjection.md)
* [Foxx Exports](Foxx/FoxxExports.md)
* [Foxx Job Queues](Foxx/FoxxQueues.md)
* [Optional Functionality](Foxx/FoxxOptional.md)
<!-- 16 -->
* [Foxx Manager](FoxxManager/README.md)

View File

@ -16,7 +16,7 @@ describe ArangoDB do
doc.code.should eq(200)
compatibility = doc.parsed_response['compatibility']
compatibility.should be_kind_of(Integer)
compatibility.should eq(20200)
compatibility.should eq(20300)
end
it "tests the compatibility value when a broken header is set" do
@ -28,7 +28,7 @@ describe ArangoDB do
doc.code.should eq(200)
compatibility = doc.parsed_response['compatibility']
compatibility.should be_kind_of(Integer)
compatibility.should eq(20200)
compatibility.should eq(20300)
end
end
@ -124,12 +124,12 @@ describe ArangoDB do
end
it "tests the compatibility value when a too high version is set" do
doc = ArangoDB.get("/_admin/echo", :headers => { "x-arango-version" => "2.3" })
doc = ArangoDB.get("/_admin/echo", :headers => { "x-arango-version" => "2.4" })
doc.code.should eq(200)
compatibility = doc.parsed_response['compatibility']
compatibility.should be_kind_of(Integer)
compatibility.should eq(20300)
compatibility.should eq(20400)
end
end

View File

@ -204,7 +204,7 @@ describe ArangoDB do
doc.headers["x-arango-replication-lastincluded"].should_not eq("0")
doc.headers["content-type"].should eq("application/x-arango-dump; charset=utf-8")
body = doc.response.body
body = doc.response.body.split("\n")[0]
document = JSON.parse(body)
document.should have_key("tick")
@ -230,7 +230,7 @@ describe ArangoDB do
c["waitForSync"].should eq(true)
end
it "fetches some collection operations the follow log" do
it "fetches some collection operations from the follow log" do
ArangoDB.drop_collection("UnitTestsReplication")
sleep 1
@ -282,28 +282,30 @@ describe ArangoDB do
document = JSON.parse(part)
if i == 0
# create collection
document.should have_key("tick")
document.should have_key("type")
document.should have_key("cid")
document.should have_key("collection")
if document["type"] == 2000 and document["cid"] == cid
# create collection
document.should have_key("tick")
document.should have_key("type")
document.should have_key("cid")
document.should have_key("collection")
document["tick"].should match(/^\d+$/)
document["tick"].to_i.should >= fromTick.to_i
document["type"].should eq(2000)
document["cid"].should eq(cid)
document["tick"].should match(/^\d+$/)
document["tick"].to_i.should >= fromTick.to_i
document["type"].should eq(2000)
document["cid"].should eq(cid)
c = document["collection"]
c.should have_key("version")
c["type"].should eq(2)
c["cid"].should eq(cid)
c["deleted"].should eq(false)
c["doCompact"].should eq(true)
c.should have_key("maximalSize")
c["maximalSize"].should be_kind_of(Integer)
c["name"].should eq("UnitTestsReplication")
c["isVolatile"].should eq(false)
c["waitForSync"].should eq(true)
c = document["collection"]
c.should have_key("version")
c["type"].should eq(2)
c["cid"].should eq(cid)
c["deleted"].should eq(false)
c["doCompact"].should eq(true)
c.should have_key("maximalSize")
c["maximalSize"].should be_kind_of(Integer)
c["name"].should eq("UnitTestsReplication")
c["isVolatile"].should eq(false)
c["waitForSync"].should eq(true)
end
elsif i == 1
# create document

View File

@ -4238,9 +4238,10 @@ static v8::Handle<v8::Value> JS_DatafileScanVocbaseCol (v8::Arguments const& arg
return scope.Close(result);
}
// .............................................................................
// generate the TRI_vocbase_col_t template
// .............................................................................
// .............................................................................
// generate the TRI_vocbase_col_t template
// .............................................................................
void TRI_InitV8collection (v8::Handle<v8::Context> context,
TRI_server_t* server,
TRI_vocbase_t* vocbase,

View File

@ -1285,7 +1285,7 @@ static v8::Handle<v8::Value> CreateCollectionCoordinator (
/// ],
/// "isNewlyCreated" : true
/// }
/// ```js
/// ```
///
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

View File

@ -1 +1 @@
#define TRI_VERSION "2.2.0-devel"
#define TRI_VERSION "2.3.0-devel"

View File

@ -6,7 +6,7 @@ dnl ============================================================================
dnl --SECTION-- triAGENS GmbH Build Environment
dnl ============================================================================
AC_INIT([triAGENS ArangoDB], [2.2.0-devel], [info@triagens.de], [arangodb], [http://www.arangodb.org])
AC_INIT([triAGENS ArangoDB], [2.3.0-devel], [info@triagens.de], [arangodb], [http://www.arangodb.org])
dnl ----------------------------------------------------------------------------
dnl auxillary directory for install-sh and missing

View File

@ -323,30 +323,4 @@
</div>
</div>
<!-- Create Edge Modal -->
<div id="edgeCreateModal" style="display:none" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Create Edge</h3>
</div>
<div class="modal-body" id="createEdge">
<p>Please complete your task:</p>
<table>
<tr>
<th class="collectionTh">_from:</th>
<th><input type="text" id="new-document-from" name="from" value=""/></th>
<th><a class="modalTooltips" title="Document _id: document handle of the linked vertex (incoming relation)"><i class="icon-info-sign"></i></a></th>
</tr>
<tr>
<th class="collectionTh">_to:</th>
<th><input type="text" id="new-document-to" name="to" value=""/></th>
<th><a class="modalTooltips" title="Document _id: document handle of the linked vertex (outgoing relation)"><i class="icon-info-sign"></i></a></th>
</tr>
</table>
</div>
<div class="modal-footer">
<button class="button-close" data-dismiss="modal" aria-hidden="true">Close</button>
<button id="confirmCreateEdge" class="button-success" style="float:right">Create</button>
</div>
</div>
</script>

View File

@ -118,4 +118,4 @@
<%
}
%>
</script>
</script>

View File

@ -31,16 +31,16 @@ window.ApplicationsView = Backbone.View.extend({
'Github information',
'',
'Your Github link comes here: username/application-name',
undefined,
"username/application-name",
false,
[
{
rule: Joi.string().required(),
msg: "No github link given."
},
{
rule: Joi.string().regex(/^[a-zA-Z0-9]+[\/]/),
msg: "No valid github link given."
},
{
rule: Joi.string().required(),
msg: "No github link given."
}
]
));
@ -50,7 +50,7 @@ window.ApplicationsView = Backbone.View.extend({
'Version (optional)',
'',
'Example: v1.1.2 for Version 1.1.2 - if no version is commited, master is used',
undefined,
'master',
false,
/[<>&'"]/
));

View File

@ -172,16 +172,20 @@
"",
true,
[
{
rule: Joi.string().required(),
msg: "No collection name given."
},
{
rule: Joi.string().regex(/^[a-zA-Z]/),
msg: "Collection name must always start with a letter."
},
{
rule: Joi.string().regex(/^[a-zA-Z0-9\-_]*$/),
msg: 'Only Symbols "_" and "-" are allowed.'
},
{
rule: Joi.string().required(),
msg: "No collection name given."
}
]
)
)
);
}

View File

@ -37,7 +37,7 @@
}, this);
//if type in collectionsDropdown2 is changed,
// the page will be rerendered, so check the toggel button
//the page will be rerendered, so check the toggel button
if($('#collectionsDropdown2').css('display') === 'none') {
$('#collectionsToggle').removeClass('activated');
@ -304,6 +304,10 @@
rule: Joi.string().regex(/^[a-zA-Z]/),
msg: "Collection name must always start with a letter."
},
{
rule: Joi.string().regex(/^[a-zA-Z0-9\-_]*$/),
msg: 'Only Symbols "_" and "-" are allowed.'
},
{
rule: Joi.string().required(),
msg: "No collection name given."

View File

@ -246,14 +246,17 @@
[
{
rule: Joi.string().regex(/^[a-zA-Z]/),
msg: "Database name must always start with a letter."
msg: "Database name must start with a letter."
},
{
rule: Joi.string().regex(/^[a-zA-Z0-9\-_]*$/),
msg: 'Only Symbols "_" and "-" are allowed.'
},
{
rule: Joi.string().required(),
msg: "No database name given."
}
]
)
);
tableContent.push(
@ -266,7 +269,13 @@
+ "you will not be able to see the database. "
+ "If there is a failure you will be informed.",
"Database Owner",
true
true,
[
{
rule: Joi.string().required(),
msg: "No username given."
}
]
)
);
tableContent.push(

View File

@ -1,5 +1,5 @@
/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true */
/*global require, arangoHelper, _, $, window, arangoHelper, templateEngine */
/*global require, arangoHelper, _, $, window, arangoHelper, templateEngine, Joi*/
(function() {
"use strict";
@ -50,7 +50,6 @@
"click #filterSend" : "sendFilter",
"click #addFilterItem" : "addFilterItem",
"click .removeFilterItem" : "removeFilterItem",
"click #confirmCreateEdge" : "addEdge",
"click #documentsTableID tr" : "clicked",
"click #deleteDoc" : "remove",
"click #addDocumentButton" : "addDocument",
@ -108,9 +107,6 @@
},
listenKey: function (e) {
if(e.keyCode === 13){
this.addEdge();
}
},
resetView: function () {
@ -342,8 +338,56 @@
// second parameter is "true" to disable caching of collection type
doctype = arangoHelper.collectionApiType(collid, true);
if (doctype === 'edge') {
$('#edgeCreateModal').modal('show');
arangoHelper.fixTooltips(".modalTooltips", "left");
//$('#edgeCreateModal').modal('show');
//arangoHelper.fixTooltips(".modalTooltips", "left");
var buttons = [], tableContent = [];
tableContent.push(
window.modalView.createTextEntry(
'new-edge-from-attr',
'_from',
'',
"document _id: document handle of the linked vertex (incoming relation)",
undefined,
false,
[
{
rule: Joi.string().required(),
msg: "No _from attribute given."
}
]
)
);
tableContent.push(
window.modalView.createTextEntry(
'new-edge-to',
'_to',
'',
"document _id: document handle of the linked vertex (outgoing relation)",
undefined,
false,
[
{
rule: Joi.string().required(),
msg: "No _to attribute given."
}
]
)
);
buttons.push(
window.modalView.createSuccessButton('Create', this.addEdge.bind(this))
);
window.modalView.show(
'modalTable.ejs',
'Create edge',
buttons,
tableContent
);
return;
}
@ -359,20 +403,13 @@
addEdge: function () {
var collid = window.location.hash.split("/")[1];
var from = $('#new-document-from').val();
var to = $('#new-document-to').val();
if (from === '') {
//Heiko: Form-Validator - from is missing
return;
}
if (to === '') {
//Heiko: Form-Validator - to is missing
return;
}
var from = $('.modal-body #new-edge-from-attr').last().val();
var to = $('.modal-body #new-edge-to').last().val();
var result = this.documentStore.createTypeEdge(collid, from, to);
if (result !== false) {
$('#edgeCreateModal').modal('hide');
//$('#edgeCreateModal').modal('hide');
window.modalView.hide();
window.location.hash = "collection/"+result;
}
//Error

View File

@ -1,5 +1,5 @@
/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true */
/*global Backbone, $, window, EJS, arangoHelper, _, templateEngine*/
/*global Backbone, $, window, EJS, arangoHelper, _, templateEngine, Joi*/
(function() {
"use strict";
@ -89,7 +89,13 @@
"change-mount-point", "Mount", this.model.get("mount"),
"The path where the app can be reached.",
"mount-path",
true
true,
[
{
rule: Joi.string().required(),
msg: "No mount-path given."
}
]
));
/*
* For the future, update apps to available newer versions

View File

@ -285,8 +285,17 @@
var ind = buttons.indexOf(this.closeButton);
buttons.splice(ind, 1);
var completeTableContent = tableContent;
try {
_.each(advancedContent.content, function(x) {
completeTableContent.push(x);
});
}
catch(ignore) {
}
//handle select2
_.each(tableContent, function(r) {
_.each(completeTableContent, function(r) {
if (r.type === self.tables.SELECT2) {
$('#'+r.id).select2({
tags: r.tags || [],
@ -299,11 +308,11 @@
});//handle select2
self.testInput = (function(){
_.each(tableContent,function(r){
_.each(completeTableContent,function(r){
if(r.validateInput) {
//catch result of validation and act
$('#' + r.id).on('keyup', function(){
$('#' + r.id).on('keyup focusout', function(e){
var validation = r.validateInput($('#' + r.id));
var error = false, msg;
@ -314,8 +323,14 @@
toCheck: validator.rule
});
var valueToCheck = $('#' + r.id).val();
if (valueToCheck === '' && e.type === "keyup") {
return;
}
Joi.validate({
toCheck: $('#' + r.id).val()
toCheck: valueToCheck
},
schema,
function (err, value) {
@ -331,13 +346,15 @@
if(error === true){
// if validation throws an error
$('#' + r.id).addClass('invalid-input');
$('.modal-footer .button-success').prop('disabled', true);
$('.modal-footer .button-success').addClass('disabled');
if (errorElement) {
//error element available
$(errorElement).text(msg);
}
else {
//render error element
//error element not available
$('#' + r.id).after('<p class="errorMessage">' + msg+ '</p>');
}
@ -345,6 +362,8 @@
else {
//validation throws success
$('#' + r.id).removeClass('invalid-input');
$('.modal-footer .button-success').prop('disabled', false);
$('.modal-footer .button-success').removeClass('disabled');
if (errorElement) {
$(errorElement).remove();
}

View File

@ -1,6 +1,6 @@
/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true */
/*global require, exports, Backbone, EJS, $, setTimeout, localStorage, ace, Storage, window, _ */
/*global arangoHelper, templateEngine, jQuery*/
/*global arangoHelper, templateEngine, jQuery, Joi*/
(function () {
"use strict";
@ -59,7 +59,12 @@
undefined,
undefined,
false,
/[<>&'"]/
[
{
rule: Joi.string().required(),
msg: "No query name given."
}
]
)
);
buttons.push(

View File

@ -27,6 +27,8 @@
self.resize();
});
this.executeJs("start_pretty_print()");
return this;
},

View File

@ -1,6 +1,6 @@
/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true */
/*global window, document, Backbone, EJS, SwaggerUi, hljs, $, arangoHelper, templateEngine,
CryptoJS */
CryptoJS, Joi */
(function() {
"use strict";
@ -406,7 +406,20 @@
tableContent = [];
tableContent.push(
window.modalView.createTextEntry("newUsername", "Username", "", false, "Username", true)
window.modalView.createTextEntry(
"newUsername",
"Username",
"",
false,
"Username",
true,
[
{
rule: Joi.string().required(),
msg: "No username given."
}
]
)
);
tableContent.push(
window.modalView.createTextEntry("newName", "Name", "", false, "Name", false)

View File

@ -38,7 +38,10 @@
.button-success {
@extend %btn;
@extend %positive;
}
.button-success:disabled {
@extend %neutral;
}
.button-danger {

View File

@ -159,6 +159,10 @@
width: 398px;
}
.collectionTh {
height: 55px;
}
.tab-content {
min-height: 200px;
}
@ -167,7 +171,8 @@
color: red;
font-size: 9pt;
margin-bottom: 5px;
margin-top: -5px;
margin-top: -9px;
position: absolute;
}
}

View File

@ -1,4 +1,7 @@
.navbar {
-webkit-font-smoothing: subpixel-antialiased;
.nav {
li.dropdown {
.active > .dropdown-toggle,

View File

@ -1420,9 +1420,9 @@ nav.navbar, footer.footer {
.button-warning:hover, .button-warning:focus {
background-color: #f89406; }
.button-neutral, .button-close {
.button-neutral, .button-success:disabled, .button-close {
background-color: #8f8d8c; }
.button-neutral:hover, .button-close:hover, .button-neutral:focus, .button-close:focus {
.button-neutral:hover, .button-success:hover:disabled, .button-close:hover, .button-neutral:focus, .button-success:focus:disabled, .button-close:focus {
background-color: #736b68; }
.dashboard-sub-bar-menu {
@ -1512,10 +1512,12 @@ ul.link-dropdown-menu, ul.user-dropdown-menu, ul.gv-dropdown-menu {
right: 8px;
top: -6px; }
.navbar .nav li.dropdown .active > .dropdown-toggle,
.navbar .nav li.dropdown .open > .dropdown-toggle,
.navbar .nav li.dropdown .open.active > .dropdown-toggle {
background: #788f3d; }
.navbar {
-webkit-font-smoothing: subpixel-antialiased; }
.navbar .nav li.dropdown .active > .dropdown-toggle,
.navbar .nav li.dropdown .open > .dropdown-toggle,
.navbar .nav li.dropdown .open.active > .dropdown-toggle {
background: #788f3d; }
nav.navbar {
height: 38px;
@ -4456,13 +4458,16 @@ div.breadcrumb a.disabledBread {
width: 384px; }
.modal-body select {
width: 398px; }
.modal-body .collectionTh {
height: 55px; }
.modal-body .tab-content {
min-height: 200px; }
.modal-body .errorMessage {
color: red;
font-size: 9pt;
margin-bottom: 5px;
margin-top: -5px; }
margin-top: -9px;
position: absolute; }
.modal-text {
font-weight: 300;

View File

@ -42,7 +42,7 @@ jasmine.getGlobal().clearTimeout = function (timeoutId) {
exports.executeTestSuite = function (specFileNames, options) {
'use strict';
var sandbox = jasmine.getEnv(),
var sandbox = new jasmine.Env(),
format = options.format || 'progress';
// Explicitly add require

View File

@ -52,6 +52,9 @@
// autoload all modules and reload routing information in all threads
internal.executeGlobalContextFunction("bootstrapCoordinator");
// start the queue manager once
require('org/arangodb/foxx/queues/manager').run();
console.info("bootstraped coordinator %s", ArangoServerState.id());
return true;
};

View File

@ -57,6 +57,9 @@
require("org/arangodb/statistics").startup();
}
// start the queue manager once
require('org/arangodb/foxx/queues/manager').run();
console.info("bootstraped DB server %s", ArangoServerState.id());
return true;
};

View File

@ -32,8 +32,10 @@ var Controller = require("org/arangodb/foxx/controller").Controller,
Model = require("org/arangodb/foxx/model").Model,
Repository = require("org/arangodb/foxx/repository").Repository,
manager = require("org/arangodb/foxx/manager"),
queues = require("org/arangodb/foxx/queues"),
arangodb = require("org/arangodb");
exports.queues = queues;
exports.Controller = Controller;
exports.Model = Model;
exports.Repository = Repository;

View File

@ -1487,7 +1487,8 @@ exports.appRoutes = function () {
return arangodb.db._executeTransaction({
collections: {
read: [ aal.name() ]
read: [ '_queues', '_jobs', aal.name() ],
write: [ '_queues', '_jobs' ]
},
params: {
aal : aal

View File

@ -0,0 +1,308 @@
/*jslint es5: true, indent: 2, nomen: true, maxlen: 120 */
/*global module, require */
////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var _ = require('underscore'),
flatten = require('internal').flatten,
arangodb = require('org/arangodb'),
console = require('console'),
db = arangodb.db,
failImmutable,
queueMap,
jobMap,
queues,
getJobs,
Job,
Queue;
failImmutable = function (name) {
'use strict';
return function () {
throw new Error(name + ' is not mutable');
};
};
queueMap = Object.create(null);
jobMap = Object.create(null);
queues = {
_jobTypes: Object.create(null),
get: function (key) {
'use strict';
if (!queueMap[key]) {
if (!db._queues.exists(key)) {
throw new Error('Queue does not exist: ' + key);
}
queueMap[key] = new Queue(key);
}
return queueMap[key];
},
create: function (key, maxWorkers) {
'use strict';
try {
db._queues.save({_key: key, maxWorkers: maxWorkers || 1});
} catch (err) {
if (!err instanceof arangodb.ArangoError ||
err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
throw err;
}
if (maxWorkers) {
db._queues.update(key, {maxWorkers: maxWorkers});
}
}
if (!queueMap[key]) {
queueMap[key] = new Queue(key);
}
return queueMap[key];
},
delete: function (key) {
'use strict';
var result = false;
db._executeTransaction({
collections: {
read: ['_queues'],
write: ['_queues']
},
action: function () {
if (db._queues.exists(key)) {
db._queues.remove(key);
result = true;
}
}
});
return result;
},
registerJobType: function (type, opts) {
'use strict';
if (typeof opts === 'function') {
opts = {execute: opts};
}
if (typeof opts.execute !== 'function') {
throw new Error('Must provide a function to execute!');
}
if (opts.schema && typeof opts.schema.validate !== 'function') {
throw new Error('Schema must be a joi schema!');
}
var cfg = _.extend({maxFailures: 0}, opts);
queues._jobTypes[type] = cfg;
}
};
getJobs = function (queue, status, type) {
'use strict';
var vars = {},
aql = 'FOR job IN _jobs';
if (queue !== undefined) {
aql += ' FILTER job.queue == @queue';
vars.queue = queue;
}
if (status !== undefined) {
aql += ' FILTER job.status == @status';
vars.status = status;
}
if (type !== undefined) {
aql += ' FILTER job.type == @type';
vars.type = type;
}
aql += ' SORT job.delayUntil ASC RETURN job._id';
return db._createStatement({
query: aql,
bindVars: vars
}).execute().toArray();
};
Job = function Job(id) {
'use strict';
var self = this;
Object.defineProperty(self, 'id', {
get: function () {
return id;
},
configurable: false,
enumerable: true
});
_.each(['data', 'status', 'type', 'failures'], function (key) {
Object.defineProperty(self, key, {
get: function () {
var value = db._jobs.document(this.id)[key];
return (value && typeof value === 'object') ? Object.freeze(value) : value;
},
set: failImmutable(key),
configurable: false,
enumerable: true
});
});
};
_.extend(Job.prototype, {
abort: function () {
'use strict';
var self = this;
db._executeTransaction({
collections: {
read: ['_jobs'],
write: ['_jobs']
},
action: function () {
var job = db._jobs.document(self.id);
if (job.status !== 'completed') {
db._jobs.update(job, {
status: 'failed',
modified: Date.now(),
failures: job.failures.concat([
flatten(new Error('Job aborted.'))
])
});
}
}
});
},
reset: function () {
'use strict';
db._jobs.update(this.id, {
status: 'pending'
});
}
});
Queue = function Queue(name) {
'use strict';
Object.defineProperty(this, 'name', {
get: function () {
return name;
},
set: failImmutable('name'),
configurable: false,
enumerable: true
});
};
_.extend(Queue.prototype, {
push: function (name, data, opts) {
'use strict';
var type, result, now;
if (typeof name !== 'string') {
throw new Error('Must pass a job type!');
}
if (!opts) {
opts = {};
}
type = queues._jobTypes[name];
if (type !== undefined) {
if (type.schema) {
result = type.schema.validate(data);
if (result.error) {
throw result.error;
}
data = result.value;
}
} else if (opts.allowUnknown) {
console.warn('Unknown job type: ' + name);
} else {
throw new Error('Unknown job type: ' + name);
}
now = Date.now();
return db._jobs.save({
status: 'pending',
queue: this.name,
type: name,
failures: [],
data: data,
created: now,
modified: now,
maxFailures: opts.maxFailures === Infinity ? -1 : opts.maxFailures,
backOff: typeof opts.backOff === 'function' ? opts.backOff.toString() : opts.backOff,
delayUntil: opts.delayUntil || now,
onSuccess: opts.success ? opts.success.toString() : null,
onFailure: opts.failure ? opts.failure.toString() : null
})._id;
},
get: function (id) {
'use strict';
if (!id.match(/^_jobs\//)) {
id = '_jobs/' + id;
}
if (!jobMap[id]) {
jobMap[id] = new Job(id);
}
return jobMap[id];
},
delete: function (id) {
'use strict';
return db._executeTransaction({
collections: {
read: ['_jobs'],
write: ['_jobs']
},
action: function () {
try {
db._jobs.remove(id);
return true;
} catch (err) {
return false;
}
}
});
},
pending: function (jobType) {
'use strict';
return getJobs(this.name, 'pending', jobType);
},
complete: function (jobType) {
'use strict';
return getJobs(this.name, 'complete', jobType);
},
failed: function (jobType) {
'use strict';
return getJobs(this.name, 'failed', jobType);
},
progress: function (jobType) {
'use strict';
return getJobs(this.name, 'progress', jobType);
},
all: function (jobType) {
'use strict';
return getJobs(this.name, undefined, jobType);
}
});
queues.create('default');
module.exports = queues;
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:

View File

@ -0,0 +1,91 @@
/*jslint es5: true, indent: 2, nomen: true, maxlen: 120 */
/*global exports, require */
////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues manager
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var QUEUE_MANAGER_PERIOD = 1000, // in ms
_ = require('underscore'),
tasks = require('org/arangodb/tasks'),
db = require('org/arangodb').db;
exports.manage = function () {
'use strict';
db._executeTransaction({
collections: {
read: ['_queues', '_jobs'],
write: ['_jobs']
},
action: function () {
db._queues.all().toArray().forEach(function (queue) {
var numBusy = db._jobs.byExample({
queue: queue._key,
status: 'progress'
}).count();
if (numBusy >= queue.maxWorkers) {
return;
}
db._createStatement({
query: (
'FOR job IN _jobs'
+ ' FILTER job.queue == @queue'
+ ' && job.status == "pending"'
+ ' && job.delayUntil <= @now'
+ ' SORT job.delayUntil ASC'
+ ' LIMIT @max'
+ ' RETURN job'
),
bindVars: {
queue: queue._key,
now: Date.now(),
max: queue.maxWorkers - numBusy
}
}).execute().toArray().forEach(function (doc) {
db._jobs.update(doc, {status: 'progress'});
tasks.register({
command: function (job) {
require('org/arangodb/foxx/queues/worker').work(job);
},
params: _.extend({}, doc, {status: 'progress'}),
offset: 0
});
});
});
}
});
};
exports.run = function () {
'use strict';
db._jobs.updateByExample({status: 'progress'}, {status: 'pending'});
return tasks.register({
command: function () {
require('org/arangodb/foxx/queues/manager').manage();
},
period: QUEUE_MANAGER_PERIOD / 1000
});
};

View File

@ -0,0 +1,134 @@
/*jslint es5: true, indent: 2, nomen: true, maxlen: 120, evil: true */
/*global exports, require */
////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues workers
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var db = require('org/arangodb').db,
flatten = require('internal').flatten,
exponentialBackOff = require('internal').exponentialBackOff,
console = require('console'),
queues = require('org/arangodb/foxx').queues,
getBackOffDelay;
getBackOffDelay = function (job, cfg) {
'use strict';
var n = job.failures.length - 1;
if (typeof job.backOff === 'string') {
try {
return eval('(' + job.backOff + ')(' + n + ')');
} catch (jobStrErr) {
console.error('Failed to call backOff function of job ' + job._key, jobStrErr);
}
} else if (typeof job.backOff === 'number' && job.backOff >= 0 && job.backOff < Infinity) {
return exponentialBackOff(n, job.backOff);
}
if (typeof cfg.backOff === 'function') {
try {
return cfg.backOff(n);
} catch (cfgFnErr) {
console.error('Failed to call backOff function of job type ' + job.type, cfgFnErr);
}
} else if (typeof cfg.backOff === 'string') {
try {
return eval('(' + cfg.backOff + ')(' + n + ')');
} catch (cfgStrErr) {
console.error('Failed to call backOff function of job type ' + job.type, cfgStrErr);
}
}
if (typeof cfg.backOff === 'number' && cfg.backOff >= 0 && cfg.backOff < Infinity) {
return exponentialBackOff(n, cfg.backOff);
}
return exponentialBackOff(n, 1000);
};
exports.work = function (job) {
'use strict';
var cfg = queues._jobTypes[job.type],
success = true,
callback = null,
now = Date.now(),
maxFailures;
if (!cfg) {
console.warn('Unknown job type for job ' + job._key + ':', job.type);
db._jobs.update(job._key, {status: 'pending'});
return;
}
maxFailures = (
typeof job.maxFailures === 'number'
? (job.maxFailures < 0 ? Infinity : job.maxFailures)
: (cfg.maxFailures < 0 ? Infinity : cfg.maxFailures)
) || 0;
try {
cfg.execute(job.data, job._id);
} catch (executeErr) {
console.error('Job ' + job._key + ' failed:', executeErr);
job.failures.push(flatten(executeErr));
success = false;
}
if (success) {
// mark complete
callback = job.onSuccess;
db._jobs.update(job._key, {modified: Date.now(), status: 'complete'});
} else if (job.failures.length > maxFailures) {
// mark failed
callback = job.onFailure;
db._jobs.update(job._key, {
modified: now,
failures: job.failures,
status: 'failed'
});
} else {
// queue for retry
db._jobs.update(job._key, {
modified: now,
delayUntil: now + getBackOffDelay(job, cfg),
failures: job.failures,
status: 'pending'
});
}
if (callback) {
try {
eval('(' + callback + ')(' + [
JSON.stringify(job._id),
JSON.stringify(job.data),
JSON.stringify(job.failures)
].join(', ') + ')');
} catch (callbackErr) {
console.error(
'Failed to execute '
+ (success ? 'success' : 'failure')
+ ' callback for job ' + job._key + ':',
callbackErr
);
}
}
};

View File

@ -45,7 +45,7 @@
var internal = require("internal");
var db = internal.db;
// one the cluster the kickstarter will call boostrap-role.js
// in the cluster the kickstarter will call boostrap-role.js
if (ArangoAgency.prefix() !== "") {
return true;
}
@ -66,6 +66,11 @@
// reload routing information
internal.loadStartup("server/bootstrap/routing.js").startup();
// start the queue manager once
if (internal.enableStatistics && internal.threadNumber === 0) {
require('org/arangodb/foxx/queues/manager').run();
}
return true;
}());

View File

@ -1298,6 +1298,44 @@
}
});
////////////////////////////////////////////////////////////////////////////////
/// @brief setupQueues
///
/// set up the collection _queues
////////////////////////////////////////////////////////////////////////////////
addTask({
name: "setupQueues",
description: "setup _queues collection",
mode: [ MODE_PRODUCTION, MODE_DEVELOPMENT ],
cluster: [ CLUSTER_NONE, CLUSTER_COORDINATOR_GLOBAL ],
database: [ DATABASE_INIT, DATABASE_UPGRADE ],
task: function () {
return createSystemCollection("_queues");
}
});
////////////////////////////////////////////////////////////////////////////////
/// @brief setupJobs
///
/// set up the collection _jobs
////////////////////////////////////////////////////////////////////////////////
addTask({
name: "setupJobs",
description: "setup _jobs collection",
mode: [ MODE_PRODUCTION, MODE_DEVELOPMENT ],
cluster: [ CLUSTER_NONE, CLUSTER_COORDINATOR_GLOBAL ],
database: [ DATABASE_INIT, DATABASE_UPGRADE ],
task: function () {
return createSystemCollection("_jobs");
}
});
// -----------------------------------------------------------------------------
// --SECTION-- public methods
// -----------------------------------------------------------------------------