From 10c4a9221c09889de5ac86f536a06d6d2ce78140 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 14 Sep 2019 15:43:52 -0700 Subject: [PATCH 01/43] Update README --- README.md | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8bd53dd..bb130dd 100755 --- a/README.md +++ b/README.md @@ -1,43 +1,46 @@ [![Gem Version](https://badge.fury.io/rb/nutella_framework.svg)](http://badge.fury.io/rb/nutella_framework) [![Build Status](https://travis-ci.org/nutella-framework/nutella_framework.svg?branch=master)](https://travis-ci.org/nutella-framework/nutella_framework) -[![Code Climate](https://codeclimate.com/github/nutella-framework/nutella_framework/badges/gpa.svg)](https://codeclimate.com/github/nutella-framework/nutella_framework) -nutella is a framework to build and run Macroworlds. It's still _very_ under development so any help [finding and fixing bugs](https://github.com/nutella-framework/nutella_framework/issues) will be greatly appreciated! +nutella is a framework to build and run Macroworlds. The original prototype was built as part of my dissertation and the code was hastly cobbled together at times. I have tried to cleanup over the years but please don't hate me if you find some skeletons. ☠️ If you want to help clean things up, give me a shout: help is greatly appreciated! # Installing -Nutella is written in ruby but it leverages a bunch of other technologies that need to be installed. You will need: +nutella works on OSX and Linux (tested on Ubuntu) and it depends on a couple other things to work correctly. You will need: 1. _ruby_ (version >= 2.1.0). Do yourself a favor and use [RVM](https://rvm.io/rvm/install) to install Ruby. -1. _git_ (version >= 1.8.0). Do yourself a favor and use [Homebrew](http://brew.sh/) to install git, if you are on OSX. +1. _git_ (version >= 1.8.0). Should come with the OS, yay! 1. _tmux_ (version >= 1.8.0). Do yourself a favor and use [Homebrew](http://brew.sh/) to install tmux, if you are on OSX. 1. _Docker_ (version >= 17.03.0). We use Docker to run the broker that handles all communications between all the pieces of the framework. Do yourself a favor and use [Docker for mac](https://store.docker.com/editions/community/docker-ce-desktop-mac), if you are on OSX. -1. _mongoDB_ (optional). You'll need mongoDB if you want to use it with `nutella.persist`. -Once you have all of these, to install nutella simply do: +Once you have all of installed, simply do: ``` gem install nutella_framework ``` Once the installation is complete you should be able to type `nutella` in your shell and get a welcome message. ## nutella checkup -If you are reading this you probably already saw the warning: "Looks like this is a fresh installation of nutella. Please run 'nutella checkup' to check all dependencies are installed". +If you are reading this you probably already saw the warning: "Looks like this is a fresh installation of nutella. Please run `nutella checkup` to check all dependencies are installed". -nutella is written in ruby but it's designed to run bots and interfaces written in virtually any programming language. All communications among these components are handled by an _MQTT broker_ which needs to be installed (together with its dependencies) before nutella can actually work correctly. Therefore **right after your install nutella** you should run: ``` nutella checkup ``` -This will install the [Mosca](http://www.mosca.io/) MQTT broker and make sure all the dependencies required by nutella are installed as well. - Congratulations! nutella is ready to use! # Where next? -If you already have an application you want to tinker with (like [RoomQuake](https://github.com/ltg-uic/roomquake)) simply checkout the application to a local folder, `cd /to/my/local/folder` and start tinkering away. Not sure how? Check out the [man page for the nutella command line tool](https://github.com/nutella-framework/nutella_framework/wiki). +If you **already have an application** you want to tinker with (like [RoomQuake](https://github.com/ltg-uic/roomquake)) simply clone the application to your local folder of choice, `cd /to/my/local/folder` and start tinkering away. Not sure how? Check out the [docs](https://github.com/nutella-framework/docs)! + +If you want to **create your first application** check out [this tutorial](https://github.com/nutella-framework/docs/blob/master/getting_started/tutorial_1.md). + -If you want to create your own application, hold on tight, a tutorial is coming soon. +# Contributing to nutella +Clone the repo, make sure you have [bundler](https://bundler.io/) installed, `bundle install` to take care of the dependencies, and then `rake` to run all the tests. If you want to build and install the gem just `rake install`. If you want a list of available build tasks, simply type `rake -T`. +# nutella 2.0 +In the summer of 2019, as a result of the feedback from nutella users (a.k.a. Tom), it became apparent to me that I needed to re-architect nutella into two separate components +1. A CLI that provided a way to interface with nutella +2. A "server" component that processes and executes the commands sent by either the CLI or other nutella components. -# Building (for contributors) -Clone the repo, `bundle install` to take care of the dependencies and then `rake` to run all the tests. If you want to build and install the gem just `rake install`. If you want a list of available build tasks, simply type `rake -T`. +It also became clear I needed to improve the documentation of the nutella protocol and how it works in order to allow the expansion to more languages than the ones currently supported. +Work on nutella 2 can be tracked on [this project](https://github.com/orgs/nutella-framework/projects/2). \ No newline at end of file From 2cf0799f38b91d2942a3cd2dde04fbdb1f2b006d Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 28 Sep 2019 16:36:13 -0700 Subject: [PATCH 02/43] Remove framework_components examples --- .../example_framework_bot.rb | 25 - .../example_framework_bot/startup | 6 - .../index.html | 24 - .../node_modules/nutella_lib/.npmignore | 10 - .../node_modules/nutella_lib/.travis.yml | 5 - .../node_modules/nutella_lib/LICENSE | 21 - .../node_modules/nutella_lib/README.md | 27 - .../nutella_lib/dist/nutella_lib.js | 4039 ----------------- .../nutella_lib/dist/nutella_lib.js.map | 1 - .../examples/browser_hello_world.html | 67 - .../nutella_lib/examples/node_hello_world.js | 51 - .../node_modules/nutella_lib/gulpfile.js | 31 - .../node_modules/nutella_lib/package.json | 41 - .../node_modules/nutella_lib/src/app_core.js | 19 - .../nutella_lib/src/app_core_browser.js | 17 - .../node_modules/nutella_lib/src/app_log.js | 50 - .../node_modules/nutella_lib/src/app_net.js | 279 -- .../nutella_lib/src/app_persist.js | 20 - .../nutella_lib/src/fr_core_browser.js | 17 - .../node_modules/nutella_lib/src/fr_log.js | 50 - .../node_modules/nutella_lib/src/fr_net.js | 499 -- .../node_modules/nutella_lib/src/nutella_i.js | 74 - .../nutella_lib/src/nutella_i_browser.js | 130 - .../nutella_lib/src/nutella_lib.js | 91 - .../nutella_lib/src/nutella_lib_browser.js | 90 - .../node_modules/nutella_lib/src/run_log.js | 51 - .../node_modules/nutella_lib/src/run_net.js | 84 - .../nutella_lib/src/run_persist.js | 20 - .../node_modules/nutella_lib/src/util/net.js | 327 -- .../nutella_lib/test/nutella.test.js | 16 - .../node_modules/nutella_lib/test/runner.html | 22 - .../package.json | 15 - 32 files changed, 6219 deletions(-) delete mode 100755 example_framework_components/example_framework_bot/example_framework_bot.rb delete mode 100755 example_framework_components/example_framework_bot/startup delete mode 100755 example_framework_components/example_framework_web_interface/index.html delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.npmignore delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.travis.yml delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/LICENSE delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/README.md delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js.map delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/browser_hello_world.html delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/node_hello_world.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/gulpfile.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/package.json delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core_browser.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_log.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_net.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_persist.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_core_browser.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_log.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_net.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i_browser.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib_browser.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_log.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_net.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_persist.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/util/net.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/nutella.test.js delete mode 100755 example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/runner.html delete mode 100755 example_framework_components/example_framework_web_interface/package.json diff --git a/example_framework_components/example_framework_bot/example_framework_bot.rb b/example_framework_components/example_framework_bot/example_framework_bot.rb deleted file mode 100755 index a2af9c4..0000000 --- a/example_framework_components/example_framework_bot/example_framework_bot.rb +++ /dev/null @@ -1,25 +0,0 @@ -require_relative '../../lib/config/runlist' -require_relative '../../lib/config/config' -require_relative '../../nutella_lib/framework_core' - -# Framework bots can access all the parameters they need directly -# from the configuration file and the runlist, -# to which they have full access to. - -# Access the config file like so: -# Nutella.config['broker'] - -# Access the runs list like so: -# Nutella.runlist.all_runs - - -# Initialize this bot as framework component -nutella.f.init(Nutella.config['broker'], 'example_framework_bot') - - -# Your code goes here! Go crazy! - - -# Does your bot die? If all your bot is doing is waiting for message on the network -# and responding to them, the main thread will terminate unless you call... -# nutella.f.net.listen \ No newline at end of file diff --git a/example_framework_components/example_framework_bot/startup b/example_framework_components/example_framework_bot/startup deleted file mode 100755 index 17b45ca..0000000 --- a/example_framework_components/example_framework_bot/startup +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -BASEDIR=$(dirname $0) - -ruby $BASEDIR/example_framework_bot.rb > /dev/null 2>&1 & -echo $! > $BASEDIR/.pid diff --git a/example_framework_components/example_framework_web_interface/index.html b/example_framework_components/example_framework_web_interface/index.html deleted file mode 100755 index f4014d9..0000000 --- a/example_framework_components/example_framework_web_interface/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Example framework interface - - - - -

Example framework interface

- - - - - - \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.npmignore b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.npmignore deleted file mode 100755 index e9d725b..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# npm modules (except simple-mqtt-client) -node_modules/* -!node_modules/simple-mqtt-client -docs/ - -# Mac -.DS_Store - -# WebStorm -.idea/ \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.travis.yml b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.travis.yml deleted file mode 100755 index 6b7f05d..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: - - "0.10" -notifications: - email: false \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/LICENSE b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/LICENSE deleted file mode 100755 index f42c0eb..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 by The Board of Trustees of the University of Illinois at Chicago - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/README.md b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/README.md deleted file mode 100755 index ec9d4ba..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/README.md +++ /dev/null @@ -1,27 +0,0 @@ -[![Build Status](https://travis-ci.org/nutella-framework/nutella_lib.js.svg?branch=master)](https://travis-ci.org/nutella-framework/nutella_lib.js) - -# nutella library for node.js and the browser - -## Installation -For node.js projects do -``` -npm install nutella_lib -``` - -For browser projects either: - -1. `npm install nutella_lib` and then use [browserify](http://browserify.org/) OR -2. use the bundled `nutella_lib.js` in `dist` - - -## Building the project -For developers working on the library. We are using gulp + browserify + watchify to continuously and incrementally build the library as we develop. - -**To contribute**: Clone the repo and `gulp bundle` inside the project directory. Every time you make a change to any of the files required by the library gulp will rebuild it. - - -## Releasing a new version -For developers working on the library, to release a new version: - -- Update the version in the `package.json` -- Publish to npm by doing `npm publish` \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js deleted file mode 100755 index 43ecf49..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js +++ /dev/null @@ -1,4039 +0,0 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.NUTELLA = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0; --i) { - result += chars[Math.round(Math.random() * (chars.length - 1))]; - } - return result; -}; - -// -// Helper function that connects the MQTT client in the browser -// -function connectBrowser (subscriptions, backlog, host, clientId) { - // Create client - var client = new mqtt_lib.Client(host, Number(1884), clientId); - // Register callback for connection lost - client.onConnectionLost = function() { - // TODO try to reconnect - }; - // Register callback for received message - client.onMessageArrived = function (message) { - // Execute all the appropriate callbacks: - // the ones specific to this channel with a single parameter (message) - // the ones associated to a wildcard channel, with two parameters (message and channel) - var cbs = findCallbacks(subscriptions, message.destinationName); - if (cbs!==undefined) { - cbs.forEach(function(cb) { - if (Object.keys(subscriptions).indexOf(message.destinationName)!==-1) - cb(message.payloadString); - else - cb(message.payloadString, message.destinationName); - }); - } - }; - // Connect - client.connect({onSuccess: function() { - // Execute the backlog of operations performed while the client wasn't connected - backlog.forEach(function(e) { - e.op.apply(this, e.params); - }); - }}); - return client; -} - - - -/** - * Disconnects from the MQTT client. - */ -SimpleMQTTClient.prototype.disconnect = function () { - this.client.disconnect(); - this.subscriptions = {}; -}; - - - -/** - * Subscribes to a channel and registers a callback. - * - * @param {string} channel - the channel we are subscribing to. - * @param {callback} callback - A function that is executed every time a message is received on that channel. - * @param {callback} [done_callback] - A function that is executed once the subscribe operation has completed successfully. - */ -SimpleMQTTClient.prototype.subscribe = function (channel, callback, done_callback) { - subscribeBrowser(this.client, this.subscriptions, this.backlog, channel, callback, done_callback); -}; - - -// -// Helper function that subscribes to a channel in the browser -// -function subscribeBrowser (client, subscriptions, backlog, channel, callback, done_callback) { - if ( addToBacklog(client, backlog, subscribeBrowser, [client, subscriptions, backlog, channel, callback, done_callback]) ) return; - if (subscriptions[channel]===undefined) { - subscriptions[channel] = [callback]; - client.subscribe(channel, {qos: 0, onSuccess: function() { - // If there is a done_callback defined, execute it - if (done_callback!==undefined) done_callback(); - }}); - } else { - subscriptions[channel].push(callback); - // If there is a done_callback defined, execute it - if (done_callback!==undefined) done_callback(); - } -} - - - -/** - * Unsubscribe from a channel. - * - * @param {string} channel - the channel we are unsubscribing from. - * @param {function} callback - the callback we are trying to unregister - * @param {callback} [done_callback] - A function that is executed once the unsubscribe operation has completed successfully. - */ -SimpleMQTTClient.prototype.unsubscribe = function (channel, callback, done_callback) { - unsubscribeBrowser(this.client, this.subscriptions, this.backlog, channel, callback, done_callback); -}; - - - - -// -// Helper function that unsubscribes from a channel in the browser -// -var unsubscribeBrowser = function(client, subscriptions, backlog, channel, callback, done_callback) { - if ( addToBacklog(client, backlog, unsubscribeBrowser, [client, subscriptions, backlog, channel, callback, done_callback]) ) return; - if (subscriptions[channel]===undefined) - return; - subscriptions[channel].splice(subscriptions[channel].indexOf(callback), 1); - if (subscriptions[channel].length===0) { - delete subscriptions[channel]; - client.unsubscribe(channel, {onSuccess : function() { - // If there is a done_callback defined, execute it - if (done_callback!==undefined) done_callback(); - }}); - } -}; - - -/** - * Lists all the channels we are currently subscribed to. - * - * @returns {Array} a lists of all the channels we are currently subscribed to. - */ -SimpleMQTTClient.prototype.getSubscriptions = function () { - return Object.keys(this.subscriptions); -}; - - -/** - * Publishes a message to a channel. - * - * @param {string} channel - the channel we are publishing to. - * @param {string} message - the message we are publishing. - */ -SimpleMQTTClient.prototype.publish = function (channel, message) { - publishBrowser(this.client, this.backlog, channel, message) -}; - - -// -// Helper function that publishes to a channel in the browser -// -var publishBrowser = function (client, backlog, channel, message) { - if ( addToBacklog(client, backlog, publishBrowser, [client, backlog, channel, message]) ) return; - message = new mqtt_lib.Message(message); - message.destinationName = channel; - client.send(message); -}; - - - -SimpleMQTTClient.prototype.isChannelWildcard = function(channel) { - return channel.indexOf('#')>-1 || channel.indexOf('+')>-1 ; -} - - - - - - -// -// Helper function that selects the right callback when a message is received -// -function findCallbacks (subscriptions, channel) { - // First try to see if a callback for the exact channel exists - if(Object.keys(subscriptions).indexOf(channel)!==-1) - return subscriptions[channel]; - // If it doesn't then let's try to see if the channel matches a wildcard callback - var pattern = matchesWildcard(subscriptions, channel); - if (pattern!== undefined) { - return subscriptions[pattern]; - } - // If there's no exact match or wildcard we have to return undefined - return undefined; -}; - - -// -// Helper function that tries to match a channel with each subscription -// it returns undefined if no match is found -// -function matchesWildcard (subscriptions, channel) { - var i; - var subs = Object.keys(subscriptions); - for (i=0; i < subs.length; i++) { - if (matchesFilter(subs[i], channel)) { - return subs[i]; - } - } - return undefined; -}; - - -// -// Helper function that checks a certain channel and see if it matches a wildcard pattern -// Returns true if the channel matches a pattern (including the exact pattern) -// -function matchesFilter (pattern, channel) { - // If multi-level wildcard is the only character in pattern, then any string will match - if (pattern==="#") { - return true; - } - // Handle all other multi-level wildcards - // FROM SPEC: The number sign (‘#’ U+0023) is a wildcard character that matches any number of levels within a topic. The multi-level wildcard represents the parent and any number of child levels. The multi-level wildcard character MUST be specified either on its own or following a topic level separator. In either case it MUST be the last character specified in the Topic Filter - var p_wo_wildcard = pattern.substring(0, pattern.length-2); - var str_wo_details = channel.substring(0, pattern.length-2); - if (pattern.slice(-1)=='#' && p_wo_wildcard==str_wo_details) { - return true; - } - // TODO Handle single-level wildcards (+) - // FROM SPEC: The single-level wildcard can be used at any level in the Topic Filter, including first and last levels. Where it is used it MUST occupy an entire level of the filter [MQTT-4.7.1-3]. It can be used at more than one level in the Topic Filter and can be used in conjunction with the multilevel wildcard. - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718107 - return false; -}; - - -// -// Helper method that queues operations into the backlog. -// This method is used to make `connect` "synchronous" by -// queueing up operations on the client until it is connected. -// -// @param {string} method - the method that needs to be added to the backlog -// @param {Array} parameters - parameters to the method being added to the backlog -// @returns {boolean} true if the method was successfully added, false otherwise -// -function addToBacklog (client, backlog, method, parameters) { - if (!client.isConnected() ) { - backlog.push({ - op : method, - params : parameters - }); - return true; - } - return false; -}; - - - - -// -// Exports SimpleMQTTClient class for other modules -// -module.exports = SimpleMQTTClient; - -},{"./paho/mqttws31":3}],3:[function(require,module,exports){ -/******************************************************************************* - * Copyright (c) 2013 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Andrew Banks - initial API and implementation and initial documentation - *******************************************************************************/ - - -// Only expose a single object name in the global namespace. -// Everything must go through this module. Global Paho.MQTT module -// only has a single public function, client, which returns -// a Paho.MQTT client object given connection details. - -/** - * Send and receive messages using web browsers. - *

- * This programming interface lets a JavaScript client application use the MQTT V3.1 or - * V3.1.1 protocol to connect to an MQTT-supporting messaging server. - * - * The function supported includes: - *

    - *
  1. Connecting to and disconnecting from a server. The server is identified by its host name and port number. - *
  2. Specifying options that relate to the communications link with the server, - * for example the frequency of keep-alive heartbeats, and whether SSL/TLS is required. - *
  3. Subscribing to and receiving messages from MQTT Topics. - *
  4. Publishing messages to MQTT Topics. - *
- *

- * The API consists of two main objects: - *

- *
{@link Paho.MQTT.Client}
- *
This contains methods that provide the functionality of the API, - * including provision of callbacks that notify the application when a message - * arrives from or is delivered to the messaging server, - * or when the status of its connection to the messaging server changes.
- *
{@link Paho.MQTT.Message}
- *
This encapsulates the payload of the message along with various attributes - * associated with its delivery, in particular the destination to which it has - * been (or is about to be) sent.
- *
- *

- * The programming interface validates parameters passed to it, and will throw - * an Error containing an error message intended for developer use, if it detects - * an error with any parameter. - *

- * Example: - * - *

-client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId");
-client.onConnectionLost = onConnectionLost;
-client.onMessageArrived = onMessageArrived;
-client.connect({onSuccess:onConnect});
-
-function onConnect() {
-  // Once a connection has been made, make a subscription and send a message.
-  console.log("onConnect");
-  client.subscribe("/World");
-  message = new Paho.MQTT.Message("Hello");
-  message.destinationName = "/World";
-  client.send(message); 
-};
-function onConnectionLost(responseObject) {
-  if (responseObject.errorCode !== 0)
-	console.log("onConnectionLost:"+responseObject.errorMessage);
-};
-function onMessageArrived(message) {
-  console.log("onMessageArrived:"+message.payloadString);
-  client.disconnect(); 
-};	
- * 
- * @namespace Paho.MQTT - */ - -if (typeof Paho === "undefined") { - Paho = {}; -} - -Paho.MQTT = (function (global) { - - // Private variables below, these are only visible inside the function closure - // which is used to define the module. - - var version = "1.0.1"; - var buildLevel = "2014-11-18T11:57:44Z"; - - /** - * Unique message type identifiers, with associated - * associated integer values. - * @private - */ - var MESSAGE_TYPE = { - CONNECT: 1, - CONNACK: 2, - PUBLISH: 3, - PUBACK: 4, - PUBREC: 5, - PUBREL: 6, - PUBCOMP: 7, - SUBSCRIBE: 8, - SUBACK: 9, - UNSUBSCRIBE: 10, - UNSUBACK: 11, - PINGREQ: 12, - PINGRESP: 13, - DISCONNECT: 14 - }; - - // Collection of utility methods used to simplify module code - // and promote the DRY pattern. - - /** - * Validate an object's parameter names to ensure they - * match a list of expected variables name for this option - * type. Used to ensure option object passed into the API don't - * contain erroneous parameters. - * @param {Object} obj - User options object - * @param {Object} keys - valid keys and types that may exist in obj. - * @throws {Error} Invalid option parameter found. - * @private - */ - var validate = function(obj, keys) { - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - if (keys.hasOwnProperty(key)) { - if (typeof obj[key] !== keys[key]) - throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key])); - } else { - var errorStr = "Unknown property, " + key + ". Valid properties are:"; - for (var key in keys) - if (keys.hasOwnProperty(key)) - errorStr = errorStr+" "+key; - throw new Error(errorStr); - } - } - } - }; - - /** - * Return a new function which runs the user function bound - * to a fixed scope. - * @param {function} User function - * @param {object} Function scope - * @return {function} User function bound to another scope - * @private - */ - var scope = function (f, scope) { - return function () { - return f.apply(scope, arguments); - }; - }; - - /** - * Unique message type identifiers, with associated - * associated integer values. - * @private - */ - var ERROR = { - OK: {code:0, text:"AMQJSC0000I OK."}, - CONNECT_TIMEOUT: {code:1, text:"AMQJSC0001E Connect timed out."}, - SUBSCRIBE_TIMEOUT: {code:2, text:"AMQJS0002E Subscribe timed out."}, - UNSUBSCRIBE_TIMEOUT: {code:3, text:"AMQJS0003E Unsubscribe timed out."}, - PING_TIMEOUT: {code:4, text:"AMQJS0004E Ping timed out."}, - INTERNAL_ERROR: {code:5, text:"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"}, - CONNACK_RETURNCODE: {code:6, text:"AMQJS0006E Bad Connack return code:{0} {1}."}, - SOCKET_ERROR: {code:7, text:"AMQJS0007E Socket error:{0}."}, - SOCKET_CLOSE: {code:8, text:"AMQJS0008I Socket closed."}, - MALFORMED_UTF: {code:9, text:"AMQJS0009E Malformed UTF data:{0} {1} {2}."}, - UNSUPPORTED: {code:10, text:"AMQJS0010E {0} is not supported by this browser."}, - INVALID_STATE: {code:11, text:"AMQJS0011E Invalid state {0}."}, - INVALID_TYPE: {code:12, text:"AMQJS0012E Invalid type {0} for {1}."}, - INVALID_ARGUMENT: {code:13, text:"AMQJS0013E Invalid argument {0} for {1}."}, - UNSUPPORTED_OPERATION: {code:14, text:"AMQJS0014E Unsupported operation."}, - INVALID_STORED_DATA: {code:15, text:"AMQJS0015E Invalid data in local storage key={0} value={1}."}, - INVALID_MQTT_MESSAGE_TYPE: {code:16, text:"AMQJS0016E Invalid MQTT message type {0}."}, - MALFORMED_UNICODE: {code:17, text:"AMQJS0017E Malformed Unicode string:{0} {1}."}, - }; - - /** CONNACK RC Meaning. */ - var CONNACK_RC = { - 0:"Connection Accepted", - 1:"Connection Refused: unacceptable protocol version", - 2:"Connection Refused: identifier rejected", - 3:"Connection Refused: server unavailable", - 4:"Connection Refused: bad user name or password", - 5:"Connection Refused: not authorized" - }; - - /** - * Format an error message text. - * @private - * @param {error} ERROR.KEY value above. - * @param {substitutions} [array] substituted into the text. - * @return the text with the substitutions made. - */ - var format = function(error, substitutions) { - var text = error.text; - if (substitutions) { - var field,start; - for (var i=0; i 0) { - var part1 = text.substring(0,start); - var part2 = text.substring(start+field.length); - text = part1+substitutions[i]+part2; - } - } - } - return text; - }; - - //MQTT protocol and version 6 M Q I s d p 3 - var MqttProtoIdentifierv3 = [0x00,0x06,0x4d,0x51,0x49,0x73,0x64,0x70,0x03]; - //MQTT proto/version for 311 4 M Q T T 4 - var MqttProtoIdentifierv4 = [0x00,0x04,0x4d,0x51,0x54,0x54,0x04]; - - /** - * Construct an MQTT wire protocol message. - * @param type MQTT packet type. - * @param options optional wire message attributes. - * - * Optional properties - * - * messageIdentifier: message ID in the range [0..65535] - * payloadMessage: Application Message - PUBLISH only - * connectStrings: array of 0 or more Strings to be put into the CONNECT payload - * topics: array of strings (SUBSCRIBE, UNSUBSCRIBE) - * requestQoS: array of QoS values [0..2] - * - * "Flag" properties - * cleanSession: true if present / false if absent (CONNECT) - * willMessage: true if present / false if absent (CONNECT) - * isRetained: true if present / false if absent (CONNECT) - * userName: true if present / false if absent (CONNECT) - * password: true if present / false if absent (CONNECT) - * keepAliveInterval: integer [0..65535] (CONNECT) - * - * @private - * @ignore - */ - var WireMessage = function (type, options) { - this.type = type; - for (var name in options) { - if (options.hasOwnProperty(name)) { - this[name] = options[name]; - } - } - }; - - WireMessage.prototype.encode = function() { - // Compute the first byte of the fixed header - var first = ((this.type & 0x0f) << 4); - - /* - * Now calculate the length of the variable header + payload by adding up the lengths - * of all the component parts - */ - - var remLength = 0; - var topicStrLength = new Array(); - var destinationNameLength = 0; - - // if the message contains a messageIdentifier then we need two bytes for that - if (this.messageIdentifier != undefined) - remLength += 2; - - switch(this.type) { - // If this a Connect then we need to include 12 bytes for its header - case MESSAGE_TYPE.CONNECT: - switch(this.mqttVersion) { - case 3: - remLength += MqttProtoIdentifierv3.length + 3; - break; - case 4: - remLength += MqttProtoIdentifierv4.length + 3; - break; - } - - remLength += UTF8Length(this.clientId) + 2; - if (this.willMessage != undefined) { - remLength += UTF8Length(this.willMessage.destinationName) + 2; - // Will message is always a string, sent as UTF-8 characters with a preceding length. - var willMessagePayloadBytes = this.willMessage.payloadBytes; - if (!(willMessagePayloadBytes instanceof Uint8Array)) - willMessagePayloadBytes = new Uint8Array(payloadBytes); - remLength += willMessagePayloadBytes.byteLength +2; - } - if (this.userName != undefined) - remLength += UTF8Length(this.userName) + 2; - if (this.password != undefined) - remLength += UTF8Length(this.password) + 2; - break; - - // Subscribe, Unsubscribe can both contain topic strings - case MESSAGE_TYPE.SUBSCRIBE: - first |= 0x02; // Qos = 1; - for ( var i = 0; i < this.topics.length; i++) { - topicStrLength[i] = UTF8Length(this.topics[i]); - remLength += topicStrLength[i] + 2; - } - remLength += this.requestedQos.length; // 1 byte for each topic's Qos - // QoS on Subscribe only - break; - - case MESSAGE_TYPE.UNSUBSCRIBE: - first |= 0x02; // Qos = 1; - for ( var i = 0; i < this.topics.length; i++) { - topicStrLength[i] = UTF8Length(this.topics[i]); - remLength += topicStrLength[i] + 2; - } - break; - - case MESSAGE_TYPE.PUBREL: - first |= 0x02; // Qos = 1; - break; - - case MESSAGE_TYPE.PUBLISH: - if (this.payloadMessage.duplicate) first |= 0x08; - first = first |= (this.payloadMessage.qos << 1); - if (this.payloadMessage.retained) first |= 0x01; - destinationNameLength = UTF8Length(this.payloadMessage.destinationName); - remLength += destinationNameLength + 2; - var payloadBytes = this.payloadMessage.payloadBytes; - remLength += payloadBytes.byteLength; - if (payloadBytes instanceof ArrayBuffer) - payloadBytes = new Uint8Array(payloadBytes); - else if (!(payloadBytes instanceof Uint8Array)) - payloadBytes = new Uint8Array(payloadBytes.buffer); - break; - - case MESSAGE_TYPE.DISCONNECT: - break; - - default: - ; - } - - // Now we can allocate a buffer for the message - - var mbi = encodeMBI(remLength); // Convert the length to MQTT MBI format - var pos = mbi.length + 1; // Offset of start of variable header - var buffer = new ArrayBuffer(remLength + pos); - var byteStream = new Uint8Array(buffer); // view it as a sequence of bytes - - //Write the fixed header into the buffer - byteStream[0] = first; - byteStream.set(mbi,1); - - // If this is a PUBLISH then the variable header starts with a topic - if (this.type == MESSAGE_TYPE.PUBLISH) - pos = writeString(this.payloadMessage.destinationName, destinationNameLength, byteStream, pos); - // If this is a CONNECT then the variable header contains the protocol name/version, flags and keepalive time - - else if (this.type == MESSAGE_TYPE.CONNECT) { - switch (this.mqttVersion) { - case 3: - byteStream.set(MqttProtoIdentifierv3, pos); - pos += MqttProtoIdentifierv3.length; - break; - case 4: - byteStream.set(MqttProtoIdentifierv4, pos); - pos += MqttProtoIdentifierv4.length; - break; - } - var connectFlags = 0; - if (this.cleanSession) - connectFlags = 0x02; - if (this.willMessage != undefined ) { - connectFlags |= 0x04; - connectFlags |= (this.willMessage.qos<<3); - if (this.willMessage.retained) { - connectFlags |= 0x20; - } - } - if (this.userName != undefined) - connectFlags |= 0x80; - if (this.password != undefined) - connectFlags |= 0x40; - byteStream[pos++] = connectFlags; - pos = writeUint16 (this.keepAliveInterval, byteStream, pos); - } - - // Output the messageIdentifier - if there is one - if (this.messageIdentifier != undefined) - pos = writeUint16 (this.messageIdentifier, byteStream, pos); - - switch(this.type) { - case MESSAGE_TYPE.CONNECT: - pos = writeString(this.clientId, UTF8Length(this.clientId), byteStream, pos); - if (this.willMessage != undefined) { - pos = writeString(this.willMessage.destinationName, UTF8Length(this.willMessage.destinationName), byteStream, pos); - pos = writeUint16(willMessagePayloadBytes.byteLength, byteStream, pos); - byteStream.set(willMessagePayloadBytes, pos); - pos += willMessagePayloadBytes.byteLength; - - } - if (this.userName != undefined) - pos = writeString(this.userName, UTF8Length(this.userName), byteStream, pos); - if (this.password != undefined) - pos = writeString(this.password, UTF8Length(this.password), byteStream, pos); - break; - - case MESSAGE_TYPE.PUBLISH: - // PUBLISH has a text or binary payload, if text do not add a 2 byte length field, just the UTF characters. - byteStream.set(payloadBytes, pos); - - break; - -// case MESSAGE_TYPE.PUBREC: -// case MESSAGE_TYPE.PUBREL: -// case MESSAGE_TYPE.PUBCOMP: -// break; - - case MESSAGE_TYPE.SUBSCRIBE: - // SUBSCRIBE has a list of topic strings and request QoS - for (var i=0; i> 4; - var messageInfo = first &= 0x0f; - pos += 1; - - - // Decode the remaining length (MBI format) - - var digit; - var remLength = 0; - var multiplier = 1; - do { - if (pos == input.length) { - return [null,startingPos]; - } - digit = input[pos++]; - remLength += ((digit & 0x7F) * multiplier); - multiplier *= 128; - } while ((digit & 0x80) != 0); - - var endPos = pos+remLength; - if (endPos > input.length) { - return [null,startingPos]; - } - - var wireMessage = new WireMessage(type); - switch(type) { - case MESSAGE_TYPE.CONNACK: - var connectAcknowledgeFlags = input[pos++]; - if (connectAcknowledgeFlags & 0x01) - wireMessage.sessionPresent = true; - wireMessage.returnCode = input[pos++]; - break; - - case MESSAGE_TYPE.PUBLISH: - var qos = (messageInfo >> 1) & 0x03; - - var len = readUint16(input, pos); - pos += 2; - var topicName = parseUTF8(input, pos, len); - pos += len; - // If QoS 1 or 2 there will be a messageIdentifier - if (qos > 0) { - wireMessage.messageIdentifier = readUint16(input, pos); - pos += 2; - } - - var message = new Paho.MQTT.Message(input.subarray(pos, endPos)); - if ((messageInfo & 0x01) == 0x01) - message.retained = true; - if ((messageInfo & 0x08) == 0x08) - message.duplicate = true; - message.qos = qos; - message.destinationName = topicName; - wireMessage.payloadMessage = message; - break; - - case MESSAGE_TYPE.PUBACK: - case MESSAGE_TYPE.PUBREC: - case MESSAGE_TYPE.PUBREL: - case MESSAGE_TYPE.PUBCOMP: - case MESSAGE_TYPE.UNSUBACK: - wireMessage.messageIdentifier = readUint16(input, pos); - break; - - case MESSAGE_TYPE.SUBACK: - wireMessage.messageIdentifier = readUint16(input, pos); - pos += 2; - wireMessage.returnCode = input.subarray(pos, endPos); - break; - - default: - ; - } - - return [wireMessage,endPos]; - } - - function writeUint16(input, buffer, offset) { - buffer[offset++] = input >> 8; //MSB - buffer[offset++] = input % 256; //LSB - return offset; - } - - function writeString(input, utf8Length, buffer, offset) { - offset = writeUint16(utf8Length, buffer, offset); - stringToUTF8(input, buffer, offset); - return offset + utf8Length; - } - - function readUint16(buffer, offset) { - return 256*buffer[offset] + buffer[offset+1]; - } - - /** - * Encodes an MQTT Multi-Byte Integer - * @private - */ - function encodeMBI(number) { - var output = new Array(1); - var numBytes = 0; - - do { - var digit = number % 128; - number = number >> 7; - if (number > 0) { - digit |= 0x80; - } - output[numBytes++] = digit; - } while ( (number > 0) && (numBytes<4) ); - - return output; - } - - /** - * Takes a String and calculates its length in bytes when encoded in UTF8. - * @private - */ - function UTF8Length(input) { - var output = 0; - for (var i = 0; i 0x7FF) - { - // Surrogate pair means its a 4 byte character - if (0xD800 <= charCode && charCode <= 0xDBFF) - { - i++; - output++; - } - output +=3; - } - else if (charCode > 0x7F) - output +=2; - else - output++; - } - return output; - } - - /** - * Takes a String and writes it into an array as UTF8 encoded bytes. - * @private - */ - function stringToUTF8(input, output, start) { - var pos = start; - for (var i = 0; i>6 & 0x1F | 0xC0; - output[pos++] = charCode & 0x3F | 0x80; - } else if (charCode <= 0xFFFF) { - output[pos++] = charCode>>12 & 0x0F | 0xE0; - output[pos++] = charCode>>6 & 0x3F | 0x80; - output[pos++] = charCode & 0x3F | 0x80; - } else { - output[pos++] = charCode>>18 & 0x07 | 0xF0; - output[pos++] = charCode>>12 & 0x3F | 0x80; - output[pos++] = charCode>>6 & 0x3F | 0x80; - output[pos++] = charCode & 0x3F | 0x80; - }; - } - return output; - } - - function parseUTF8(input, offset, length) { - var output = ""; - var utf16; - var pos = offset; - - while (pos < offset+length) - { - var byte1 = input[pos++]; - if (byte1 < 128) - utf16 = byte1; - else - { - var byte2 = input[pos++]-128; - if (byte2 < 0) - throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16),""])); - if (byte1 < 0xE0) // 2 byte character - utf16 = 64*(byte1-0xC0) + byte2; - else - { - var byte3 = input[pos++]-128; - if (byte3 < 0) - throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16)])); - if (byte1 < 0xF0) // 3 byte character - utf16 = 4096*(byte1-0xE0) + 64*byte2 + byte3; - else - { - var byte4 = input[pos++]-128; - if (byte4 < 0) - throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); - if (byte1 < 0xF8) // 4 byte character - utf16 = 262144*(byte1-0xF0) + 4096*byte2 + 64*byte3 + byte4; - else // longer encodings are not supported - throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); - } - } - } - - if (utf16 > 0xFFFF) // 4 byte character - express as a surrogate pair - { - utf16 -= 0x10000; - output += String.fromCharCode(0xD800 + (utf16 >> 10)); // lead character - utf16 = 0xDC00 + (utf16 & 0x3FF); // trail character - } - output += String.fromCharCode(utf16); - } - return output; - } - - /** - * Repeat keepalive requests, monitor responses. - * @ignore - */ - var Pinger = function(client, window, keepAliveInterval) { - this._client = client; - this._window = window; - this._keepAliveInterval = keepAliveInterval*1000; - this.isReset = false; - - var pingReq = new WireMessage(MESSAGE_TYPE.PINGREQ).encode(); - - var doTimeout = function (pinger) { - return function () { - return doPing.apply(pinger); - }; - }; - - /** @ignore */ - var doPing = function() { - if (!this.isReset) { - this._client._trace("Pinger.doPing", "Timed out"); - this._client._disconnected( ERROR.PING_TIMEOUT.code , format(ERROR.PING_TIMEOUT)); - } else { - this.isReset = false; - this._client._trace("Pinger.doPing", "send PINGREQ"); - this._client.socket.send(pingReq); - this.timeout = this._window.setTimeout(doTimeout(this), this._keepAliveInterval); - } - } - - this.reset = function() { - this.isReset = true; - this._window.clearTimeout(this.timeout); - if (this._keepAliveInterval > 0) - this.timeout = setTimeout(doTimeout(this), this._keepAliveInterval); - } - - this.cancel = function() { - this._window.clearTimeout(this.timeout); - } - }; - - /** - * Monitor request completion. - * @ignore - */ - var Timeout = function(client, window, timeoutSeconds, action, args) { - this._window = window; - if (!timeoutSeconds) - timeoutSeconds = 30; - - var doTimeout = function (action, client, args) { - return function () { - return action.apply(client, args); - }; - }; - this.timeout = setTimeout(doTimeout(action, client, args), timeoutSeconds * 1000); - - this.cancel = function() { - this._window.clearTimeout(this.timeout); - } - }; - - /* - * Internal implementation of the Websockets MQTT V3.1 client. - * - * @name Paho.MQTT.ClientImpl @constructor - * @param {String} host the DNS nameof the webSocket host. - * @param {Number} port the port number for that host. - * @param {String} clientId the MQ client identifier. - */ - var ClientImpl = function (uri, host, port, path, clientId) { - // Check dependencies are satisfied in this browser. - if (!("WebSocket" in global && global["WebSocket"] !== null)) { - throw new Error(format(ERROR.UNSUPPORTED, ["WebSocket"])); - } - if (!("localStorage" in global && global["localStorage"] !== null)) { - throw new Error(format(ERROR.UNSUPPORTED, ["localStorage"])); - } - if (!("ArrayBuffer" in global && global["ArrayBuffer"] !== null)) { - throw new Error(format(ERROR.UNSUPPORTED, ["ArrayBuffer"])); - } - this._trace("Paho.MQTT.Client", uri, host, port, path, clientId); - - this.host = host; - this.port = port; - this.path = path; - this.uri = uri; - this.clientId = clientId; - - // Local storagekeys are qualified with the following string. - // The conditional inclusion of path in the key is for backward - // compatibility to when the path was not configurable and assumed to - // be /mqtt - this._localKey=host+":"+port+(path!="/mqtt"?":"+path:"")+":"+clientId+":"; - - // Create private instance-only message queue - // Internal queue of messages to be sent, in sending order. - this._msg_queue = []; - - // Messages we have sent and are expecting a response for, indexed by their respective message ids. - this._sentMessages = {}; - - // Messages we have received and acknowleged and are expecting a confirm message for - // indexed by their respective message ids. - this._receivedMessages = {}; - - // Internal list of callbacks to be executed when messages - // have been successfully sent over web socket, e.g. disconnect - // when it doesn't have to wait for ACK, just message is dispatched. - this._notify_msg_sent = {}; - - // Unique identifier for SEND messages, incrementing - // counter as messages are sent. - this._message_identifier = 1; - - // Used to determine the transmission sequence of stored sent messages. - this._sequence = 0; - - - // Load the local state, if any, from the saved version, only restore state relevant to this client. - for (var key in localStorage) - if ( key.indexOf("Sent:"+this._localKey) == 0 - || key.indexOf("Received:"+this._localKey) == 0) - this.restore(key); - }; - - // Messaging Client public instance members. - ClientImpl.prototype.host; - ClientImpl.prototype.port; - ClientImpl.prototype.path; - ClientImpl.prototype.uri; - ClientImpl.prototype.clientId; - - // Messaging Client private instance members. - ClientImpl.prototype.socket; - /* true once we have received an acknowledgement to a CONNECT packet. */ - ClientImpl.prototype.connected = false; - /* The largest message identifier allowed, may not be larger than 2**16 but - * if set smaller reduces the maximum number of outbound messages allowed. - */ - ClientImpl.prototype.maxMessageIdentifier = 65536; - ClientImpl.prototype.connectOptions; - ClientImpl.prototype.hostIndex; - ClientImpl.prototype.onConnectionLost; - ClientImpl.prototype.onMessageDelivered; - ClientImpl.prototype.onMessageArrived; - ClientImpl.prototype.traceFunction; - ClientImpl.prototype._msg_queue = null; - ClientImpl.prototype._connectTimeout; - /* The sendPinger monitors how long we allow before we send data to prove to the server that we are alive. */ - ClientImpl.prototype.sendPinger = null; - /* The receivePinger monitors how long we allow before we require evidence that the server is alive. */ - ClientImpl.prototype.receivePinger = null; - - ClientImpl.prototype.receiveBuffer = null; - - ClientImpl.prototype._traceBuffer = null; - ClientImpl.prototype._MAX_TRACE_ENTRIES = 100; - - ClientImpl.prototype.connect = function (connectOptions) { - var connectOptionsMasked = this._traceMask(connectOptions, "password"); - this._trace("Client.connect", connectOptionsMasked, this.socket, this.connected); - - if (this.connected) - throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); - if (this.socket) - throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); - - this.connectOptions = connectOptions; - - if (connectOptions.uris) { - this.hostIndex = 0; - this._doConnect(connectOptions.uris[0]); - } else { - this._doConnect(this.uri); - } - - }; - - ClientImpl.prototype.subscribe = function (filter, subscribeOptions) { - this._trace("Client.subscribe", filter, subscribeOptions); - - if (!this.connected) - throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); - - var wireMessage = new WireMessage(MESSAGE_TYPE.SUBSCRIBE); - wireMessage.topics=[filter]; - if (subscribeOptions.qos != undefined) - wireMessage.requestedQos = [subscribeOptions.qos]; - else - wireMessage.requestedQos = [0]; - - if (subscribeOptions.onSuccess) { - wireMessage.onSuccess = function(grantedQos) {subscribeOptions.onSuccess({invocationContext:subscribeOptions.invocationContext,grantedQos:grantedQos});}; - } - - if (subscribeOptions.onFailure) { - wireMessage.onFailure = function(errorCode) {subscribeOptions.onFailure({invocationContext:subscribeOptions.invocationContext,errorCode:errorCode});}; - } - - if (subscribeOptions.timeout) { - wireMessage.timeOut = new Timeout(this, window, subscribeOptions.timeout, subscribeOptions.onFailure - , [{invocationContext:subscribeOptions.invocationContext, - errorCode:ERROR.SUBSCRIBE_TIMEOUT.code, - errorMessage:format(ERROR.SUBSCRIBE_TIMEOUT)}]); - } - - // All subscriptions return a SUBACK. - this._requires_ack(wireMessage); - this._schedule_message(wireMessage); - }; - - /** @ignore */ - ClientImpl.prototype.unsubscribe = function(filter, unsubscribeOptions) { - this._trace("Client.unsubscribe", filter, unsubscribeOptions); - - if (!this.connected) - throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); - - var wireMessage = new WireMessage(MESSAGE_TYPE.UNSUBSCRIBE); - wireMessage.topics = [filter]; - - if (unsubscribeOptions.onSuccess) { - wireMessage.callback = function() {unsubscribeOptions.onSuccess({invocationContext:unsubscribeOptions.invocationContext});}; - } - if (unsubscribeOptions.timeout) { - wireMessage.timeOut = new Timeout(this, window, unsubscribeOptions.timeout, unsubscribeOptions.onFailure - , [{invocationContext:unsubscribeOptions.invocationContext, - errorCode:ERROR.UNSUBSCRIBE_TIMEOUT.code, - errorMessage:format(ERROR.UNSUBSCRIBE_TIMEOUT)}]); - } - - // All unsubscribes return a SUBACK. - this._requires_ack(wireMessage); - this._schedule_message(wireMessage); - }; - - ClientImpl.prototype.send = function (message) { - this._trace("Client.send", message); - - if (!this.connected) - throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); - - wireMessage = new WireMessage(MESSAGE_TYPE.PUBLISH); - wireMessage.payloadMessage = message; - - if (message.qos > 0) - this._requires_ack(wireMessage); - else if (this.onMessageDelivered) - this._notify_msg_sent[wireMessage] = this.onMessageDelivered(wireMessage.payloadMessage); - this._schedule_message(wireMessage); - }; - - ClientImpl.prototype.disconnect = function () { - this._trace("Client.disconnect"); - - if (!this.socket) - throw new Error(format(ERROR.INVALID_STATE, ["not connecting or connected"])); - - wireMessage = new WireMessage(MESSAGE_TYPE.DISCONNECT); - - // Run the disconnected call back as soon as the message has been sent, - // in case of a failure later on in the disconnect processing. - // as a consequence, the _disconected call back may be run several times. - this._notify_msg_sent[wireMessage] = scope(this._disconnected, this); - - this._schedule_message(wireMessage); - }; - - ClientImpl.prototype.getTraceLog = function () { - if ( this._traceBuffer !== null ) { - this._trace("Client.getTraceLog", new Date()); - this._trace("Client.getTraceLog in flight messages", this._sentMessages.length); - for (var key in this._sentMessages) - this._trace("_sentMessages ",key, this._sentMessages[key]); - for (var key in this._receivedMessages) - this._trace("_receivedMessages ",key, this._receivedMessages[key]); - - return this._traceBuffer; - } - }; - - ClientImpl.prototype.startTrace = function () { - if ( this._traceBuffer === null ) { - this._traceBuffer = []; - } - this._trace("Client.startTrace", new Date(), version); - }; - - ClientImpl.prototype.stopTrace = function () { - delete this._traceBuffer; - }; - - ClientImpl.prototype._doConnect = function (wsurl) { - // When the socket is open, this client will send the CONNECT WireMessage using the saved parameters. - if (this.connectOptions.useSSL) { - var uriParts = wsurl.split(":"); - uriParts[0] = "wss"; - wsurl = uriParts.join(":"); - } - this.connected = false; - if (this.connectOptions.mqttVersion < 4) { - this.socket = new WebSocket(wsurl, ["mqttv3.1"]); - } else { - this.socket = new WebSocket(wsurl, ["mqtt"]); - } - this.socket.binaryType = 'arraybuffer'; - - this.socket.onopen = scope(this._on_socket_open, this); - this.socket.onmessage = scope(this._on_socket_message, this); - this.socket.onerror = scope(this._on_socket_error, this); - this.socket.onclose = scope(this._on_socket_close, this); - - this.sendPinger = new Pinger(this, window, this.connectOptions.keepAliveInterval); - this.receivePinger = new Pinger(this, window, this.connectOptions.keepAliveInterval); - - this._connectTimeout = new Timeout(this, window, this.connectOptions.timeout, this._disconnected, [ERROR.CONNECT_TIMEOUT.code, format(ERROR.CONNECT_TIMEOUT)]); - }; - - - // Schedule a new message to be sent over the WebSockets - // connection. CONNECT messages cause WebSocket connection - // to be started. All other messages are queued internally - // until this has happened. When WS connection starts, process - // all outstanding messages. - ClientImpl.prototype._schedule_message = function (message) { - this._msg_queue.push(message); - // Process outstanding messages in the queue if we have an open socket, and have received CONNACK. - if (this.connected) { - this._process_queue(); - } - }; - - ClientImpl.prototype.store = function(prefix, wireMessage) { - var storedMessage = {type:wireMessage.type, messageIdentifier:wireMessage.messageIdentifier, version:1}; - - switch(wireMessage.type) { - case MESSAGE_TYPE.PUBLISH: - if(wireMessage.pubRecReceived) - storedMessage.pubRecReceived = true; - - // Convert the payload to a hex string. - storedMessage.payloadMessage = {}; - var hex = ""; - var messageBytes = wireMessage.payloadMessage.payloadBytes; - for (var i=0; i= 2) { - var x = parseInt(hex.substring(0, 2), 16); - hex = hex.substring(2, hex.length); - byteStream[i++] = x; - } - var payloadMessage = new Paho.MQTT.Message(byteStream); - - payloadMessage.qos = storedMessage.payloadMessage.qos; - payloadMessage.destinationName = storedMessage.payloadMessage.destinationName; - if (storedMessage.payloadMessage.duplicate) - payloadMessage.duplicate = true; - if (storedMessage.payloadMessage.retained) - payloadMessage.retained = true; - wireMessage.payloadMessage = payloadMessage; - - break; - - default: - throw Error(format(ERROR.INVALID_STORED_DATA, [key, value])); - } - - if (key.indexOf("Sent:"+this._localKey) == 0) { - wireMessage.payloadMessage.duplicate = true; - this._sentMessages[wireMessage.messageIdentifier] = wireMessage; - } else if (key.indexOf("Received:"+this._localKey) == 0) { - this._receivedMessages[wireMessage.messageIdentifier] = wireMessage; - } - }; - - ClientImpl.prototype._process_queue = function () { - var message = null; - // Process messages in order they were added - var fifo = this._msg_queue.reverse(); - - // Send all queued messages down socket connection - while ((message = fifo.pop())) { - this._socket_send(message); - // Notify listeners that message was successfully sent - if (this._notify_msg_sent[message]) { - this._notify_msg_sent[message](); - delete this._notify_msg_sent[message]; - } - } - }; - - /** - * Expect an ACK response for this message. Add message to the set of in progress - * messages and set an unused identifier in this message. - * @ignore - */ - ClientImpl.prototype._requires_ack = function (wireMessage) { - var messageCount = Object.keys(this._sentMessages).length; - if (messageCount > this.maxMessageIdentifier) - throw Error ("Too many messages:"+messageCount); - - while(this._sentMessages[this._message_identifier] !== undefined) { - this._message_identifier++; - } - wireMessage.messageIdentifier = this._message_identifier; - this._sentMessages[wireMessage.messageIdentifier] = wireMessage; - if (wireMessage.type === MESSAGE_TYPE.PUBLISH) { - this.store("Sent:", wireMessage); - } - if (this._message_identifier === this.maxMessageIdentifier) { - this._message_identifier = 1; - } - }; - - /** - * Called when the underlying websocket has been opened. - * @ignore - */ - ClientImpl.prototype._on_socket_open = function () { - // Create the CONNECT message object. - var wireMessage = new WireMessage(MESSAGE_TYPE.CONNECT, this.connectOptions); - wireMessage.clientId = this.clientId; - this._socket_send(wireMessage); - }; - - /** - * Called when the underlying websocket has received a complete packet. - * @ignore - */ - ClientImpl.prototype._on_socket_message = function (event) { - this._trace("Client._on_socket_message", event.data); - // Reset the receive ping timer, we now have evidence the server is alive. - this.receivePinger.reset(); - var messages = this._deframeMessages(event.data); - for (var i = 0; i < messages.length; i+=1) { - this._handleMessage(messages[i]); - } - } - - ClientImpl.prototype._deframeMessages = function(data) { - var byteArray = new Uint8Array(data); - if (this.receiveBuffer) { - var newData = new Uint8Array(this.receiveBuffer.length+byteArray.length); - newData.set(this.receiveBuffer); - newData.set(byteArray,this.receiveBuffer.length); - byteArray = newData; - delete this.receiveBuffer; - } - try { - var offset = 0; - var messages = []; - while(offset < byteArray.length) { - var result = decodeMessage(byteArray,offset); - var wireMessage = result[0]; - offset = result[1]; - if (wireMessage !== null) { - messages.push(wireMessage); - } else { - break; - } - } - if (offset < byteArray.length) { - this.receiveBuffer = byteArray.subarray(offset); - } - } catch (error) { - this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()])); - return; - } - return messages; - } - - ClientImpl.prototype._handleMessage = function(wireMessage) { - - this._trace("Client._handleMessage", wireMessage); - - try { - switch(wireMessage.type) { - case MESSAGE_TYPE.CONNACK: - this._connectTimeout.cancel(); - - // If we have started using clean session then clear up the local state. - if (this.connectOptions.cleanSession) { - for (var key in this._sentMessages) { - var sentMessage = this._sentMessages[key]; - localStorage.removeItem("Sent:"+this._localKey+sentMessage.messageIdentifier); - } - this._sentMessages = {}; - - for (var key in this._receivedMessages) { - var receivedMessage = this._receivedMessages[key]; - localStorage.removeItem("Received:"+this._localKey+receivedMessage.messageIdentifier); - } - this._receivedMessages = {}; - } - // Client connected and ready for business. - if (wireMessage.returnCode === 0) { - this.connected = true; - // Jump to the end of the list of uris and stop looking for a good host. - if (this.connectOptions.uris) - this.hostIndex = this.connectOptions.uris.length; - } else { - this._disconnected(ERROR.CONNACK_RETURNCODE.code , format(ERROR.CONNACK_RETURNCODE, [wireMessage.returnCode, CONNACK_RC[wireMessage.returnCode]])); - break; - } - - // Resend messages. - var sequencedMessages = new Array(); - for (var msgId in this._sentMessages) { - if (this._sentMessages.hasOwnProperty(msgId)) - sequencedMessages.push(this._sentMessages[msgId]); - } - - // Sort sentMessages into the original sent order. - var sequencedMessages = sequencedMessages.sort(function(a,b) {return a.sequence - b.sequence;} ); - for (var i=0, len=sequencedMessages.length; i - * Most applications will create just one Client object and then call its connect() method, - * however applications can create more than one Client object if they wish. - * In this case the combination of host, port and clientId attributes must be different for each Client object. - *

- * The send, subscribe and unsubscribe methods are implemented as asynchronous JavaScript methods - * (even though the underlying protocol exchange might be synchronous in nature). - * This means they signal their completion by calling back to the application, - * via Success or Failure callback functions provided by the application on the method in question. - * Such callbacks are called at most once per method invocation and do not persist beyond the lifetime - * of the script that made the invocation. - *

- * In contrast there are some callback functions, most notably onMessageArrived, - * that are defined on the {@link Paho.MQTT.Client} object. - * These may get called multiple times, and aren't directly related to specific method invocations made by the client. - * - * @name Paho.MQTT.Client - * - * @constructor - * - * @param {string} host - the address of the messaging server, as a fully qualified WebSocket URI, as a DNS name or dotted decimal IP address. - * @param {number} port - the port number to connect to - only required if host is not a URI - * @param {string} path - the path on the host to connect to - only used if host is not a URI. Default: '/mqtt'. - * @param {string} clientId - the Messaging client identifier, between 1 and 23 characters in length. - * - * @property {string} host - read only the server's DNS hostname or dotted decimal IP address. - * @property {number} port - read only the server's port. - * @property {string} path - read only the server's path. - * @property {string} clientId - read only used when connecting to the server. - * @property {function} onConnectionLost - called when a connection has been lost. - * after a connect() method has succeeded. - * Establish the call back used when a connection has been lost. The connection may be - * lost because the client initiates a disconnect or because the server or network - * cause the client to be disconnected. The disconnect call back may be called without - * the connectionComplete call back being invoked if, for example the client fails to - * connect. - * A single response object parameter is passed to the onConnectionLost callback containing the following fields: - *

    - *
  1. errorCode - *
  2. errorMessage - *
- * @property {function} onMessageDelivered called when a message has been delivered. - * All processing that this Client will ever do has been completed. So, for example, - * in the case of a Qos=2 message sent by this client, the PubComp flow has been received from the server - * and the message has been removed from persistent storage before this callback is invoked. - * Parameters passed to the onMessageDelivered callback are: - *
    - *
  1. {@link Paho.MQTT.Message} that was delivered. - *
- * @property {function} onMessageArrived called when a message has arrived in this Paho.MQTT.client. - * Parameters passed to the onMessageArrived callback are: - *
    - *
  1. {@link Paho.MQTT.Message} that has arrived. - *
- */ - var Client = function (host, port, path, clientId) { - - var uri; - - if (typeof host !== "string") - throw new Error(format(ERROR.INVALID_TYPE, [typeof host, "host"])); - - if (arguments.length == 2) { - // host: must be full ws:// uri - // port: clientId - clientId = port; - uri = host; - var match = uri.match(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/); - if (match) { - host = match[4]||match[2]; - port = parseInt(match[7]); - path = match[8]; - } else { - throw new Error(format(ERROR.INVALID_ARGUMENT,[host,"host"])); - } - } else { - if (arguments.length == 3) { - clientId = path; - path = "/mqtt"; - } - if (typeof port !== "number" || port < 0) - throw new Error(format(ERROR.INVALID_TYPE, [typeof port, "port"])); - if (typeof path !== "string") - throw new Error(format(ERROR.INVALID_TYPE, [typeof path, "path"])); - - var ipv6AddSBracket = (host.indexOf(":") != -1 && host.slice(0,1) != "[" && host.slice(-1) != "]"); - uri = "ws://"+(ipv6AddSBracket?"["+host+"]":host)+":"+port+path; - } - - var clientIdLength = 0; - for (var i = 0; i 65535) - throw new Error(format(ERROR.INVALID_ARGUMENT, [clientId, "clientId"])); - - var client = new ClientImpl(uri, host, port, path, clientId); - this._getHost = function() { return host; }; - this._setHost = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - - this._getPort = function() { return port; }; - this._setPort = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - - this._getPath = function() { return path; }; - this._setPath = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - - this._getURI = function() { return uri; }; - this._setURI = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - - this._getClientId = function() { return client.clientId; }; - this._setClientId = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - - this._getOnConnectionLost = function() { return client.onConnectionLost; }; - this._setOnConnectionLost = function(newOnConnectionLost) { - if (typeof newOnConnectionLost === "function") - client.onConnectionLost = newOnConnectionLost; - else - throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnectionLost, "onConnectionLost"])); - }; - - this._getOnMessageDelivered = function() { return client.onMessageDelivered; }; - this._setOnMessageDelivered = function(newOnMessageDelivered) { - if (typeof newOnMessageDelivered === "function") - client.onMessageDelivered = newOnMessageDelivered; - else - throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageDelivered, "onMessageDelivered"])); - }; - - this._getOnMessageArrived = function() { return client.onMessageArrived; }; - this._setOnMessageArrived = function(newOnMessageArrived) { - if (typeof newOnMessageArrived === "function") - client.onMessageArrived = newOnMessageArrived; - else - throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageArrived, "onMessageArrived"])); - }; - - this._getTrace = function() { return client.traceFunction; }; - this._setTrace = function(trace) { - if(typeof trace === "function"){ - client.traceFunction = trace; - }else{ - throw new Error(format(ERROR.INVALID_TYPE, [typeof trace, "onTrace"])); - } - }; - - /** - * Connect this Messaging client to its server. - * - * @name Paho.MQTT.Client#connect - * @function - * @param {Object} connectOptions - attributes used with the connection. - * @param {number} connectOptions.timeout - If the connect has not succeeded within this - * number of seconds, it is deemed to have failed. - * The default is 30 seconds. - * @param {string} connectOptions.userName - Authentication username for this connection. - * @param {string} connectOptions.password - Authentication password for this connection. - * @param {Paho.MQTT.Message} connectOptions.willMessage - sent by the server when the client - * disconnects abnormally. - * @param {Number} connectOptions.keepAliveInterval - the server disconnects this client if - * there is no activity for this number of seconds. - * The default value of 60 seconds is assumed if not set. - * @param {boolean} connectOptions.cleanSession - if true(default) the client and server - * persistent state is deleted on successful connect. - * @param {boolean} connectOptions.useSSL - if present and true, use an SSL Websocket connection. - * @param {object} connectOptions.invocationContext - passed to the onSuccess callback or onFailure callback. - * @param {function} connectOptions.onSuccess - called when the connect acknowledgement - * has been received from the server. - * A single response object parameter is passed to the onSuccess callback containing the following fields: - *
    - *
  1. invocationContext as passed in to the onSuccess method in the connectOptions. - *
- * @config {function} [onFailure] called when the connect request has failed or timed out. - * A single response object parameter is passed to the onFailure callback containing the following fields: - *
    - *
  1. invocationContext as passed in to the onFailure method in the connectOptions. - *
  2. errorCode a number indicating the nature of the error. - *
  3. errorMessage text describing the error. - *
- * @config {Array} [hosts] If present this contains either a set of hostnames or fully qualified - * WebSocket URIs (ws://example.com:1883/mqtt), that are tried in order in place - * of the host and port paramater on the construtor. The hosts are tried one at at time in order until - * one of then succeeds. - * @config {Array} [ports] If present the set of ports matching the hosts. If hosts contains URIs, this property - * is not used. - * @throws {InvalidState} if the client is not in disconnected state. The client must have received connectionLost - * or disconnected before calling connect for a second or subsequent time. - */ - this.connect = function (connectOptions) { - connectOptions = connectOptions || {} ; - validate(connectOptions, {timeout:"number", - userName:"string", - password:"string", - willMessage:"object", - keepAliveInterval:"number", - cleanSession:"boolean", - useSSL:"boolean", - invocationContext:"object", - onSuccess:"function", - onFailure:"function", - hosts:"object", - ports:"object", - mqttVersion:"number"}); - - // If no keep alive interval is set, assume 60 seconds. - if (connectOptions.keepAliveInterval === undefined) - connectOptions.keepAliveInterval = 60; - - if (connectOptions.mqttVersion > 4 || connectOptions.mqttVersion < 3) { - throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.mqttVersion, "connectOptions.mqttVersion"])); - } - - if (connectOptions.mqttVersion === undefined) { - connectOptions.mqttVersionExplicit = false; - connectOptions.mqttVersion = 4; - } else { - connectOptions.mqttVersionExplicit = true; - } - - //Check that if password is set, so is username - if (connectOptions.password === undefined && connectOptions.userName !== undefined) - throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.password, "connectOptions.password"])) - - if (connectOptions.willMessage) { - if (!(connectOptions.willMessage instanceof Message)) - throw new Error(format(ERROR.INVALID_TYPE, [connectOptions.willMessage, "connectOptions.willMessage"])); - // The will message must have a payload that can be represented as a string. - // Cause the willMessage to throw an exception if this is not the case. - connectOptions.willMessage.stringPayload; - - if (typeof connectOptions.willMessage.destinationName === "undefined") - throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.willMessage.destinationName, "connectOptions.willMessage.destinationName"])); - } - if (typeof connectOptions.cleanSession === "undefined") - connectOptions.cleanSession = true; - if (connectOptions.hosts) { - - if (!(connectOptions.hosts instanceof Array) ) - throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"])); - if (connectOptions.hosts.length <1 ) - throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"])); - - var usingURIs = false; - for (var i = 0; i - * @param {object} subscribeOptions - used to control the subscription - * - * @param {number} subscribeOptions.qos - the maiximum qos of any publications sent - * as a result of making this subscription. - * @param {object} subscribeOptions.invocationContext - passed to the onSuccess callback - * or onFailure callback. - * @param {function} subscribeOptions.onSuccess - called when the subscribe acknowledgement - * has been received from the server. - * A single response object parameter is passed to the onSuccess callback containing the following fields: - *
    - *
  1. invocationContext if set in the subscribeOptions. - *
- * @param {function} subscribeOptions.onFailure - called when the subscribe request has failed or timed out. - * A single response object parameter is passed to the onFailure callback containing the following fields: - *
    - *
  1. invocationContext - if set in the subscribeOptions. - *
  2. errorCode - a number indicating the nature of the error. - *
  3. errorMessage - text describing the error. - *
- * @param {number} subscribeOptions.timeout - which, if present, determines the number of - * seconds after which the onFailure calback is called. - * The presence of a timeout does not prevent the onSuccess - * callback from being called when the subscribe completes. - * @throws {InvalidState} if the client is not in connected state. - */ - this.subscribe = function (filter, subscribeOptions) { - if (typeof filter !== "string") - throw new Error("Invalid argument:"+filter); - subscribeOptions = subscribeOptions || {} ; - validate(subscribeOptions, {qos:"number", - invocationContext:"object", - onSuccess:"function", - onFailure:"function", - timeout:"number" - }); - if (subscribeOptions.timeout && !subscribeOptions.onFailure) - throw new Error("subscribeOptions.timeout specified with no onFailure callback."); - if (typeof subscribeOptions.qos !== "undefined" - && !(subscribeOptions.qos === 0 || subscribeOptions.qos === 1 || subscribeOptions.qos === 2 )) - throw new Error(format(ERROR.INVALID_ARGUMENT, [subscribeOptions.qos, "subscribeOptions.qos"])); - client.subscribe(filter, subscribeOptions); - }; - - /** - * Unsubscribe for messages, stop receiving messages sent to destinations described by the filter. - * - * @name Paho.MQTT.Client#unsubscribe - * @function - * @param {string} filter - describing the destinations to receive messages from. - * @param {object} unsubscribeOptions - used to control the subscription - * @param {object} unsubscribeOptions.invocationContext - passed to the onSuccess callback - or onFailure callback. - * @param {function} unsubscribeOptions.onSuccess - called when the unsubscribe acknowledgement has been received from the server. - * A single response object parameter is passed to the - * onSuccess callback containing the following fields: - *
    - *
  1. invocationContext - if set in the unsubscribeOptions. - *
- * @param {function} unsubscribeOptions.onFailure called when the unsubscribe request has failed or timed out. - * A single response object parameter is passed to the onFailure callback containing the following fields: - *
    - *
  1. invocationContext - if set in the unsubscribeOptions. - *
  2. errorCode - a number indicating the nature of the error. - *
  3. errorMessage - text describing the error. - *
- * @param {number} unsubscribeOptions.timeout - which, if present, determines the number of seconds - * after which the onFailure callback is called. The presence of - * a timeout does not prevent the onSuccess callback from being - * called when the unsubscribe completes - * @throws {InvalidState} if the client is not in connected state. - */ - this.unsubscribe = function (filter, unsubscribeOptions) { - if (typeof filter !== "string") - throw new Error("Invalid argument:"+filter); - unsubscribeOptions = unsubscribeOptions || {} ; - validate(unsubscribeOptions, {invocationContext:"object", - onSuccess:"function", - onFailure:"function", - timeout:"number" - }); - if (unsubscribeOptions.timeout && !unsubscribeOptions.onFailure) - throw new Error("unsubscribeOptions.timeout specified with no onFailure callback."); - client.unsubscribe(filter, unsubscribeOptions); - }; - - /** - * Send a message to the consumers of the destination in the Message. - * - * @name Paho.MQTT.Client#send - * @function - * @param {string|Paho.MQTT.Message} topic - mandatory The name of the destination to which the message is to be sent. - * - If it is the only parameter, used as Paho.MQTT.Message object. - * @param {String|ArrayBuffer} payload - The message data to be sent. - * @param {number} qos The Quality of Service used to deliver the message. - *
- *
0 Best effort (default). - *
1 At least once. - *
2 Exactly once. - *
- * @param {Boolean} retained If true, the message is to be retained by the server and delivered - * to both current and future subscriptions. - * If false the server only delivers the message to current subscribers, this is the default for new Messages. - * A received message has the retained boolean set to true if the message was published - * with the retained boolean set to true - * and the subscrption was made after the message has been published. - * @throws {InvalidState} if the client is not connected. - */ - this.send = function (topic,payload,qos,retained) { - var message ; - - if(arguments.length == 0){ - throw new Error("Invalid argument."+"length"); - - }else if(arguments.length == 1) { - - if (!(topic instanceof Message) && (typeof topic !== "string")) - throw new Error("Invalid argument:"+ typeof topic); - - message = topic; - if (typeof message.destinationName === "undefined") - throw new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,"Message.destinationName"])); - client.send(message); - - }else { - //parameter checking in Message object - message = new Message(payload); - message.destinationName = topic; - if(arguments.length >= 3) - message.qos = qos; - if(arguments.length >= 4) - message.retained = retained; - client.send(message); - } - }; - - /** - * Normal disconnect of this Messaging client from its server. - * - * @name Paho.MQTT.Client#disconnect - * @function - * @throws {InvalidState} if the client is already disconnected. - */ - this.disconnect = function () { - client.disconnect(); - }; - - /** - * Get the contents of the trace log. - * - * @name Paho.MQTT.Client#getTraceLog - * @function - * @return {Object[]} tracebuffer containing the time ordered trace records. - */ - this.getTraceLog = function () { - return client.getTraceLog(); - } - - /** - * Start tracing. - * - * @name Paho.MQTT.Client#startTrace - * @function - */ - this.startTrace = function () { - client.startTrace(); - }; - - /** - * Stop tracing. - * - * @name Paho.MQTT.Client#stopTrace - * @function - */ - this.stopTrace = function () { - client.stopTrace(); - }; - - this.isConnected = function() { - return client.connected; - }; - }; - - Client.prototype = { - get host() { return this._getHost(); }, - set host(newHost) { this._setHost(newHost); }, - - get port() { return this._getPort(); }, - set port(newPort) { this._setPort(newPort); }, - - get path() { return this._getPath(); }, - set path(newPath) { this._setPath(newPath); }, - - get clientId() { return this._getClientId(); }, - set clientId(newClientId) { this._setClientId(newClientId); }, - - get onConnectionLost() { return this._getOnConnectionLost(); }, - set onConnectionLost(newOnConnectionLost) { this._setOnConnectionLost(newOnConnectionLost); }, - - get onMessageDelivered() { return this._getOnMessageDelivered(); }, - set onMessageDelivered(newOnMessageDelivered) { this._setOnMessageDelivered(newOnMessageDelivered); }, - - get onMessageArrived() { return this._getOnMessageArrived(); }, - set onMessageArrived(newOnMessageArrived) { this._setOnMessageArrived(newOnMessageArrived); }, - - get trace() { return this._getTrace(); }, - set trace(newTraceFunction) { this._setTrace(newTraceFunction); } - - }; - - /** - * An application message, sent or received. - *

- * All attributes may be null, which implies the default values. - * - * @name Paho.MQTT.Message - * @constructor - * @param {String|ArrayBuffer} payload The message data to be sent. - *

- * @property {string} payloadString read only The payload as a string if the payload consists of valid UTF-8 characters. - * @property {ArrayBuffer} payloadBytes read only The payload as an ArrayBuffer. - *

- * @property {string} destinationName mandatory The name of the destination to which the message is to be sent - * (for messages about to be sent) or the name of the destination from which the message has been received. - * (for messages received by the onMessage function). - *

- * @property {number} qos The Quality of Service used to deliver the message. - *

- *
0 Best effort (default). - *
1 At least once. - *
2 Exactly once. - *
- *

- * @property {Boolean} retained If true, the message is to be retained by the server and delivered - * to both current and future subscriptions. - * If false the server only delivers the message to current subscribers, this is the default for new Messages. - * A received message has the retained boolean set to true if the message was published - * with the retained boolean set to true - * and the subscrption was made after the message has been published. - *

- * @property {Boolean} duplicate read only If true, this message might be a duplicate of one which has already been received. - * This is only set on messages received from the server. - * - */ - var Message = function (newPayload) { - var payload; - if ( typeof newPayload === "string" - || newPayload instanceof ArrayBuffer - || newPayload instanceof Int8Array - || newPayload instanceof Uint8Array - || newPayload instanceof Int16Array - || newPayload instanceof Uint16Array - || newPayload instanceof Int32Array - || newPayload instanceof Uint32Array - || newPayload instanceof Float32Array - || newPayload instanceof Float64Array - ) { - payload = newPayload; - } else { - throw (format(ERROR.INVALID_ARGUMENT, [newPayload, "newPayload"])); - } - - this._getPayloadString = function () { - if (typeof payload === "string") - return payload; - else - return parseUTF8(payload, 0, payload.length); - }; - - this._getPayloadBytes = function() { - if (typeof payload === "string") { - var buffer = new ArrayBuffer(UTF8Length(payload)); - var byteStream = new Uint8Array(buffer); - stringToUTF8(payload, byteStream, 0); - - return byteStream; - } else { - return payload; - }; - }; - - var destinationName = undefined; - this._getDestinationName = function() { return destinationName; }; - this._setDestinationName = function(newDestinationName) { - if (typeof newDestinationName === "string") - destinationName = newDestinationName; - else - throw new Error(format(ERROR.INVALID_ARGUMENT, [newDestinationName, "newDestinationName"])); - }; - - var qos = 0; - this._getQos = function() { return qos; }; - this._setQos = function(newQos) { - if (newQos === 0 || newQos === 1 || newQos === 2 ) - qos = newQos; - else - throw new Error("Invalid argument:"+newQos); - }; - - var retained = false; - this._getRetained = function() { return retained; }; - this._setRetained = function(newRetained) { - if (typeof newRetained === "boolean") - retained = newRetained; - else - throw new Error(format(ERROR.INVALID_ARGUMENT, [newRetained, "newRetained"])); - }; - - var duplicate = false; - this._getDuplicate = function() { return duplicate; }; - this._setDuplicate = function(newDuplicate) { duplicate = newDuplicate; }; - }; - - Message.prototype = { - get payloadString() { return this._getPayloadString(); }, - get payloadBytes() { return this._getPayloadBytes(); }, - - get destinationName() { return this._getDestinationName(); }, - set destinationName(newDestinationName) { this._setDestinationName(newDestinationName); }, - - get qos() { return this._getQos(); }, - set qos(newQos) { this._setQos(newQos); }, - - get retained() { return this._getRetained(); }, - set retained(newRetained) { this._setRetained(newRetained); }, - - get duplicate() { return this._getDuplicate(); }, - set duplicate(newDuplicate) { this._setDuplicate(newDuplicate); } - }; - - // Module contents. - return { - Client: Client, - Message: Message - }; -})(window); -module.exports=Paho.MQTT; -},{}],4:[function(require,module,exports){ -/** - * Application-level APIs for nutella, browser version - */ - -// Require various sub-modules -var AppNetSubModule = require('./app_net'); -var AppLogSubModule = require('./app_log'); - - -var AppSubModule = function(main_nutella) { - // Initialized the various sub-modules - this.net = new AppNetSubModule(main_nutella); - this.log = new AppLogSubModule(main_nutella); -}; - - -module.exports = AppSubModule; -},{"./app_log":5,"./app_net":6}],5:[function(require,module,exports){ -/** - * App-level log APIs for nutella - */ - -var AppNetSubModule = require('./app_net'); - -var AppLogSubModule = function(main_nutella) { - this.net = new AppNetSubModule(main_nutella); -}; - - - -AppLogSubModule.prototype.debug = function(message, code) { - console.debug(message); - this.net.publish('logging', logToJson(message, code, 'debug')); - return code; -}; - -AppLogSubModule.prototype.info = function(message, code) { - console.info(message); - this.net.publish('logging', logToJson(message, code, 'info')); - return code; -}; - -AppLogSubModule.prototype.success = function(message, code) { - console.log('%c '+ message , 'color: #009933'); - this.net.publish('logging', logToJson(message, code, 'success')); - return code; -}; - -AppLogSubModule.prototype.warn = function(message, code) { - console.warn(message); - this.net.publish('logging', logToJson(message, code, 'warn')); - return code; -}; - -AppLogSubModule.prototype.error = function(message, code) { - console.error(message); - this.net.publish('logging', logToJson(message, code, 'error')); - return code; -}; - - -function logToJson( message, code, level) { - return (code === undefined) ? {level: level, message: message} : {level: level, message: message, code: code}; -} - - - -module.exports = AppLogSubModule; - -},{"./app_net":6}],6:[function(require,module,exports){ -/** - * App-level Networking APIs for nutella - */ - - -var AbstractNet = require('./util/net'); - - -/** - * App-level network APIs for nutella - * @param main_nutella - * @constructor - */ -var AppNetSubModule = function(main_nutella) { - this.net = new AbstractNet(main_nutella); -}; - - - -/** - * Subscribes to a channel or filter. - * - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.subscribe = function(channel, callback, done_callback) { - this.net.subscribe_to(channel, callback, this.net.nutella.appId, undefined, done_callback); -}; - - - -/** - * Unsubscribes from a channel - * - * @param channel - * @param done_callback - */ -AppNetSubModule.prototype.unsubscribe = function(channel, done_callback) { - this.net.unsubscribe_from(channel, this.net.nutella.appId, undefined, done_callback); -}; - - - -/** - * Publishes a message to a channel - * - * @param channel - * @param message - */ -AppNetSubModule.prototype.publish = function(channel, message) { - this.net.publish_to(channel, message, this.net.nutella.appId, undefined); -}; - - - -/** - * Sends a request. - * - * @param channel - * @param message - * @param callback - */ -AppNetSubModule.prototype.request = function(channel, message, callback) { - this.net.request_to(channel, message, callback, this.net.nutella.appId, undefined); -}; - - - -/** - * Handles requests. - * - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.handle_requests = function (channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, this.net.nutella.appId, undefined, done_callback); -}; - - - -//---------------------------------------------------------------------------------------------------------------- -// Application-level APIs to communicate at the run-level -//---------------------------------------------------------------------------------------------------------------- - -/** - * Allows application-level APIs to subscribe to a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.subscribe_to_run = function(run_id, channel, callback, done_callback) { - this.net.subscribe_to(channel,callback,this.net.nutella.appId,run_id,done_callback); -}; - - -/** - * Allows application-level APIs to unsubscribe from a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param done_callback - */ -AppNetSubModule.prototype.unsubscribe_from_run = function(run_id, channel, done_callback) { - this.net.unsubscribe_from(channel,this.net.nutella.appId,run_id,done_callback); -}; - - -/** - * Allows application-level APIs to publish to a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param message - */ -AppNetSubModule.prototype.publish_to_run = function( run_id, channel, message ) { - this.net.publish_to(channel,message,this.net.nutella.appId, run_id); -}; - - -/** - * Allows application-level APIs to make a request to a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param request - * @param callback - */ -AppNetSubModule.prototype.request_to_run = function( run_id, channel, request, callback) { - this.net.request_to(channel,request,callback,this.net.nutella.appId,run_id); -}; - - -/** - * Allows application-level APIs to handle requests on a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.handle_requests_on_run = function( run_id, channel, callback, done_callback ) { - this.net.handle_requests_on(channel,callback,this.net.nutella.appId,run_id,done_callback); -}; - - -//---------------------------------------------------------------------------------------------------------------- -// Application-level APIs to communicate at the run-level (broadcast) -//---------------------------------------------------------------------------------------------------------------- - -/** - * Fired whenever a message is received on the specified channel for any of the runs in the application - * - * @callback all_runs_cb - * @param {string} message - the received message. Messages that are not JSON are discarded. - * @param {string} run_id - the run_id of the channel the message was sent on - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - */ - -/** - * Allows application-level APIs to subscribe to a run-level channel *for ALL runs* - * - * @param {string} channel - the run-level channel we are subscribing to. Can be wildcard - * @param {all_runs_cb} callback - the callback that is fired whenever a message is received on the channel - */ -AppNetSubModule.prototype.subscribe_to_all_runs = function(channel, callback, done_callback) { - var app_id = this.net.nutella.appId; - //Pad channel - var padded_channel = this.net.pad_channel(channel, app_id, '+'); - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var run_id = extractRunId(app_id, mqtt_channel); - if(f.type==='publish') - callback(f.payload, run_id, f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Add to subscriptions, save mqtt callback and subscribe - this.net.subscriptions.push(padded_channel); - this.net.callbacks.push(mqtt_cb); - this.net.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, this.net.nutella.appId, undefined); -}; - - -/** - * Allows application-level APIs to publish a message to a run-level channel *for ALL runs* - * - * @param channel - * @param message - */ -AppNetSubModule.prototype.publish_to_all_runs = function(channel, message) { - this.net.nutella.runs_list.forEach(function(run_id){ - this.net.publish_to(channel,message,this.net.nutella.appId,run_id); - }.bind(this)); -}; - - -/** - * Allows application-level APIs to send a request to a run-level channel *for ALL runs* - * - * @param channel - * @param request - * @param callback - */ -AppNetSubModule.prototype.request_to_all_runs = function(channel, request, callback) { - this.net.nutella.runs_list.forEach(function(run_id){ - this.net.request_to(channel,request,callback,this.net.nutella.appId,run_id); - }.bind(this)); -}; - - -/** - * This callback is used to handle all runs - * @callback handle_all_run - * @param {string} message - the received message. Messages that are not JSON are discarded. - * @param {string} run_id - the run_id of the channel the message was sent on - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - * @return {Object} the response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - */ - -/** - * Allows application-level APIs to handle requests to a run-level channel *for ALL runs* - * - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.handle_requests_on_all_runs = function(channel, callback, done_callback) { - var app_id = this.net.nutella.appId; - // Pad channel - var padded_channel = this.net.pad_channel(channel, app_id, '+'); - var ln = this.net; - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var run_id = extractRunId(app_id, mqtt_channel); - // Only handle requests that have proper id set - if(f.type!=='request' || f.id===undefined) return; - // Execute callback and send response - var m = ln.prepare_message_for_response(callback(f.payload, run_id, f.from), f.id); - ln.nutella.mqtt_client.publish( padded_channel, m ); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - this.net.nutella.mqtt_client.subscribe( padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, this.net.nutella.appId, undefined); -}; - - - -// Utility function - -function extractRunId(app_id, mqtt_channel) { - var pc = '/nutella/apps/' + app_id + '/runs/'; - var sp = mqtt_channel.replace(pc, '').split('/'); - return sp[0]; -} - - -module.exports = AppNetSubModule; - -},{"./util/net":13}],7:[function(require,module,exports){ -/** - * Framework-level APIs for nutella, browser version - */ - -// Require various sub-modules -var FrNetSubModule = require('./fr_net'); -var FrLogSubModule = require('./fr_log'); - - -var FrSubModule = function(main_nutella) { - // Initialized the various sub-modules - this.net = new FrNetSubModule(main_nutella); - this.log = new FrLogSubModule(main_nutella); -}; - - -module.exports = FrSubModule; -},{"./fr_log":8,"./fr_net":9}],8:[function(require,module,exports){ -/** - * Framework-level log APIs for nutella - */ - -var FrNetSubModule = require('./app_net'); - -var FrLogSubModule = function(main_nutella) { - this.net = new FrNetSubModule(main_nutella); -}; - - - -FrLogSubModule.prototype.debug = function(message, code) { - console.debug(message); - this.net.publish('logging', logToJson(message, code, 'debug')); - return code; -}; - -FrLogSubModule.prototype.info = function(message, code) { - console.info(message); - this.net.publish('logging', logToJson(message, code, 'info')); - return code; -}; - -FrLogSubModule.prototype.success = function(message, code) { - console.log('%c '+ message , 'color: #009933'); - this.net.publish('logging', logToJson(message, code, 'success')); - return code; -}; - -FrLogSubModule.prototype.warn = function(message, code) { - console.warn(message); - this.net.publish('logging', logToJson(message, code, 'warn')); - return code; -}; - -FrLogSubModule.prototype.error = function(message, code) { - console.error(message); - this.net.publish('logging', logToJson(message, code, 'error')); - return code; -}; - - -function logToJson( message, code, level) { - return (code === undefined) ? {level: level, message: message} : {level: level, message: message, code: code}; -} - - - -module.exports = FrLogSubModule; - -},{"./app_net":6}],9:[function(require,module,exports){ -/** - * Framework-level Networking APIs for nutella - */ - - -var AbstractNet = require('./util/net'); - - -/** - * Framework-level network APIs for nutella - * @param main_nutella - * @constructor - */ -var FRNetSubModule = function(main_nutella) { - this.net = new AbstractNet(main_nutella); -}; - - - -/** - * Subscribes to a channel or filter. - * - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe = function(channel, callback, done_callback) { - this.net.subscribe_to(channel, callback, undefined, undefined, done_callback); -}; - - -/** - * Unsubscribes from a channel - * - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe = function(channel, done_callback) { - this.net.unsubscribe_from(channel, undefined, undefined, done_callback); -}; - - -/** - * Publishes a message to a channel - * - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish = function(channel, message) { - this.net.publish_to(channel, message, undefined, undefined); -}; - - -/** - * Sends a request. - * - * @param channel - * @param message - * @param callback - */ -FRNetSubModule.prototype.request = function(channel, message, callback) { - this.net.request_to(channel, message, callback, undefined, undefined); -}; - - -/** - * Handles requests. - * - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.handle_requests = function(channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, undefined, undefined, done_callback); -}; - - - -//---------------------------------------------------------------------------------------------------------------- -// Framework-level APIs to communicate at the run-level to a specific run -//---------------------------------------------------------------------------------------------------------------- - -/** - * Allows framework-level APIs to subscribe to a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe_to_run = function(app_id, run_id, channel, callback,done_callback) { - this.net.subscribe_to(channel,callback,app_id,run_id,done_callback) -}; - - -/** - * Allows framework-level APIs to unsubscribe from a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe_to_run = function( app_id, run_id, channel, done_callback ) { - this.net.unsubscribe_from(channel, app_id, run_id, done_callback); -}; - - -/** - * Allows framework-level APIs to publish to a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish_to_run = function( app_id, run_id, channel, message ) { - this.net.publish_to(channel, message, app_id, run_id); -}; - - -/** - * Allows framework-level APIs to make an asynchronous request to a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param request - * @param callback - */ -FRNetSubModule.prototype.request_to_run = function( app_id, run_id, channel, request, callback) { - this.net.request_to(channel, request, callback, app_id, run_id); -}; - - -/** - * Allows framework-level APIs to handle requests on a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param callback - */ -FRNetSubModule.prototype.handle_requests_on_run = function( app_id, run_id, channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, app_id, run_id, done_callback) -}; - - - -//---------------------------------------------------------------------------------------------------------------- -// Framework-level APIs to communicate at the run-level (broadcast) -//---------------------------------------------------------------------------------------------------------------- - - -/** - * Callback for subscribing to all runs - * @callback allRunsCb - # @param {string} message - the received message. Messages that are not JSON are discarded - # @param {String} app_id - the app_id of the channel the message was sent on - # @param {String} run_id - the run_id of the channel the message was sent on - # @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - */ - -/** - * Allows framework-level APIs to subscribe to a run-level channel *for ALL runs* - * - * @param channel - * @param {allRunsCb} callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe_to_all_runs = function( channel, callback, done_callback ) { - //Pad channel - var padded_channel = this.net.pad_channel(channel, '+', '+'); - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var f1 = extractRunIdAndAppId(mqtt_channel); - if(f.type==='publish') - callback(f.payload, f1.appId, f1.runId, f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Add to subscriptions, save mqtt callback and subscribe - this.net.subscriptions.push(padded_channel); - this.net.callbacks.push(mqtt_cb); - this.net.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, undefined, undefined); -}; - - -/** - * Allows framework-level APIs to unsubscribe from a run-level channel *for ALL runs* - * - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe_from_all_runs = function(channel, done_callback) { - this.net.unsubscribe_from(channel, '+', '+', done_callback); -}; - - -/** - * Allows framework-level APIs to publish a message to a run-level channel *for ALL runs* - * - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish_to_all_runs = function( channel, message ) { - Object.keys(this.net.nutella.runs_list).forEach(function(app_id) { - this.net.nutella.runs_list[app_id].runs.forEach(function(run_id){ - this.net.publish_to(channel, message, app_id, run_id); - }.bind(this)); - }.bind(this)); -}; - - -/** - * Allows framework-level APIs to send a request to a run-level channel *for ALL runs* - * - * @param channel - * @param request - * @param callback - */ -FRNetSubModule.prototype.request_to_all_runs = function(channel, request, callback) { - Object.keys(this.net.nutella.runs_list).forEach(function(app_id) { - this.net.nutella.runs_list[app_id].runs.forEach(function(run_id){ - this.net.publish_to(channel, message, app_id, run_id); - this.net.request_to(channel, request, callback, app_id, run_id); - }.bind(this)); - }.bind(this)); -}; - -/** - * Callback that is used to handle messages from all runs - * @callback handle_all_runs_cb - * @param {string} payload - the received message (request). Messages that are not JSON are discarded - * @param {string} app_id - the app_id of the channel the request was sent on - * @param {string} run_id - the run_id of the channel the request was sent on - * @param {Object} from - the sender's identifiers (from containing, run_id, app_id, component_id and optionally resource_id) - * @return {Object} the response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - */ - -/** - * Allows framework-level APIs to handle requests to a run-level channel *for ALL runs* - * - * @param channel - * @param {handle_all_runs_cb} callback - * @param done_callback - */ -FRNetSubModule.prototype.handle_requests_on_all_runs = function(channel, callback, done_callback) { - // Pad channel - var padded_channel = this.net.pad_channel(channel, '+', '+'); - var ln = this.net; - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var f1 = extractRunIdAndAppId(mqtt_channel); - // Only handle requests that have proper id set - if(f.type!=='request' || f.id===undefined) return; - // Execute callback and send response - var m = ln.prepare_message_for_response(callback(f.payload, f1.appId, f1.runId, f.from), f.id); - ln.nutella.mqtt_client.publish( padded_channel, m ); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - this.net.nutella.mqtt_client.subscribe( padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, undefined, undefined); -}; - - - -//---------------------------------------------------------------------------------------------------------------- -// Framework-level APIs to communicate at the application-level -//---------------------------------------------------------------------------------------------------------------- - - -/** - * Allows framework-level APIs to subscribe to an app-level channel - * - * @param app_id - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe_to_app = function(app_id, channel, callback, done_callback) { - this.net.subscribe_to(channel,callback,app_id, undefined, done_callback) -}; - - -/** - * Allows framework-level APIs to unsubscribe from an app-level channel within a specific run - * - * @param app_id - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe_to_app = function( app_id, channel, done_callback) { - this.net.unsubscribe_from(channel,app_id,undefined, done_callback); -}; - - -/** - * Allows framework-level APIs to publish to an app-level channel - * - * @param app_id - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish_to_app = function(app_id, channel, message) { - this.net.publish_to(channel,message,app_id,undefined); -}; - - -/** - * Allows framework-level APIs to make an asynchronous request to a run-level channel within a specific run - * - * @param app_id - * @param channel - * @param request - * @param callback - */ -FRNetSubModule.prototype.request_to_app = function( app_id, channel, request, callback) { - this.net.request_to(channel, request, callback, app_id, undefined); -}; - - -/** - * Allows framework-level APIs to handle requests on a run-level channel within a specific run - * - * @param app_id - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.handle_requests_on_app = function(app_id, channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, app_id, undefined, done_callback); -}; - - -//---------------------------------------------------------------------------------------------------------------- -// Framework-level APIs to communicate at the application-level (broadcast) -//---------------------------------------------------------------------------------------------------------------- - -/** - * Callback used to handle all messages received when subscribing to all applications - * @callback subscribeToAllAppsCb - * @param {string} message - the received message. Messages that are not JSON are discarded - * @param {string} app_id - the app_id of the channel the message was sent on - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - */ - -/** - * Allows framework-level APIs to subscribe to an app-level channel *for ALL apps* - * - * @param channel - * @param {subscribeToAllAppsCb} callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe_to_all_apps = function(channel, callback, done_callback) { - //Pad channel - var padded_channel = this.net.pad_channel(channel, '+', undefined); - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var app_id = extractAppId(mqtt_channel); - if(f.type==='publish') - callback(f.payload, app_id, f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Add to subscriptions, save mqtt callback and subscribe - this.net.subscriptions.push(padded_channel); - this.net.callbacks.push(mqtt_cb); - this.net.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, undefined, undefined); -}; - - -/** - * Allows framework-level APIs to unsubscribe from an app-level channel *for ALL apps* - * - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe_from_all_apps = function(channel, done_callback) { - this.net.unsubscribe_from(channel, '+', undefined, done_callback); -}; - - -/** - * Allows framework-level APIs to publish a message to an app-level channel *for ALL apps* - * - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish_to_all_apps = function(channel, message) { - Object.keys(this.net.nutella.runs_list).forEach(function(app_id) { - this.net.publish_to(channel, message, app_id, undefined); - }.bind(this)); -}; - - -/** - * Allows framework-level APIs to send a request to an app-level channel *for ALL apps* - * - * @param channel - * @param request - * @param callback - */ -FRNetSubModule.prototype.request_to_all_apps = function(channel, request, callback) { - Object.keys(this.net.nutella.runs_list).forEach(function(app_id) { - this.net.request_to(channel, request, callback, app_id, undefined); - }.bind(this)); -}; - - -/** - * This callback is used to handle messages coming from all applications - * @callback handleAllAppsCb - * @param {string} request - the received message (request). Messages that are not JSON are discarded. - * @param {string} app_id - the app_id of the channel the request was sent on - * @param {Object} from - the sender's identifiers (from containing, run_id, app_id, component_id and optionally resource_id) - * @return {Object} The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - */ - -/** - * Allows framework-level APIs to handle requests to app-level channel *for ALL runs* - * - * @param channel - * @param {handleAllAppsCb} callback - * @param done_callback - */ -FRNetSubModule.prototype.handle_requests_on_all_apps = function(channel, callback, done_callback) { - // Pad channel - var padded_channel = this.net.pad_channel(channel, '+', undefined); - var ln = this.net; - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var f1 = extractRunIdAndAppId(mqtt_channel); - // Only handle requests that have proper id set - if(f.type!=='request' || f.id===undefined) return; - // Execute callback and send response - var m = ln.prepare_message_for_response(callback(f.payload, f1.appId, f1.runId, f.from), f.id); - ln.nutella.mqtt_client.publish( padded_channel, m ); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - this.net.nutella.mqtt_client.subscribe( padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, undefined, undefined); -}; - - -// Utility functions - - -function extractRunIdAndAppId(mqtt_channel) { - var sp = mqtt_channel.replace('/nutella/apps/', '').split('/'); - return {appId: sp[0], runId: sp[2]}; -} - -function extractAppId(mqtt_channel) { - var sp = mqtt_channel.replace('/nutella/apps/', '').split('/'); - return sp[0]; -} - - - - -module.exports = FRNetSubModule; - -},{"./util/net":13}],10:[function(require,module,exports){ -/** - * Run-level and App-level Nutella instances for the browser - */ - -var SimpleMQTTClient = require('simple-mqtt-client'); - -// Require various sub-modules -var AppSubModule = require('./app_core_browser'); -var FrSubModule = require('./fr_core_browser'); -var NetSubModule = require('./run_net'); -var LogSubModule = require('./run_log'); - - -/** - * Defines the RunNutellaInstance class. - * - * @param {String } app_id - the app_id this component belongs to - * @param {string} run_id - the run_id this component is launched in - * @param {string} broker_hostname - the hostname of the broker. - * @param {string} component_id - the name of this component - */ -var RunNutellaInstance = function (broker_hostname, app_id, run_id, component_id) { - //Initialize parameters - this.mqtt_client = new SimpleMQTTClient(broker_hostname); - this.appId = app_id; - this.runId = run_id; - this.componentId = component_id; - // Initialized the various sub-modules - this.net = new NetSubModule(this); - this.log = new LogSubModule(this); - // Start pinging - setInterval(function(){ - this.net.publish('pings', 'ping'); - }.bind(this),5000); -}; - -/** - * Sets the resource id for this instance of nutella - * - * @param {string} resource_id - the resource_id associated to this instance of nutella - */ -RunNutellaInstance.prototype.setResourceId = function(resource_id){ - this.resourceId = resource_id; -}; - - -/** - * Defines the AppNutellaInstance class. - * - * @param {String } app_id - the app_id this component belongs to - * @param {string} broker_hostname - the hostname of the broker. - * @param {string} component_id - the name of this component - */ -var AppNutellaInstance = function (broker_hostname, app_id, component_id) { - //Initialize parameters - this.mqtt_client = new SimpleMQTTClient(broker_hostname); - this.appId = app_id; - this.componentId = component_id; - // Initialized the various sub-modules - this.app = new AppSubModule(this); - //Initialize the runs list - this.runs_list = []; - // Fetch the runs list - this.app.net.request('app_runs_list', undefined, function(response) { - this.runs_list = response; - }.bind(this)); - // Subscribe to runs list updates - this.app.net.subscribe('app_runs_list', function(message, from) { - this.runs_list = message; - }.bind(this)); - // Start pinging - setInterval(function(){ - this.app.net.publish('pings', 'ping'); - }.bind(this),5000); -}; - -/** - * Sets the resource id for this instance of nutella - * - * @param {string} resource_id - the resource_id associated to this instance of nutella - */ -AppNutellaInstance.prototype.setResourceId = function(resource_id){ - this.resourceId = resource_id; -}; - - -/** - * Defines the FRNutellaInstance class. - * - * @param {string} broker_hostname - the hostname of the broker. - * @param {string} component_id - the name of this component - */ -var FrNutellaInstance = function (broker_hostname, component_id) { - //Initialize parameters - this.mqtt_client = new SimpleMQTTClient(broker_hostname); - this.componentId = component_id; - // Initialize the various sub-modules - this.f = new FrSubModule(this); - //Initialize the runs list - this.runs_list = {}; - // Fetch the runs list - this.f.net.request('runs_list', undefined, function(response) { - this.runs_list = response; - }.bind(this)); - // Subscribe to runs list updates - this.f.net.subscribe('runs_list', function(message, from) { - this.runs_list = message; - }.bind(this)); - // Start pinging - setInterval(function(){ - this.f.net.publish('pings', 'ping'); - }.bind(this),5000); -}; - -/** - * Sets the resource id for this instance of nutella - * - * @param {string} resource_id - the resource_id associated to this instance of nutella - */ -FrNutellaInstance.prototype.setResourceId = function(resource_id){ - this.resourceId = resource_id; -}; - - - -module.exports = { - RunNutellaInstance : RunNutellaInstance, - AppNutellaInstance : AppNutellaInstance, - FrNutellaInstance : FrNutellaInstance -}; -},{"./app_core_browser":4,"./fr_core_browser":7,"./run_log":11,"./run_net":12,"simple-mqtt-client":2}],11:[function(require,module,exports){ -/** - * Run-level Logging APIs for nutella - */ - -var NetSubModule = require('./run_net'); - -var LogSubModule = function(main_nutella) { - this.net = new NetSubModule(main_nutella); -}; - - -LogSubModule.prototype.debug = function(message, code) { - console.debug(message); - this.net.publish('logging', logToJson(message, code, 'debug')); - return code; -}; - -LogSubModule.prototype.info = function(message, code) { - console.info(message); - this.net.publish('logging', logToJson(message, code, 'info')); - return code; -}; - -LogSubModule.prototype.success = function(message, code) { - console.log('%c '+ message , 'color: #009933'); - this.net.publish('logging', logToJson(message, code, 'success')); - return code; -}; - -LogSubModule.prototype.warn = function(message, code) { - console.warn(message); - this.net.publish('logging', logToJson(message, code, 'warn')); - return code; -}; - -LogSubModule.prototype.error = function(message, code) { - console.error(message); - this.net.publish('logging', logToJson(message, code, 'error')); - return code; -}; - - -function logToJson( message, code, level) { - return (code===undefined) ? {level: level, message: message} : {level: level, message: message, code: code}; -} - - - - - -module.exports = LogSubModule; -},{"./run_net":12}],12:[function(require,module,exports){ -/** - * Run-level Network APIs for nutella - */ - - -var AbstractNet = require('./util/net'); - - -/** - * Run-level network APIs for nutella - * @param main_nutella - * @constructor - */ -var NetSubModule = function(main_nutella) { - // Store a reference to the main module - this.nutella = main_nutella; - this.net = new AbstractNet(main_nutella); -}; - - - -/** - * Subscribes to a channel or filter. - * - * @param channel - * @param callback - * @param done_callback - */ -NetSubModule.prototype.subscribe = function(channel, callback, done_callback) { - this.net.subscribe_to(channel, callback, this.nutella.appId, this.nutella.runId, done_callback); -}; - - - -/** - * Unsubscribes from a channel - * - * @param channel - * @param done_callback - */ -NetSubModule.prototype.unsubscribe = function(channel, done_callback) { - this.net.unsubscribe_from(channel, this.nutella.appId, this.nutella.runId, done_callback); -}; - - - -/** - * Publishes a message to a channel - * - * @param channel - * @param message - */ -NetSubModule.prototype.publish = function(channel, message) { - this.net.publish_to(channel, message, this.nutella.appId, this.nutella.runId); -}; - - - -/** - * Sends a request. - * - * @param channel - * @param message - * @param callback - */ -NetSubModule.prototype.request = function(channel, message, callback) { - this.net.request_to(channel, message, callback, this.nutella.appId, this.nutella.runId); -}; - - - -/** - * Handles requests. - * - * @param channel - * @param callback - * @param done_callback - */ -NetSubModule.prototype.handle_requests = function(channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, this.nutella.appId, this.nutella.runId, done_callback); -}; - - -module.exports = NetSubModule; - -},{"./util/net":13}],13:[function(require,module,exports){ -/** - * Network APIs abstraction - */ - - -var AbstractNet = function(main_nutella) { - this.subscriptions = []; - this.callbacks = []; - this.nutella = main_nutella; -}; - - -/** - * This callback is fired whenever a message is received by the subscribe - * - * @callback subscribeCallback - * @param {string} message - the received message. Messages that are not JSON are discarded - * @param {string} [channel] - the channel the message was received on (optional, only for wildcard subscriptions) - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - */ - -/** - * Subscribes to a channel or to a set of channels - * - * @param {string} channel - the channel or filter we are subscribing to. Can contain wildcard(s) - * @param {subscribeCallback} callback - fired whenever a message is received - * @param {string|undefined} appId - used to pad channels - * @param {string|undefined} runId - used to pad channels - * @param {function} done_callback - fired whenever the subscribe is successful - */ -AbstractNet.prototype.subscribe_to = function(channel, callback, appId, runId, done_callback) { - // Pad channel - var padded_channel = this.pad_channel(channel, appId, runId); - // Maintain unique subscriptions - if(this.subscriptions.indexOf(padded_channel)>-1) - throw 'You can`t subscribe twice to the same channel`'; - // Depending on what type of channel we are subscribing to (wildcard or simple) - // register a different kind of callback - var mqtt_cb; - if(this.nutella.mqtt_client.isChannelWildcard(padded_channel)) - mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - if(f.type==='publish') - callback(f.payload, this.un_pad_channel(mqtt_channel, appId, runId), f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - else - mqtt_cb = function(mqtt_message) { - try { - var f = JSON.parse(mqtt_message); - if(f.type==='publish') - callback(f.payload, f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Add to subscriptions, save mqtt callback and subscribe - this.subscriptions.push(padded_channel); - this.callbacks.push(mqtt_cb); - this.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, appId, runId); -}; - - -/** - * Unsubscribes from a channel or a set of channels - * - * @param {string} channel - we want to unsubscribe from. Can contain wildcard(s) - * @param {string|undefined} appId - used to pad channels - * @param {string|undefined} runId - used to pad channels - * @param {function} done_callback - fired whenever the subscribe is successful - */ -AbstractNet.prototype.unsubscribe_from = function(channel, appId, runId, done_callback ) { - // Pad channel - var padded_channel = this.pad_channel(channel, appId, run_id); - var idx = this.subscriptions.indexOf(padded_channel); - // If we are not subscribed to this channel, return (no error is given) - if(idx===-1) return; - // Fetch the mqtt_callback associated with this channel/subscription - var mqtt_cb = this.callbacks[idx]; - // Remove from subscriptions, callbacks and unsubscribe - this.subscriptions.splice(idx, 1); - this.callbacks.splice(idx, 1); - this.nutella.mqtt_client.unsubscribe(padded_channel, mqtt_cb, done_callback); -}; - - -/** - * Publishes a message to a channel - * - * @param {String} channel - the channel we want to publish the message to. *CANNOT* contain wildcard(s)! - * @param {Object} message - the message we are publishing. This can be any JS variable, even undefined. - * @param {String|undefined} appId - used to pad the channels - * @param {String|undefined} runId - used to pad the channels - */ -AbstractNet.prototype.publish_to = function(channel, message, appId, runId) { - // Pad channel - var padded_channel = this.pad_channel(channel, appId, runId); - // Throw exception if trying to publish something that is not JSON - try { - var m = this.prepare_message_for_publish(message); - this.nutella.mqtt_client.publish(padded_channel, m); - } catch(e) { - console.error('Error: you are trying to publish something that is not JSON'); - console.error(e.message); - } -}; - - -/** - * This callback is fired whenever a response to a request is received - * - * @callback requestCallback - * @param {string} response - the body of the response - */ - -/** - * Performs an asynchronous request - * - * @param {string} channel - the channel we want to make the request to. *CANNOT* contain wildcard(s)! - * @param {string} message - the body of the request. This can be any JS variable, even undefined. - * @param {requestCallback} callback - the callback that is fired whenever a response is received - * @param {string|undefined} appId - used to pad channels - * @param {string|undefined} runId - used to pad channels - */ -AbstractNet.prototype.request_to = function( channel, message, callback, appId, runId ) { - // Save nutella reference - var nut = this.nutella; - // Pad channel - var padded_channel = this.pad_channel(channel, appId, runId); - // Prepare message - var m = this.prepare_message_for_request(message); - //Prepare callback - var mqtt_cb = function(mqtt_message) { - var f = JSON.parse(mqtt_message); - if (f.id===m.id && f.type==='response') { - callback(f.payload); - nut.mqtt_client.unsubscribe(padded_channel, mqtt_cb); - } - }; - // Subscribe - this.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, function() { - // Publish message - nut.mqtt_client.publish( padded_channel, m.message ); - }); - -}; - - -/** - * This callback is fired whenever a request is received that needs to be handled - * - * @callback handleCallback - * @param {string} request - the body of the received request (payload). Messages that are not JSON are discarded. - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - * @return {Object} The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - */ - -/** - * Handles requests on a certain channel or a certain set of channels - * - * @param {string} channel - the channel we want to listen for requests on. Can contain wildcard(s). - * @param {handleCallback} callback - fired whenever a message is received - * @param {string|undefined} appId - used to pad channels - * @param {string|undefined} runId - used to pad channels - * @param {function} done_callback - fired whenever we are ready to handle requests - */ -AbstractNet.prototype.handle_requests_on = function( channel, callback, appId, runId, done_callback) { - // Save nutella reference - var nut = this.nutella; - // Pad channel - var padded_channel = this.pad_channel(channel, appId, runId); - var mqtt_cb = function(request) { - try { - // Extract nutella fields - var f = JSON.parse(request); - // Only handle requests that have proper id set - if(f.type!=='request' || f.id===undefined) return; - // Execute callback and send response - var m = this.prepare_message_for_response(callback(f.payload, f.from), f.id); - nut.mqtt_client.publish( padded_channel, m ); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Subscribe to the channel - this.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, appId, runId); -}; - - - -/** - * Pads the channel with app_id and run_id - * - * @param channel - * @param app_id - * @param run_id - * @return {string} the padded channel - */ -AbstractNet.prototype.pad_channel = function(channel, app_id, run_id) { - if (run_id!==undefined && app_id===undefined) - throw 'If the run_id is specified, app_id needs to also be specified'; - if (app_id===undefined && run_id===undefined) - return '/nutella/' + channel; - if (app_id!==undefined && run_id===undefined) - return '/nutella/apps/' + app_id + '/' + channel; - return '/nutella/apps/' + app_id + '/runs/' + run_id + '/' + channel; -}; - - -/** - * Un-pads the channel with app_id and run_id - * - * @param channel - * @param app_id - * @param run_id - * @return {string} the un-padded channel - */ -AbstractNet.prototype.un_pad_channel = function(channel, app_id, run_id) { - if (run_id!==undefined && app_id===undefined) - throw 'If the run_id is specified, app_id needs to also be specified'; - if (app_id===undefined && run_id===undefined) - return channel.replace('/nutella/', ''); - if (app_id!==undefined && run_id===undefined) - return channel.replace("/nutella/apps/" + app_id + "/", ''); - return channel.replace("/nutella/apps/" + app_id + "/runs/" + run_id + "/", ''); -}; - - -/** - * Assembles the unique ID of the component, starting from app_id, run_id, component_id and resource_id - * - * @return {Object} an object containing the unique ID of the component sending the message - */ -AbstractNet.prototype.assemble_from = function() { - var from = {}; - // Set type, run_id and app_id whenever appropriate - if(this.nutella.runId===undefined) { - if(this.nutella.appId===undefined) { - from.type = 'framework'; - } else { - from.type = 'app'; - from.app_id = this.nutella.appId; - } - } else { - from.type = 'run'; - from.app_id = this.nutella.appId; - from.run_id = this.nutella.runId; - } - // Set the component_id - from.component_id = this.nutella.componentId; - // Set resource_id, if defined - if (this.nutella.resourceId!==undefined) - from.resource_id = this.nutella.resourceId; - return from; -}; - - -/** - * Prepares a message for a publish - * - * @param {Object} message - the message content - * @return {string} the serialized message, ready to be shipped over the net - */ -AbstractNet.prototype.prepare_message_for_publish = function (message) { - if(message===undefined) - return JSON.stringify({type: 'publish', from: this.assemble_from()}); - return JSON.stringify({type: 'publish', from: this.assemble_from(), payload: message}); -}; - - -/** - * Prepares a message for a request - * - * @param {Object} message - the message content - * @return {Object} the serialized response, ready to be shipped over the net and the id of the response - */ -AbstractNet.prototype.prepare_message_for_request = function (message) { - var id = Math.floor((Math.random() * 100000) + 1).toString(); - var m = {}; - m.id = id; - if(message===undefined) - m.message = JSON.stringify({id: id, type: 'request', from: this.assemble_from()}); - else - m.message = JSON.stringify({id: id, type: 'request', from: this.assemble_from(), payload: message}); - return m; -}; - - -/** - * Prepares a message for a response - * - * @param {Object} response - the response content - * @param {string} id - the original request id - * @return {string} the serialized message, ready to be shipped over the net - */ -AbstractNet.prototype.prepare_message_for_response = function (response, id) { - if(response===undefined) - return JSON.stringify({id: id, type: 'response', from: this.assemble_from()}); - return JSON.stringify({id: id, type: 'response', from: this.assemble_from(), payload: response}); -}; - - - -// Export module -module.exports = AbstractNet; -},{}]},{},[1])(1) -}); \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js.map b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js.map deleted file mode 100755 index 769f835..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"names":[],"mappings":"","sources":["nutella_lib.js"],"sourcesContent":["(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o - - - - Nutella hello world - - - - - - - - \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/node_hello_world.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/node_hello_world.js deleted file mode 100755 index 93e51c7..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/node_hello_world.js +++ /dev/null @@ -1,51 +0,0 @@ -var NUTELLA = require('../src/nutella_lib') - -// Initialize nutella -//var p = NUTELLA.parseURLParameters(); // This only works in the browser -var p = NUTELLA.parseAppComponentArgs(); -console.log(p) -var nutella = NUTELLA.init('ltg.evl.uic.edu', 'my_app_id', 'my_run_id', 'demo_node_bot'); -nutella.setResourceId('r_id'); -nutella.log.test(); -nutella.net.publish('channel', 'message'); -nutella.persist.test(); // this should only work in node -nutella = NUTELLA.initApp('ltg.evl.uic.edu', 'my_app_id', 'demo_node_bot'); -nutella.app.net.test(); -nutella.app.log.test(); -nutella.app.persist.test(); - - - -// // Subscribe to a channel -// nutella.net.subscribe("demo1", function(message, c_id, r_id) { -// console.log('Received "' + JSON.stringify(message) + '" from ' + c_id + '/' + r_id); -// nutella.net.unsubscribe('demo1'); -// }); - -// // Wildcard subscribe -// nutella.net.subscribe("demo2/#", function(message, channel, c_id, r_id) { -// console.log('Received "' + JSON.stringify(message) + '" on channel ' + channel + ' from ' + c_id + '/' + r_id); -// }); - -// // Publish some stuff -// nutella.net.publish('demo1'); -// nutella.net.publish('demo1', 'just a string'); -// nutella.net.publish('demo1', {a: 'proper', key: 'value'}); -// nutella.setResourceId('a_particular_resource'); -// nutella.net.publish('demo1'); -// nutella.net.publish('demo1', 'just a string'); -// nutella.net.publish('demo1', {a: 'proper', key: 'value'}); - - // Handle requests - //nutella.net.handle_requests('demo1', function(message, component_id, resource_id) { - // return 'this is the returned value'; - //}); - // - //// Perform a couple requests - //nutella.net.request('demo1', function(response) { - // console.log("This is the response to empty request (GET)"); - //}); - // - //nutella.net.request('demo1', 'my_request', function(response) { - // console.log("This is the response to non-empty request"); - //}); diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/gulpfile.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/gulpfile.js deleted file mode 100755 index e9fa525..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/gulpfile.js +++ /dev/null @@ -1,31 +0,0 @@ -var gulp = require('gulp'); -var gutil = require('gulp-util'); -var browserify = require('browserify'); -var watchify = require('watchify'); -var source = require('vinyl-source-stream'); - -// bundler used to run watchify and browserify -var bundler = watchify(browserify(watchify.args, {standalone: 'NUTELLA'})); -// add the lib file to bundle -bundler.add('./src/nutella_lib.js'); - - -gulp.task('bundle', bundle); // so you can run `gulp js` to build the file -bundler.on('update', bundle); // on any dep update, runs the bundler -bundler.on('log', gutil.log); // output build logs to terminal - -function bundle() { - return bundler.bundle() - // log errors if they happen - .on('error', gutil.log.bind(gutil, 'Browserify Error')) - .pipe(source('nutella_lib.js')) - .pipe(gulp.dest('./dist')); -} - - -gulp.task('default', function() { - // place code for your default task here -}); - - - diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/package.json b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/package.json deleted file mode 100755 index baa6f39..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "nutella_lib", - "version": "0.4.3", - "description": "Nutella library for JavaScript", - "main": "src/nutella_lib.js", - "browser": "src/nutella_lib_browser.js", - "scripts": { - "test": "mocha" - }, - "repository": { - "type": "git", - "url": "https://github.com/nutella-framework/nutella_lib.js" - }, - "devDependencies": { - "browserify": "^9.0.3", - "chai": "^2.1.2", - "gulp": "^3.8.11", - "gulp-util": "^3.0.4", - "mocha": "^2.2.1", - "vinyl-source-stream": "^1.1.0", - "watchify": "^2.6.2" - }, - "keywords": [ - "nutella", - "framework" - ], - "author": { - "name": "Alessandro Gnoli" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/nutella-framework/nutella_lib.js/issues" - }, - "homepage": "https://github.com/nutella-framework/nutella_lib.js", - "readme": "[![Build Status](https://travis-ci.org/nutella-framework/nutella_lib.js.svg?branch=master)](https://travis-ci.org/nutella-framework/nutella_lib.js)\n\n# nutella library for node.js and the browser\n\n## Installation\nFor node.js projects do\n```\nnpm install nutella_lib\n```\n\nFor browser projects either:\n\n1. `npm install nutella_lib` and then use [browserify](http://browserify.org/) OR\n2. use the bundled `nutella_lib.js` in `dist`\n\n\n## Building the project\nFor developers working on the library. We are using gulp + browserify + watchify to continuously and incrementally build the library as we develop. \n\n**To contribute**: Clone the repo and `gulp bundle` inside the project directory. Every time you make a change to any of the files required by the library gulp will rebuild it. \n\n\n## Releasing a new version\nFor developers working on the library, to release a new version:\n\n- Update the version in the `package.json`\n- Publish to npm by doing `npm publish`", - "readmeFilename": "README.md", - "gitHead": "92101787b67f169eb26aa2139110c6a0ab307abf", - "_id": "nutella_lib@0.4.3", - "_shasum": "21dbfd227fab6a500dc6ccb1bdc2a764908910e2", - "_from": "nutella_lib@*" -} diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core.js deleted file mode 100755 index 32b34b6..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Application-level APIs for nutella, node version - */ - -// Require various sub-modules -var AppNetSubModule = require('./app_net'); -var AppLogSubModule = require('./app_log'); -var AppPersistSubModule = require('./app_persist'); - - -var AppSubModule = function(main_nutella) { - // Initialized the various sub-modules - this.net = new AppNetSubModule(main_nutella); - this.log = new AppLogSubModule(main_nutella); - this.persist = new AppPersistSubModule(main_nutella); -}; - - -module.exports = AppSubModule; \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core_browser.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core_browser.js deleted file mode 100755 index 924ec3b..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core_browser.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Application-level APIs for nutella, browser version - */ - -// Require various sub-modules -var AppNetSubModule = require('./app_net'); -var AppLogSubModule = require('./app_log'); - - -var AppSubModule = function(main_nutella) { - // Initialized the various sub-modules - this.net = new AppNetSubModule(main_nutella); - this.log = new AppLogSubModule(main_nutella); -}; - - -module.exports = AppSubModule; \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_log.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_log.js deleted file mode 100755 index 2b353e8..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_log.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * App-level log APIs for nutella - */ - -var AppNetSubModule = require('./app_net'); - -var AppLogSubModule = function(main_nutella) { - this.net = new AppNetSubModule(main_nutella); -}; - - - -AppLogSubModule.prototype.debug = function(message, code) { - console.debug(message); - this.net.publish('logging', logToJson(message, code, 'debug')); - return code; -}; - -AppLogSubModule.prototype.info = function(message, code) { - console.info(message); - this.net.publish('logging', logToJson(message, code, 'info')); - return code; -}; - -AppLogSubModule.prototype.success = function(message, code) { - console.log('%c '+ message , 'color: #009933'); - this.net.publish('logging', logToJson(message, code, 'success')); - return code; -}; - -AppLogSubModule.prototype.warn = function(message, code) { - console.warn(message); - this.net.publish('logging', logToJson(message, code, 'warn')); - return code; -}; - -AppLogSubModule.prototype.error = function(message, code) { - console.error(message); - this.net.publish('logging', logToJson(message, code, 'error')); - return code; -}; - - -function logToJson( message, code, level) { - return (code === undefined) ? {level: level, message: message} : {level: level, message: message, code: code}; -} - - - -module.exports = AppLogSubModule; diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_net.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_net.js deleted file mode 100755 index 67a385f..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_net.js +++ /dev/null @@ -1,279 +0,0 @@ -/** - * App-level Networking APIs for nutella - */ - - -var AbstractNet = require('./util/net'); - - -/** - * App-level network APIs for nutella - * @param main_nutella - * @constructor - */ -var AppNetSubModule = function(main_nutella) { - this.net = new AbstractNet(main_nutella); -}; - - - -/** - * Subscribes to a channel or filter. - * - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.subscribe = function(channel, callback, done_callback) { - this.net.subscribe_to(channel, callback, this.net.nutella.appId, undefined, done_callback); -}; - - - -/** - * Unsubscribes from a channel - * - * @param channel - * @param done_callback - */ -AppNetSubModule.prototype.unsubscribe = function(channel, done_callback) { - this.net.unsubscribe_from(channel, this.net.nutella.appId, undefined, done_callback); -}; - - - -/** - * Publishes a message to a channel - * - * @param channel - * @param message - */ -AppNetSubModule.prototype.publish = function(channel, message) { - this.net.publish_to(channel, message, this.net.nutella.appId, undefined); -}; - - - -/** - * Sends a request. - * - * @param channel - * @param message - * @param callback - */ -AppNetSubModule.prototype.request = function(channel, message, callback) { - this.net.request_to(channel, message, callback, this.net.nutella.appId, undefined); -}; - - - -/** - * Handles requests. - * - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.handle_requests = function (channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, this.net.nutella.appId, undefined, done_callback); -}; - - - -//---------------------------------------------------------------------------------------------------------------- -// Application-level APIs to communicate at the run-level -//---------------------------------------------------------------------------------------------------------------- - -/** - * Allows application-level APIs to subscribe to a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.subscribe_to_run = function(run_id, channel, callback, done_callback) { - this.net.subscribe_to(channel,callback,this.net.nutella.appId,run_id,done_callback); -}; - - -/** - * Allows application-level APIs to unsubscribe from a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param done_callback - */ -AppNetSubModule.prototype.unsubscribe_from_run = function(run_id, channel, done_callback) { - this.net.unsubscribe_from(channel,this.net.nutella.appId,run_id,done_callback); -}; - - -/** - * Allows application-level APIs to publish to a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param message - */ -AppNetSubModule.prototype.publish_to_run = function( run_id, channel, message ) { - this.net.publish_to(channel,message,this.net.nutella.appId, run_id); -}; - - -/** - * Allows application-level APIs to make a request to a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param request - * @param callback - */ -AppNetSubModule.prototype.request_to_run = function( run_id, channel, request, callback) { - this.net.request_to(channel,request,callback,this.net.nutella.appId,run_id); -}; - - -/** - * Allows application-level APIs to handle requests on a run-level channel within a specific run - * - * @param run_id - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.handle_requests_on_run = function( run_id, channel, callback, done_callback ) { - this.net.handle_requests_on(channel,callback,this.net.nutella.appId,run_id,done_callback); -}; - - -//---------------------------------------------------------------------------------------------------------------- -// Application-level APIs to communicate at the run-level (broadcast) -//---------------------------------------------------------------------------------------------------------------- - -/** - * Fired whenever a message is received on the specified channel for any of the runs in the application - * - * @callback all_runs_cb - * @param {string} message - the received message. Messages that are not JSON are discarded. - * @param {string} run_id - the run_id of the channel the message was sent on - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - */ - -/** - * Allows application-level APIs to subscribe to a run-level channel *for ALL runs* - * - * @param {string} channel - the run-level channel we are subscribing to. Can be wildcard - * @param {all_runs_cb} callback - the callback that is fired whenever a message is received on the channel - */ -AppNetSubModule.prototype.subscribe_to_all_runs = function(channel, callback, done_callback) { - var app_id = this.net.nutella.appId; - //Pad channel - var padded_channel = this.net.pad_channel(channel, app_id, '+'); - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var run_id = extractRunId(app_id, mqtt_channel); - if(f.type==='publish') - callback(f.payload, run_id, f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Add to subscriptions, save mqtt callback and subscribe - this.net.subscriptions.push(padded_channel); - this.net.callbacks.push(mqtt_cb); - this.net.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, this.net.nutella.appId, undefined); -}; - - -/** - * Allows application-level APIs to publish a message to a run-level channel *for ALL runs* - * - * @param channel - * @param message - */ -AppNetSubModule.prototype.publish_to_all_runs = function(channel, message) { - this.net.nutella.runs_list.forEach(function(run_id){ - this.net.publish_to(channel,message,this.net.nutella.appId,run_id); - }.bind(this)); -}; - - -/** - * Allows application-level APIs to send a request to a run-level channel *for ALL runs* - * - * @param channel - * @param request - * @param callback - */ -AppNetSubModule.prototype.request_to_all_runs = function(channel, request, callback) { - this.net.nutella.runs_list.forEach(function(run_id){ - this.net.request_to(channel,request,callback,this.net.nutella.appId,run_id); - }.bind(this)); -}; - - -/** - * This callback is used to handle all runs - * @callback handle_all_run - * @param {string} message - the received message. Messages that are not JSON are discarded. - * @param {string} run_id - the run_id of the channel the message was sent on - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - * @return {Object} the response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - */ - -/** - * Allows application-level APIs to handle requests to a run-level channel *for ALL runs* - * - * @param channel - * @param callback - * @param done_callback - */ -AppNetSubModule.prototype.handle_requests_on_all_runs = function(channel, callback, done_callback) { - var app_id = this.net.nutella.appId; - // Pad channel - var padded_channel = this.net.pad_channel(channel, app_id, '+'); - var ln = this.net; - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var run_id = extractRunId(app_id, mqtt_channel); - // Only handle requests that have proper id set - if(f.type!=='request' || f.id===undefined) return; - // Execute callback and send response - var m = ln.prepare_message_for_response(callback(f.payload, run_id, f.from), f.id); - ln.nutella.mqtt_client.publish( padded_channel, m ); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - this.net.nutella.mqtt_client.subscribe( padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, this.net.nutella.appId, undefined); -}; - - - -// Utility function - -function extractRunId(app_id, mqtt_channel) { - var pc = '/nutella/apps/' + app_id + '/runs/'; - var sp = mqtt_channel.replace(pc, '').split('/'); - return sp[0]; -} - - -module.exports = AppNetSubModule; diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_persist.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_persist.js deleted file mode 100755 index ac9ee20..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_persist.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * App-level persistence APIs for nutella - */ - - - -var AppPersistSubModule = function(main_nutella) { - // Store a reference to the main module - this.main_nutella = main_nutella; -}; - - - -AppPersistSubModule.prototype.test = function () { - console.log("This is just a test method for the APP persist sub-module"); -}; - - - -module.exports = AppPersistSubModule; diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_core_browser.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_core_browser.js deleted file mode 100755 index 455093b..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_core_browser.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Framework-level APIs for nutella, browser version - */ - -// Require various sub-modules -var FrNetSubModule = require('./fr_net'); -var FrLogSubModule = require('./fr_log'); - - -var FrSubModule = function(main_nutella) { - // Initialized the various sub-modules - this.net = new FrNetSubModule(main_nutella); - this.log = new FrLogSubModule(main_nutella); -}; - - -module.exports = FrSubModule; \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_log.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_log.js deleted file mode 100755 index e187559..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_log.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Framework-level log APIs for nutella - */ - -var FrNetSubModule = require('./app_net'); - -var FrLogSubModule = function(main_nutella) { - this.net = new FrNetSubModule(main_nutella); -}; - - - -FrLogSubModule.prototype.debug = function(message, code) { - console.debug(message); - this.net.publish('logging', logToJson(message, code, 'debug')); - return code; -}; - -FrLogSubModule.prototype.info = function(message, code) { - console.info(message); - this.net.publish('logging', logToJson(message, code, 'info')); - return code; -}; - -FrLogSubModule.prototype.success = function(message, code) { - console.log('%c '+ message , 'color: #009933'); - this.net.publish('logging', logToJson(message, code, 'success')); - return code; -}; - -FrLogSubModule.prototype.warn = function(message, code) { - console.warn(message); - this.net.publish('logging', logToJson(message, code, 'warn')); - return code; -}; - -FrLogSubModule.prototype.error = function(message, code) { - console.error(message); - this.net.publish('logging', logToJson(message, code, 'error')); - return code; -}; - - -function logToJson( message, code, level) { - return (code === undefined) ? {level: level, message: message} : {level: level, message: message, code: code}; -} - - - -module.exports = FrLogSubModule; diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_net.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_net.js deleted file mode 100755 index d72dadd..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_net.js +++ /dev/null @@ -1,499 +0,0 @@ -/** - * Framework-level Networking APIs for nutella - */ - - -var AbstractNet = require('./util/net'); - - -/** - * Framework-level network APIs for nutella - * @param main_nutella - * @constructor - */ -var FRNetSubModule = function(main_nutella) { - this.net = new AbstractNet(main_nutella); -}; - - - -/** - * Subscribes to a channel or filter. - * - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe = function(channel, callback, done_callback) { - this.net.subscribe_to(channel, callback, undefined, undefined, done_callback); -}; - - -/** - * Unsubscribes from a channel - * - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe = function(channel, done_callback) { - this.net.unsubscribe_from(channel, undefined, undefined, done_callback); -}; - - -/** - * Publishes a message to a channel - * - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish = function(channel, message) { - this.net.publish_to(channel, message, undefined, undefined); -}; - - -/** - * Sends a request. - * - * @param channel - * @param message - * @param callback - */ -FRNetSubModule.prototype.request = function(channel, message, callback) { - this.net.request_to(channel, message, callback, undefined, undefined); -}; - - -/** - * Handles requests. - * - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.handle_requests = function(channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, undefined, undefined, done_callback); -}; - - - -//---------------------------------------------------------------------------------------------------------------- -// Framework-level APIs to communicate at the run-level to a specific run -//---------------------------------------------------------------------------------------------------------------- - -/** - * Allows framework-level APIs to subscribe to a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe_to_run = function(app_id, run_id, channel, callback,done_callback) { - this.net.subscribe_to(channel,callback,app_id,run_id,done_callback) -}; - - -/** - * Allows framework-level APIs to unsubscribe from a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe_to_run = function( app_id, run_id, channel, done_callback ) { - this.net.unsubscribe_from(channel, app_id, run_id, done_callback); -}; - - -/** - * Allows framework-level APIs to publish to a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish_to_run = function( app_id, run_id, channel, message ) { - this.net.publish_to(channel, message, app_id, run_id); -}; - - -/** - * Allows framework-level APIs to make an asynchronous request to a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param request - * @param callback - */ -FRNetSubModule.prototype.request_to_run = function( app_id, run_id, channel, request, callback) { - this.net.request_to(channel, request, callback, app_id, run_id); -}; - - -/** - * Allows framework-level APIs to handle requests on a run-level channel within a specific run - * - * @param app_id - * @param run_id - * @param channel - * @param callback - */ -FRNetSubModule.prototype.handle_requests_on_run = function( app_id, run_id, channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, app_id, run_id, done_callback) -}; - - - -//---------------------------------------------------------------------------------------------------------------- -// Framework-level APIs to communicate at the run-level (broadcast) -//---------------------------------------------------------------------------------------------------------------- - - -/** - * Callback for subscribing to all runs - * @callback allRunsCb - # @param {string} message - the received message. Messages that are not JSON are discarded - # @param {String} app_id - the app_id of the channel the message was sent on - # @param {String} run_id - the run_id of the channel the message was sent on - # @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - */ - -/** - * Allows framework-level APIs to subscribe to a run-level channel *for ALL runs* - * - * @param channel - * @param {allRunsCb} callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe_to_all_runs = function( channel, callback, done_callback ) { - //Pad channel - var padded_channel = this.net.pad_channel(channel, '+', '+'); - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var f1 = extractRunIdAndAppId(mqtt_channel); - if(f.type==='publish') - callback(f.payload, f1.appId, f1.runId, f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Add to subscriptions, save mqtt callback and subscribe - this.net.subscriptions.push(padded_channel); - this.net.callbacks.push(mqtt_cb); - this.net.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, undefined, undefined); -}; - - -/** - * Allows framework-level APIs to unsubscribe from a run-level channel *for ALL runs* - * - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe_from_all_runs = function(channel, done_callback) { - this.net.unsubscribe_from(channel, '+', '+', done_callback); -}; - - -/** - * Allows framework-level APIs to publish a message to a run-level channel *for ALL runs* - * - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish_to_all_runs = function( channel, message ) { - Object.keys(this.net.nutella.runs_list).forEach(function(app_id) { - this.net.nutella.runs_list[app_id].runs.forEach(function(run_id){ - this.net.publish_to(channel, message, app_id, run_id); - }.bind(this)); - }.bind(this)); -}; - - -/** - * Allows framework-level APIs to send a request to a run-level channel *for ALL runs* - * - * @param channel - * @param request - * @param callback - */ -FRNetSubModule.prototype.request_to_all_runs = function(channel, request, callback) { - Object.keys(this.net.nutella.runs_list).forEach(function(app_id) { - this.net.nutella.runs_list[app_id].runs.forEach(function(run_id){ - this.net.publish_to(channel, message, app_id, run_id); - this.net.request_to(channel, request, callback, app_id, run_id); - }.bind(this)); - }.bind(this)); -}; - -/** - * Callback that is used to handle messages from all runs - * @callback handle_all_runs_cb - * @param {string} payload - the received message (request). Messages that are not JSON are discarded - * @param {string} app_id - the app_id of the channel the request was sent on - * @param {string} run_id - the run_id of the channel the request was sent on - * @param {Object} from - the sender's identifiers (from containing, run_id, app_id, component_id and optionally resource_id) - * @return {Object} the response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - */ - -/** - * Allows framework-level APIs to handle requests to a run-level channel *for ALL runs* - * - * @param channel - * @param {handle_all_runs_cb} callback - * @param done_callback - */ -FRNetSubModule.prototype.handle_requests_on_all_runs = function(channel, callback, done_callback) { - // Pad channel - var padded_channel = this.net.pad_channel(channel, '+', '+'); - var ln = this.net; - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var f1 = extractRunIdAndAppId(mqtt_channel); - // Only handle requests that have proper id set - if(f.type!=='request' || f.id===undefined) return; - // Execute callback and send response - var m = ln.prepare_message_for_response(callback(f.payload, f1.appId, f1.runId, f.from), f.id); - ln.nutella.mqtt_client.publish( padded_channel, m ); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - this.net.nutella.mqtt_client.subscribe( padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, undefined, undefined); -}; - - - -//---------------------------------------------------------------------------------------------------------------- -// Framework-level APIs to communicate at the application-level -//---------------------------------------------------------------------------------------------------------------- - - -/** - * Allows framework-level APIs to subscribe to an app-level channel - * - * @param app_id - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe_to_app = function(app_id, channel, callback, done_callback) { - this.net.subscribe_to(channel,callback,app_id, undefined, done_callback) -}; - - -/** - * Allows framework-level APIs to unsubscribe from an app-level channel within a specific run - * - * @param app_id - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe_to_app = function( app_id, channel, done_callback) { - this.net.unsubscribe_from(channel,app_id,undefined, done_callback); -}; - - -/** - * Allows framework-level APIs to publish to an app-level channel - * - * @param app_id - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish_to_app = function(app_id, channel, message) { - this.net.publish_to(channel,message,app_id,undefined); -}; - - -/** - * Allows framework-level APIs to make an asynchronous request to a run-level channel within a specific run - * - * @param app_id - * @param channel - * @param request - * @param callback - */ -FRNetSubModule.prototype.request_to_app = function( app_id, channel, request, callback) { - this.net.request_to(channel, request, callback, app_id, undefined); -}; - - -/** - * Allows framework-level APIs to handle requests on a run-level channel within a specific run - * - * @param app_id - * @param channel - * @param callback - * @param done_callback - */ -FRNetSubModule.prototype.handle_requests_on_app = function(app_id, channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, app_id, undefined, done_callback); -}; - - -//---------------------------------------------------------------------------------------------------------------- -// Framework-level APIs to communicate at the application-level (broadcast) -//---------------------------------------------------------------------------------------------------------------- - -/** - * Callback used to handle all messages received when subscribing to all applications - * @callback subscribeToAllAppsCb - * @param {string} message - the received message. Messages that are not JSON are discarded - * @param {string} app_id - the app_id of the channel the message was sent on - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - */ - -/** - * Allows framework-level APIs to subscribe to an app-level channel *for ALL apps* - * - * @param channel - * @param {subscribeToAllAppsCb} callback - * @param done_callback - */ -FRNetSubModule.prototype.subscribe_to_all_apps = function(channel, callback, done_callback) { - //Pad channel - var padded_channel = this.net.pad_channel(channel, '+', undefined); - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var app_id = extractAppId(mqtt_channel); - if(f.type==='publish') - callback(f.payload, app_id, f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Add to subscriptions, save mqtt callback and subscribe - this.net.subscriptions.push(padded_channel); - this.net.callbacks.push(mqtt_cb); - this.net.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, undefined, undefined); -}; - - -/** - * Allows framework-level APIs to unsubscribe from an app-level channel *for ALL apps* - * - * @param channel - * @param done_callback - */ -FRNetSubModule.prototype.unsubscribe_from_all_apps = function(channel, done_callback) { - this.net.unsubscribe_from(channel, '+', undefined, done_callback); -}; - - -/** - * Allows framework-level APIs to publish a message to an app-level channel *for ALL apps* - * - * @param channel - * @param message - */ -FRNetSubModule.prototype.publish_to_all_apps = function(channel, message) { - Object.keys(this.net.nutella.runs_list).forEach(function(app_id) { - this.net.publish_to(channel, message, app_id, undefined); - }.bind(this)); -}; - - -/** - * Allows framework-level APIs to send a request to an app-level channel *for ALL apps* - * - * @param channel - * @param request - * @param callback - */ -FRNetSubModule.prototype.request_to_all_apps = function(channel, request, callback) { - Object.keys(this.net.nutella.runs_list).forEach(function(app_id) { - this.net.request_to(channel, request, callback, app_id, undefined); - }.bind(this)); -}; - - -/** - * This callback is used to handle messages coming from all applications - * @callback handleAllAppsCb - * @param {string} request - the received message (request). Messages that are not JSON are discarded. - * @param {string} app_id - the app_id of the channel the request was sent on - * @param {Object} from - the sender's identifiers (from containing, run_id, app_id, component_id and optionally resource_id) - * @return {Object} The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - */ - -/** - * Allows framework-level APIs to handle requests to app-level channel *for ALL runs* - * - * @param channel - * @param {handleAllAppsCb} callback - * @param done_callback - */ -FRNetSubModule.prototype.handle_requests_on_all_apps = function(channel, callback, done_callback) { - // Pad channel - var padded_channel = this.net.pad_channel(channel, '+', undefined); - var ln = this.net; - var mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - var f1 = extractRunIdAndAppId(mqtt_channel); - // Only handle requests that have proper id set - if(f.type!=='request' || f.id===undefined) return; - // Execute callback and send response - var m = ln.prepare_message_for_response(callback(f.payload, f1.appId, f1.runId, f.from), f.id); - ln.nutella.mqtt_client.publish( padded_channel, m ); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - this.net.nutella.mqtt_client.subscribe( padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.net.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, undefined, undefined); -}; - - -// Utility functions - - -function extractRunIdAndAppId(mqtt_channel) { - var sp = mqtt_channel.replace('/nutella/apps/', '').split('/'); - return {appId: sp[0], runId: sp[2]}; -} - -function extractAppId(mqtt_channel) { - var sp = mqtt_channel.replace('/nutella/apps/', '').split('/'); - return sp[0]; -} - - - - -module.exports = FRNetSubModule; diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i.js deleted file mode 100755 index d31e412..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Run-level and App-level Nutella instances for node - */ - -var SimpleMQTTClient = require('simple-mqtt-client'); - -// Require various sub-modules -var AppSubModule = require('./app_core'); -var NetSubModule = require('./run_net'); -var LogSubModule = require('./run_log'); -var PersistSubModule = require('./run_persist'); - - -/** - * Defines the RunNutellaInstance class. - * - * @param {String } app_id - the app_id this component belongs to - * @param {string} run_id - the run_id this component is launched in - * @param {string} broker_hostname - the hostname of the broker. - * @param {string} component_id - the name of this component - */ -var RunNutellaInstance = function (broker_hostname, app_id, run_id, component_id) { - //Initialize parameters - this.mqtt_client = new SimpleMQTTClient(broker_hostname); - this.appId = app_id; - this.runId = run_id; - this.componentId = component_id; - // Initialized the various sub-modules - this.net = new NetSubModule(this); - this.log = new LogSubModule(this); - this.persist = new PersistSubModule(this); -}; - -/** - * Sets the resource id for this instance of nutella - * - * @param {string} resource_id - the resource_id associated to this instance of nutella - */ -RunNutellaInstance.prototype.setResourceId = function(resource_id){ - this.resourceId = resource_id; -}; - - - -/** - * Defines the AppNutellaInstance class. - * - * @param {String } app_id - the app_id this component belongs to - * @param {string} broker_hostname - the hostname of the broker. - * @param {string} component_id - the name of this component - */ -var AppNutellaInstance = function (broker_hostname, app_id, component_id) { - //Initialize parameters - this.mqtt_client = new SimpleMQTTClient(broker_hostname); - this.appId = app_id; - this.componentId = component_id; - // Initialized the various sub-modules - this.app = new AppSubModule(this); -}; - -/** - * Sets the resource id for this instance of nutella - * - * @param {string} resource_id - the resource_id associated to this instance of nutella - */ -AppNutellaInstance.prototype.setResourceId = function(resource_id){ - this.resourceId = resource_id; -}; - - -module.exports = { - RunNutellaInstance : RunNutellaInstance, - AppNutellaInstance : AppNutellaInstance -}; \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i_browser.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i_browser.js deleted file mode 100755 index 8d93f4f..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i_browser.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Run-level and App-level Nutella instances for the browser - */ - -var SimpleMQTTClient = require('simple-mqtt-client'); - -// Require various sub-modules -var AppSubModule = require('./app_core_browser'); -var FrSubModule = require('./fr_core_browser'); -var NetSubModule = require('./run_net'); -var LogSubModule = require('./run_log'); - - -/** - * Defines the RunNutellaInstance class. - * - * @param {String } app_id - the app_id this component belongs to - * @param {string} run_id - the run_id this component is launched in - * @param {string} broker_hostname - the hostname of the broker. - * @param {string} component_id - the name of this component - */ -var RunNutellaInstance = function (broker_hostname, app_id, run_id, component_id) { - //Initialize parameters - this.mqtt_client = new SimpleMQTTClient(broker_hostname); - this.appId = app_id; - this.runId = run_id; - this.componentId = component_id; - // Initialized the various sub-modules - this.net = new NetSubModule(this); - this.log = new LogSubModule(this); - // Start pinging - setInterval(function(){ - this.net.publish('pings', 'ping'); - }.bind(this),5000); -}; - -/** - * Sets the resource id for this instance of nutella - * - * @param {string} resource_id - the resource_id associated to this instance of nutella - */ -RunNutellaInstance.prototype.setResourceId = function(resource_id){ - this.resourceId = resource_id; -}; - - -/** - * Defines the AppNutellaInstance class. - * - * @param {String } app_id - the app_id this component belongs to - * @param {string} broker_hostname - the hostname of the broker. - * @param {string} component_id - the name of this component - */ -var AppNutellaInstance = function (broker_hostname, app_id, component_id) { - //Initialize parameters - this.mqtt_client = new SimpleMQTTClient(broker_hostname); - this.appId = app_id; - this.componentId = component_id; - // Initialized the various sub-modules - this.app = new AppSubModule(this); - //Initialize the runs list - this.runs_list = []; - // Fetch the runs list - this.app.net.request('app_runs_list', undefined, function(response) { - this.runs_list = response; - }.bind(this)); - // Subscribe to runs list updates - this.app.net.subscribe('app_runs_list', function(message, from) { - this.runs_list = message; - }.bind(this)); - // Start pinging - setInterval(function(){ - this.app.net.publish('pings', 'ping'); - }.bind(this),5000); -}; - -/** - * Sets the resource id for this instance of nutella - * - * @param {string} resource_id - the resource_id associated to this instance of nutella - */ -AppNutellaInstance.prototype.setResourceId = function(resource_id){ - this.resourceId = resource_id; -}; - - -/** - * Defines the FRNutellaInstance class. - * - * @param {string} broker_hostname - the hostname of the broker. - * @param {string} component_id - the name of this component - */ -var FrNutellaInstance = function (broker_hostname, component_id) { - //Initialize parameters - this.mqtt_client = new SimpleMQTTClient(broker_hostname); - this.componentId = component_id; - // Initialize the various sub-modules - this.f = new FrSubModule(this); - //Initialize the runs list - this.runs_list = {}; - // Fetch the runs list - this.f.net.request('runs_list', undefined, function(response) { - this.runs_list = response; - }.bind(this)); - // Subscribe to runs list updates - this.f.net.subscribe('runs_list', function(message, from) { - this.runs_list = message; - }.bind(this)); - // Start pinging - setInterval(function(){ - this.f.net.publish('pings', 'ping'); - }.bind(this),5000); -}; - -/** - * Sets the resource id for this instance of nutella - * - * @param {string} resource_id - the resource_id associated to this instance of nutella - */ -FrNutellaInstance.prototype.setResourceId = function(resource_id){ - this.resourceId = resource_id; -}; - - - -module.exports = { - RunNutellaInstance : RunNutellaInstance, - AppNutellaInstance : AppNutellaInstance, - FrNutellaInstance : FrNutellaInstance -}; \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib.js deleted file mode 100755 index 49b648c..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib.js +++ /dev/null @@ -1,91 +0,0 @@ -/****************** - * nutella_lib.js * - ******************/ - -"use strict"; - -/** - * Entry point for nutella_lib in node - */ - -var nutella_i = require('./nutella_i'); - -// Internal reference to this library (used below) -var nutella = {}; - - -/** - * Creates a new instance of nutella - * and initialize it. This is a factory method. - * - * @param {string} broker_hostname - the hostname of the broker.* - * @param {string} app_id - the app_id this component belongs to - * @param {string} run_id - the run_id this component is launched in - * @param {string} component_id - the name of this component - */ -nutella.init = function(broker_hostname, app_id, run_id, component_id) { - if (broker_hostname===undefined || app_id===undefined || run_id===undefined || component_id=== undefined) { - console.warn("Couldn't initialize nutella. Make sure you are setting all four the required parameters (broker_hostname, app_id, run_id, component_id)"); - } - return new nutella_i.RunNutellaInstance(broker_hostname, app_id, run_id, component_id); -}; - - -/** - * Creates a new instance of nutella - * and initialize it for an app-level component. - * This is a factory method. - * - * @param {string} broker_hostname - the hostname of the broker.* - * @param {string} app_id - the app_id this component belongs to - * @param {string} run_id - the run_id this component is launched in - * @param {string} component_id - the name of this component - */ -nutella.initApp = function(broker_hostname, app_id, component_id) { - if (broker_hostname===undefined || app_id===undefined || component_id=== undefined) { - console.warn("Couldn't initialize nutella. Make sure you are setting all three the required parameters (broker_hostname, app_id, run_id, component_id)"); - } - return new nutella_i.AppNutellaInstance(broker_hostname, app_id, component_id); -}; - - -/** - * Utility method that parses CLI parameters. - * It is obviously only available in node. - * - * @return {Object} An object containing all the CLI parameters: broker, app_id, run_id - */ -nutella.parseArgs = function() { - if (process.argv.length<5) { - console.warn("Couldn't read broker address, app_id and run_id from the command line, you might have troubles initializing nutella") - return; - } - var t = {}; - t.broker = process.argv[2]; - t.app_id = process.argv[3]; - t.run_id = process.argv[4]; - return t; -}; - - -/** - * Utility method that parses CLI parameters for - * application level components. - * It is obviously only available in node. - * - * @return {Object} An object containing all the CLI parameters: broker, app_id, run_id - */ -nutella.parseAppComponentArgs = function() { - if (process.argv.length<4) { - console.warn("Couldn't read broker address and app_id from the command line, you might have troubles initializing nutella") - return; - } - var t = {}; - t.broker = process.argv[2]; - t.app_id = process.argv[3]; - return t; -}; - - -// Exports nutella object -module.exports = nutella; \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib_browser.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib_browser.js deleted file mode 100755 index c90f4e7..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib_browser.js +++ /dev/null @@ -1,90 +0,0 @@ -/****************** - * nutella_lib.js * - ******************/ - -"use strict"; - -/** - * Entry point for nutella_lib in the browser - */ - -var nutella_i = require('./nutella_i_browser'); - -// Internal reference to this library (used below) -var nutella = {}; - - -/** - * Creates a new instance of nutella - * and initialize it. This is a factory method. - * - * @param {string} broker_hostname - the hostname of the broker.* - * @param {string} app_id - the app_id this component belongs to - * @param {string} run_id - the run_id this component is launched in - * @param {string} component_id - the name of this component - */ -nutella.init = function(broker_hostname, app_id, run_id, component_id) { - if (broker_hostname===undefined || app_id===undefined || run_id===undefined || component_id=== undefined) { - console.warn("Couldn't initialize nutella. Make sure you are setting all four required parameters (broker_hostname, app_id, run_id, component_id)"); - } - return new nutella_i.RunNutellaInstance(broker_hostname, app_id, run_id, component_id); -}; - - -/** - * Creates a new instance of nutella - * and initialize it for an app-level component. - * This is a factory method. - * - * @param {string} broker_hostname - the hostname of the broker.* - * @param {string} app_id - the app_id this component belongs to - * @param {string} component_id - the name of this component - */ -nutella.initApp = function(broker_hostname, app_id, component_id) { - if (broker_hostname===undefined || app_id===undefined || component_id=== undefined) { - console.warn("Couldn't initialize nutella. Make sure you are setting all three required parameters (broker_hostname, app_id, component_id)"); - } - return new nutella_i.AppNutellaInstance(broker_hostname, app_id, component_id); -}; - - -/** - * Creates a new instance of nutella - * and initialize it for a framework-level component. - * This is a factory method. - * - * @param {string} broker_hostname - the hostname of the broker.* - * @param {string} component_id - the name of this component - */ -nutella.initFramework = function(broker_hostname, component_id) { - if (broker_hostname===undefined || component_id=== undefined) { - console.warn("Couldn't initialize nutella. Make sure you are setting all two required parameters (broker_hostname, component_id)"); - } - return new nutella_i.FrNutellaInstance(broker_hostname, component_id); -}; - - - -/** - * Utility method that parses URL parameters from the URL. - * It is obviously only available in the browser. - * - * @return {Object} An object containing all the URL query parameters - */ -nutella.parseURLParameters = function () { - var str = location.search; - var queries = str.replace(/^\?/, '').split('&'); - var searchObject = {}; - for( var i = 0; i < queries.length; i++ ) { - var split = queries[i].split('='); - searchObject[split[0]] = split[1]; - } - return searchObject; -}; - - -nutella.version = '0.4.3'; - - -// Exports nutella object -module.exports = nutella; \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_log.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_log.js deleted file mode 100755 index 9d80860..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_log.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Run-level Logging APIs for nutella - */ - -var NetSubModule = require('./run_net'); - -var LogSubModule = function(main_nutella) { - this.net = new NetSubModule(main_nutella); -}; - - -LogSubModule.prototype.debug = function(message, code) { - console.debug(message); - this.net.publish('logging', logToJson(message, code, 'debug')); - return code; -}; - -LogSubModule.prototype.info = function(message, code) { - console.info(message); - this.net.publish('logging', logToJson(message, code, 'info')); - return code; -}; - -LogSubModule.prototype.success = function(message, code) { - console.log('%c '+ message , 'color: #009933'); - this.net.publish('logging', logToJson(message, code, 'success')); - return code; -}; - -LogSubModule.prototype.warn = function(message, code) { - console.warn(message); - this.net.publish('logging', logToJson(message, code, 'warn')); - return code; -}; - -LogSubModule.prototype.error = function(message, code) { - console.error(message); - this.net.publish('logging', logToJson(message, code, 'error')); - return code; -}; - - -function logToJson( message, code, level) { - return (code===undefined) ? {level: level, message: message} : {level: level, message: message, code: code}; -} - - - - - -module.exports = LogSubModule; \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_net.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_net.js deleted file mode 100755 index 08f8d7e..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_net.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Run-level Network APIs for nutella - */ - - -var AbstractNet = require('./util/net'); - - -/** - * Run-level network APIs for nutella - * @param main_nutella - * @constructor - */ -var NetSubModule = function(main_nutella) { - // Store a reference to the main module - this.nutella = main_nutella; - this.net = new AbstractNet(main_nutella); -}; - - - -/** - * Subscribes to a channel or filter. - * - * @param channel - * @param callback - * @param done_callback - */ -NetSubModule.prototype.subscribe = function(channel, callback, done_callback) { - this.net.subscribe_to(channel, callback, this.nutella.appId, this.nutella.runId, done_callback); -}; - - - -/** - * Unsubscribes from a channel - * - * @param channel - * @param done_callback - */ -NetSubModule.prototype.unsubscribe = function(channel, done_callback) { - this.net.unsubscribe_from(channel, this.nutella.appId, this.nutella.runId, done_callback); -}; - - - -/** - * Publishes a message to a channel - * - * @param channel - * @param message - */ -NetSubModule.prototype.publish = function(channel, message) { - this.net.publish_to(channel, message, this.nutella.appId, this.nutella.runId); -}; - - - -/** - * Sends a request. - * - * @param channel - * @param message - * @param callback - */ -NetSubModule.prototype.request = function(channel, message, callback) { - this.net.request_to(channel, message, callback, this.nutella.appId, this.nutella.runId); -}; - - - -/** - * Handles requests. - * - * @param channel - * @param callback - * @param done_callback - */ -NetSubModule.prototype.handle_requests = function(channel, callback, done_callback) { - this.net.handle_requests_on(channel, callback, this.nutella.appId, this.nutella.runId, done_callback); -}; - - -module.exports = NetSubModule; diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_persist.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_persist.js deleted file mode 100755 index 704a6cf..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_persist.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Run-level persistence APIs for nutella - */ - - - -var PersistSubModule = function(main_nutella) { - // Store a reference to the main module - this.main_nutella = main_nutella; -}; - - - -PersistSubModule.prototype.test = function () { - console.log("This is just a test method for the persist sub-module"); -}; - - - -module.exports = PersistSubModule; diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/util/net.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/util/net.js deleted file mode 100755 index e0547aa..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/util/net.js +++ /dev/null @@ -1,327 +0,0 @@ -/** - * Network APIs abstraction - */ - - -var AbstractNet = function(main_nutella) { - this.subscriptions = []; - this.callbacks = []; - this.nutella = main_nutella; -}; - - -/** - * This callback is fired whenever a message is received by the subscribe - * - * @callback subscribeCallback - * @param {string} message - the received message. Messages that are not JSON are discarded - * @param {string} [channel] - the channel the message was received on (optional, only for wildcard subscriptions) - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - */ - -/** - * Subscribes to a channel or to a set of channels - * - * @param {string} channel - the channel or filter we are subscribing to. Can contain wildcard(s) - * @param {subscribeCallback} callback - fired whenever a message is received - * @param {string|undefined} appId - used to pad channels - * @param {string|undefined} runId - used to pad channels - * @param {function} done_callback - fired whenever the subscribe is successful - */ -AbstractNet.prototype.subscribe_to = function(channel, callback, appId, runId, done_callback) { - // Pad channel - var padded_channel = this.pad_channel(channel, appId, runId); - // Maintain unique subscriptions - if(this.subscriptions.indexOf(padded_channel)>-1) - throw 'You can`t subscribe twice to the same channel`'; - // Depending on what type of channel we are subscribing to (wildcard or simple) - // register a different kind of callback - var mqtt_cb; - if(this.nutella.mqtt_client.isChannelWildcard(padded_channel)) - mqtt_cb = function(mqtt_message, mqtt_channel) { - try { - var f = JSON.parse(mqtt_message); - if(f.type==='publish') - callback(f.payload, this.un_pad_channel(mqtt_channel, appId, runId), f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - else - mqtt_cb = function(mqtt_message) { - try { - var f = JSON.parse(mqtt_message); - if(f.type==='publish') - callback(f.payload, f.from); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Add to subscriptions, save mqtt callback and subscribe - this.subscriptions.push(padded_channel); - this.callbacks.push(mqtt_cb); - this.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, appId, runId); -}; - - -/** - * Unsubscribes from a channel or a set of channels - * - * @param {string} channel - we want to unsubscribe from. Can contain wildcard(s) - * @param {string|undefined} appId - used to pad channels - * @param {string|undefined} runId - used to pad channels - * @param {function} done_callback - fired whenever the subscribe is successful - */ -AbstractNet.prototype.unsubscribe_from = function(channel, appId, runId, done_callback ) { - // Pad channel - var padded_channel = this.pad_channel(channel, appId, run_id); - var idx = this.subscriptions.indexOf(padded_channel); - // If we are not subscribed to this channel, return (no error is given) - if(idx===-1) return; - // Fetch the mqtt_callback associated with this channel/subscription - var mqtt_cb = this.callbacks[idx]; - // Remove from subscriptions, callbacks and unsubscribe - this.subscriptions.splice(idx, 1); - this.callbacks.splice(idx, 1); - this.nutella.mqtt_client.unsubscribe(padded_channel, mqtt_cb, done_callback); -}; - - -/** - * Publishes a message to a channel - * - * @param {String} channel - the channel we want to publish the message to. *CANNOT* contain wildcard(s)! - * @param {Object} message - the message we are publishing. This can be any JS variable, even undefined. - * @param {String|undefined} appId - used to pad the channels - * @param {String|undefined} runId - used to pad the channels - */ -AbstractNet.prototype.publish_to = function(channel, message, appId, runId) { - // Pad channel - var padded_channel = this.pad_channel(channel, appId, runId); - // Throw exception if trying to publish something that is not JSON - try { - var m = this.prepare_message_for_publish(message); - this.nutella.mqtt_client.publish(padded_channel, m); - } catch(e) { - console.error('Error: you are trying to publish something that is not JSON'); - console.error(e.message); - } -}; - - -/** - * This callback is fired whenever a response to a request is received - * - * @callback requestCallback - * @param {string} response - the body of the response - */ - -/** - * Performs an asynchronous request - * - * @param {string} channel - the channel we want to make the request to. *CANNOT* contain wildcard(s)! - * @param {string} message - the body of the request. This can be any JS variable, even undefined. - * @param {requestCallback} callback - the callback that is fired whenever a response is received - * @param {string|undefined} appId - used to pad channels - * @param {string|undefined} runId - used to pad channels - */ -AbstractNet.prototype.request_to = function( channel, message, callback, appId, runId ) { - // Save nutella reference - var nut = this.nutella; - // Pad channel - var padded_channel = this.pad_channel(channel, appId, runId); - // Prepare message - var m = this.prepare_message_for_request(message); - //Prepare callback - var mqtt_cb = function(mqtt_message) { - var f = JSON.parse(mqtt_message); - if (f.id===m.id && f.type==='response') { - callback(f.payload); - nut.mqtt_client.unsubscribe(padded_channel, mqtt_cb); - } - }; - // Subscribe - this.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, function() { - // Publish message - nut.mqtt_client.publish( padded_channel, m.message ); - }); - -}; - - -/** - * This callback is fired whenever a request is received that needs to be handled - * - * @callback handleCallback - * @param {string} request - the body of the received request (payload). Messages that are not JSON are discarded. - * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - * @return {Object} The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - */ - -/** - * Handles requests on a certain channel or a certain set of channels - * - * @param {string} channel - the channel we want to listen for requests on. Can contain wildcard(s). - * @param {handleCallback} callback - fired whenever a message is received - * @param {string|undefined} appId - used to pad channels - * @param {string|undefined} runId - used to pad channels - * @param {function} done_callback - fired whenever we are ready to handle requests - */ -AbstractNet.prototype.handle_requests_on = function( channel, callback, appId, runId, done_callback) { - // Save nutella reference - var nut = this.nutella; - // Pad channel - var padded_channel = this.pad_channel(channel, appId, runId); - var mqtt_cb = function(request) { - try { - // Extract nutella fields - var f = JSON.parse(request); - // Only handle requests that have proper id set - if(f.type!=='request' || f.id===undefined) return; - // Execute callback and send response - var m = this.prepare_message_for_response(callback(f.payload, f.from), f.id); - nut.mqtt_client.publish( padded_channel, m ); - } catch(e) { - if (e instanceof SyntaxError) { - // Message is not JSON, drop it - } else { - // Bubble up whatever exception is thrown - throw e; - } - } - }; - // Subscribe to the channel - this.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback); - // Notify subscription - this.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, appId, runId); -}; - - - -/** - * Pads the channel with app_id and run_id - * - * @param channel - * @param app_id - * @param run_id - * @return {string} the padded channel - */ -AbstractNet.prototype.pad_channel = function(channel, app_id, run_id) { - if (run_id!==undefined && app_id===undefined) - throw 'If the run_id is specified, app_id needs to also be specified'; - if (app_id===undefined && run_id===undefined) - return '/nutella/' + channel; - if (app_id!==undefined && run_id===undefined) - return '/nutella/apps/' + app_id + '/' + channel; - return '/nutella/apps/' + app_id + '/runs/' + run_id + '/' + channel; -}; - - -/** - * Un-pads the channel with app_id and run_id - * - * @param channel - * @param app_id - * @param run_id - * @return {string} the un-padded channel - */ -AbstractNet.prototype.un_pad_channel = function(channel, app_id, run_id) { - if (run_id!==undefined && app_id===undefined) - throw 'If the run_id is specified, app_id needs to also be specified'; - if (app_id===undefined && run_id===undefined) - return channel.replace('/nutella/', ''); - if (app_id!==undefined && run_id===undefined) - return channel.replace("/nutella/apps/" + app_id + "/", ''); - return channel.replace("/nutella/apps/" + app_id + "/runs/" + run_id + "/", ''); -}; - - -/** - * Assembles the unique ID of the component, starting from app_id, run_id, component_id and resource_id - * - * @return {Object} an object containing the unique ID of the component sending the message - */ -AbstractNet.prototype.assemble_from = function() { - var from = {}; - // Set type, run_id and app_id whenever appropriate - if(this.nutella.runId===undefined) { - if(this.nutella.appId===undefined) { - from.type = 'framework'; - } else { - from.type = 'app'; - from.app_id = this.nutella.appId; - } - } else { - from.type = 'run'; - from.app_id = this.nutella.appId; - from.run_id = this.nutella.runId; - } - // Set the component_id - from.component_id = this.nutella.componentId; - // Set resource_id, if defined - if (this.nutella.resourceId!==undefined) - from.resource_id = this.nutella.resourceId; - return from; -}; - - -/** - * Prepares a message for a publish - * - * @param {Object} message - the message content - * @return {string} the serialized message, ready to be shipped over the net - */ -AbstractNet.prototype.prepare_message_for_publish = function (message) { - if(message===undefined) - return JSON.stringify({type: 'publish', from: this.assemble_from()}); - return JSON.stringify({type: 'publish', from: this.assemble_from(), payload: message}); -}; - - -/** - * Prepares a message for a request - * - * @param {Object} message - the message content - * @return {Object} the serialized response, ready to be shipped over the net and the id of the response - */ -AbstractNet.prototype.prepare_message_for_request = function (message) { - var id = Math.floor((Math.random() * 100000) + 1).toString(); - var m = {}; - m.id = id; - if(message===undefined) - m.message = JSON.stringify({id: id, type: 'request', from: this.assemble_from()}); - else - m.message = JSON.stringify({id: id, type: 'request', from: this.assemble_from(), payload: message}); - return m; -}; - - -/** - * Prepares a message for a response - * - * @param {Object} response - the response content - * @param {string} id - the original request id - * @return {string} the serialized message, ready to be shipped over the net - */ -AbstractNet.prototype.prepare_message_for_response = function (response, id) { - if(response===undefined) - return JSON.stringify({id: id, type: 'response', from: this.assemble_from()}); - return JSON.stringify({id: id, type: 'response', from: this.assemble_from(), payload: response}); -}; - - - -// Export module -module.exports = AbstractNet; \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/nutella.test.js b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/nutella.test.js deleted file mode 100755 index 9b9c5e1..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/nutella.test.js +++ /dev/null @@ -1,16 +0,0 @@ -if( typeof NUTELLA === 'undefined' ) { - var NUTELLA = require('../src/nutella_lib'); - var assert = require('chai').assert; -} else { - var assert = chai.assert; -} - - - -describe('Nutella', function(){ - describe('NUTELLA', function(){ - it('should return defined when called', function(){ - assert.notEqual(undefined, NUTELLA, 'NUTELLA is undefined!'); - }) - }) -}) \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/runner.html b/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/runner.html deleted file mode 100755 index 3016718..0000000 --- a/example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/runner.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - Nutella Tests - - - -

- - - - - - - - \ No newline at end of file diff --git a/example_framework_components/example_framework_web_interface/package.json b/example_framework_components/example_framework_web_interface/package.json deleted file mode 100755 index 4752b78..0000000 --- a/example_framework_components/example_framework_web_interface/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "example_framework_web_interface", - "version": "0.1.0", - "description": "An example framework interface", - "main": "index.html", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Alessandro Gnoli", - "license": "MIT", - "private": true, - "dependencies": { - "nutella_lib": "*" - } -} From 590730eb59c9739c7dec48479e3d31c0e33c1f1b Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 28 Sep 2019 17:44:00 -0700 Subject: [PATCH 03/43] Refactor CLI and commands, commit before switcihing to rspec --- Gemfile | 8 ++-- lib/{ => cli}/commands/broker.rb | 0 lib/{ => cli}/commands/checkup.rb | 0 lib/{ => cli}/commands/compile.rb | 0 lib/{ => cli}/commands/dependencies.rb | 0 lib/{ => cli}/commands/help.rb | 0 lib/{ => cli}/commands/install.rb | 0 lib/{ => cli}/commands/meta/command.rb | 0 lib/{ => cli}/commands/meta/run_command.rb | 0 .../commands/meta/template_command.rb | 0 lib/{ => cli}/commands/new.rb | 0 lib/{ => cli}/commands/reset.rb | 0 lib/{ => cli}/commands/runs.rb | 0 lib/{ => cli}/commands/start.rb | 0 lib/{ => cli}/commands/stop.rb | 0 lib/{ => cli}/commands/template.rb | 0 .../commands/util/components_list.rb | 0 .../commands/util/components_starter.rb | 0 lib/{core => cli}/nutella_cli.rb | 31 +++++++++++++- lib/config/config.rb | 11 ----- lib/config_files_management/config.rb | 21 ++++++++++ .../current_app_utils.rb | 0 .../persisted_hash.rb | 0 .../runlist.rb | 0 lib/core/nutella_core.rb | 41 ------------------- lib/nutella_framework.rb | 26 ++++++------ test/config_files_management/test_config.rb | 34 +++++++++++++++ .../test_current_app_utils.rb | 0 .../test_persisted_hash.rb | 0 .../test_runlist.rb | 0 30 files changed, 102 insertions(+), 70 deletions(-) rename lib/{ => cli}/commands/broker.rb (100%) rename lib/{ => cli}/commands/checkup.rb (100%) rename lib/{ => cli}/commands/compile.rb (100%) rename lib/{ => cli}/commands/dependencies.rb (100%) rename lib/{ => cli}/commands/help.rb (100%) rename lib/{ => cli}/commands/install.rb (100%) rename lib/{ => cli}/commands/meta/command.rb (100%) rename lib/{ => cli}/commands/meta/run_command.rb (100%) rename lib/{ => cli}/commands/meta/template_command.rb (100%) rename lib/{ => cli}/commands/new.rb (100%) rename lib/{ => cli}/commands/reset.rb (100%) rename lib/{ => cli}/commands/runs.rb (100%) rename lib/{ => cli}/commands/start.rb (100%) rename lib/{ => cli}/commands/stop.rb (100%) rename lib/{ => cli}/commands/template.rb (100%) rename lib/{ => cli}/commands/util/components_list.rb (100%) rename lib/{ => cli}/commands/util/components_starter.rb (100%) rename lib/{core => cli}/nutella_cli.rb (60%) delete mode 100755 lib/config/config.rb create mode 100755 lib/config_files_management/config.rb rename lib/{config => config_files_management}/current_app_utils.rb (100%) rename lib/{config => config_files_management}/persisted_hash.rb (100%) rename lib/{config => config_files_management}/runlist.rb (100%) delete mode 100755 lib/core/nutella_core.rb create mode 100644 test/config_files_management/test_config.rb rename test/{config => config_files_management}/test_current_app_utils.rb (100%) rename test/{config => config_files_management}/test_persisted_hash.rb (100%) rename test/{config => config_files_management}/test_runlist.rb (100%) diff --git a/Gemfile b/Gemfile index 66824be..e44e70c 100755 --- a/Gemfile +++ b/Gemfile @@ -13,14 +13,14 @@ gem 'activesupport', '~>4.2' gem 'bson', '~> 3.0' group :development do - gem 'shoulda', '~> 3.0' gem 'yard', '~> 0.9.11' gem 'rdoc', '~> 4.0' - gem 'bundler', '~> 1.0' - gem 'jeweler', '~> 2.0' - gem 'simplecov', '~> 0.9' + gem 'bundler', '~> 2.0' + gem 'jeweler', '~> 2.3' + gem 'simplecov', '~> 0.17' end group :test do gem 'rake' + gem 'rspec', '~> 3.8' end diff --git a/lib/commands/broker.rb b/lib/cli/commands/broker.rb similarity index 100% rename from lib/commands/broker.rb rename to lib/cli/commands/broker.rb diff --git a/lib/commands/checkup.rb b/lib/cli/commands/checkup.rb similarity index 100% rename from lib/commands/checkup.rb rename to lib/cli/commands/checkup.rb diff --git a/lib/commands/compile.rb b/lib/cli/commands/compile.rb similarity index 100% rename from lib/commands/compile.rb rename to lib/cli/commands/compile.rb diff --git a/lib/commands/dependencies.rb b/lib/cli/commands/dependencies.rb similarity index 100% rename from lib/commands/dependencies.rb rename to lib/cli/commands/dependencies.rb diff --git a/lib/commands/help.rb b/lib/cli/commands/help.rb similarity index 100% rename from lib/commands/help.rb rename to lib/cli/commands/help.rb diff --git a/lib/commands/install.rb b/lib/cli/commands/install.rb similarity index 100% rename from lib/commands/install.rb rename to lib/cli/commands/install.rb diff --git a/lib/commands/meta/command.rb b/lib/cli/commands/meta/command.rb similarity index 100% rename from lib/commands/meta/command.rb rename to lib/cli/commands/meta/command.rb diff --git a/lib/commands/meta/run_command.rb b/lib/cli/commands/meta/run_command.rb similarity index 100% rename from lib/commands/meta/run_command.rb rename to lib/cli/commands/meta/run_command.rb diff --git a/lib/commands/meta/template_command.rb b/lib/cli/commands/meta/template_command.rb similarity index 100% rename from lib/commands/meta/template_command.rb rename to lib/cli/commands/meta/template_command.rb diff --git a/lib/commands/new.rb b/lib/cli/commands/new.rb similarity index 100% rename from lib/commands/new.rb rename to lib/cli/commands/new.rb diff --git a/lib/commands/reset.rb b/lib/cli/commands/reset.rb similarity index 100% rename from lib/commands/reset.rb rename to lib/cli/commands/reset.rb diff --git a/lib/commands/runs.rb b/lib/cli/commands/runs.rb similarity index 100% rename from lib/commands/runs.rb rename to lib/cli/commands/runs.rb diff --git a/lib/commands/start.rb b/lib/cli/commands/start.rb similarity index 100% rename from lib/commands/start.rb rename to lib/cli/commands/start.rb diff --git a/lib/commands/stop.rb b/lib/cli/commands/stop.rb similarity index 100% rename from lib/commands/stop.rb rename to lib/cli/commands/stop.rb diff --git a/lib/commands/template.rb b/lib/cli/commands/template.rb similarity index 100% rename from lib/commands/template.rb rename to lib/cli/commands/template.rb diff --git a/lib/commands/util/components_list.rb b/lib/cli/commands/util/components_list.rb similarity index 100% rename from lib/commands/util/components_list.rb rename to lib/cli/commands/util/components_list.rb diff --git a/lib/commands/util/components_starter.rb b/lib/cli/commands/util/components_starter.rb similarity index 100% rename from lib/commands/util/components_starter.rb rename to lib/cli/commands/util/components_starter.rb diff --git a/lib/core/nutella_cli.rb b/lib/cli/nutella_cli.rb similarity index 60% rename from lib/core/nutella_cli.rb rename to lib/cli/nutella_cli.rb index 6e124fc..92752fd 100755 --- a/lib/core/nutella_cli.rb +++ b/lib/cli/nutella_cli.rb @@ -1,3 +1,10 @@ +# Require all commands by iterating through all the files +# in the commands directory +Dir["#{File.dirname(__FILE__)}/../commands/*.rb"].each do |file| + # noinspection RubyResolve + require "cli/commands/#{File.basename(file, File.extname(file))}" +end + module Nutella class NutellaCLI @@ -27,15 +34,35 @@ def self.run # If nutella is not ready to be used (i.e. nobody has invoked the "nutella checkup" command yet), # append warning/reminder message - if Nutella.config['ready'].nil? && command!='checkup' + if Config.file['ready'].nil? && command!='checkup' console.warn 'Looks like this is a fresh installation of nutella. Please run \'nutella checkup\' to check all dependencies are installed.' end # Execute the appropriate command - Nutella.execute_command command, args + execute_command(command, args) exit 0 end + # This method executes a particular command + # @param command [String] the name of the command + # @param args [Array] command line parameters passed to the command + def self.execute_command (command, args=nil) + # Check that the command exists and if it does, + # execute its run method passing the args parameters + if command_exists?(command) + Object::const_get("Nutella::#{command.capitalize}").new.run(args) + else + console.error "Unknown command #{command}" + end + end + + # This method checks that a particular command exists + # @return [Boolean] true if the command exists, false otherwise + def self.command_exists?(command) + return Nutella.const_get("Nutella::#{command.capitalize}").is_a?(Class) + rescue NameError + return false + end # Print nutella logo def self.print_nutella_logo diff --git a/lib/config/config.rb b/lib/config/config.rb deleted file mode 100755 index 25c8ea2..0000000 --- a/lib/config/config.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'config/persisted_hash' - -module Nutella - - # Calling this method (Nutella.config) simply returns and instance of - # PersistedHash linked to file config.json in nutella home directory - def Nutella.config - PersistedHash.new( "#{ENV['HOME']}/.nutella/config.json" ) - end - -end diff --git a/lib/config_files_management/config.rb b/lib/config_files_management/config.rb new file mode 100755 index 0000000..a76fc4a --- /dev/null +++ b/lib/config_files_management/config.rb @@ -0,0 +1,21 @@ +require 'config/persisted_hash' + +module Nutella + class Config + # Calling this method returns a PersistedHash instance + # "linked" to the config.json file in the nutella home directory + def self.file + PersistedHash.new( "#{ENV['HOME']}/.nutella/config.json" ) + end + + # This method initializes the nutella configuration file (config.json) with: + # - config_dir: directory where the configuration files are stored in + # - broker_dir: directory where the local broker is installed in + # - main_interface_port: the port used to serve interfaces + def self.init + file['config_dir'] = "#{ENV['HOME']}/.nutella/" + file['broker_dir'] = "#{file['config_dir']}broker/" + file['main_interface_port'] = 57880 + end + end +end diff --git a/lib/config/current_app_utils.rb b/lib/config_files_management/current_app_utils.rb similarity index 100% rename from lib/config/current_app_utils.rb rename to lib/config_files_management/current_app_utils.rb diff --git a/lib/config/persisted_hash.rb b/lib/config_files_management/persisted_hash.rb similarity index 100% rename from lib/config/persisted_hash.rb rename to lib/config_files_management/persisted_hash.rb diff --git a/lib/config/runlist.rb b/lib/config_files_management/runlist.rb similarity index 100% rename from lib/config/runlist.rb rename to lib/config_files_management/runlist.rb diff --git a/lib/core/nutella_core.rb b/lib/core/nutella_core.rb deleted file mode 100755 index 0036c3a..0000000 --- a/lib/core/nutella_core.rb +++ /dev/null @@ -1,41 +0,0 @@ -# Require all commands by iterating through all the files -# in the commands directory -Dir["#{File.dirname(__FILE__)}/../commands/*.rb"].each do |file| - # noinspection RubyResolve - require "commands/#{File.basename(file, File.extname(file))}" -end - -module Nutella - - # This method executes a particular command - # @param command [String] the name of the command - # @param args [Array] command line parameters passed to the command - def Nutella.execute_command (command, args=nil) - # Check that the command exists and if it does, - # execute its run method passing the args parameters - if command_exists?(command) - Object::const_get("Nutella::#{command.capitalize}").new.run(args) - else - console.error "Unknown command #{command}" - end - end - - # This method checks that a particular command exists - # @return [Boolean] true if the command exists, false otherwise - def Nutella.command_exists?(command) - return Nutella.const_get("Nutella::#{command.capitalize}").is_a?(Class) - rescue NameError - return false - end - - # This method initializes the nutella configuration file (config.json) with: - # - config_dir: directory where the configuration files are stored in - # - broker_dir: directory where the local broker is installed in - # - main_interface_port: the port used to serve interfaces - def Nutella.init - Nutella.config['config_dir'] = "#{ENV['HOME']}/.nutella/" - Nutella.config['broker_dir'] = "#{Nutella.config['config_dir']}broker/" - Nutella.config['main_interface_port'] = 57880 - end - -end \ No newline at end of file diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index c98a9de..bea4a85 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -1,22 +1,24 @@ -# Import all the modules -require 'logging/nutella_logging' -require 'core/nutella_core' -require 'core/nutella_cli' -require 'config/config' -require 'config/runlist' -require 'config/current_app_utils' +# This is the entry point to the gem. The code here gets executed BEFORE +# anything else. For this reason, this is a great place to import all the +# nutella modules. +require 'config_files_management/config' +require 'cli/nutella_cli' +# require 'logging/nutella_logging' +# require 'cli/nutella_core' + +# require 'config/runlist' +# require 'config/current_app_utils' module Nutella - # Initialize nutella home and temporary folder - home_dir = File.dirname(__FILE__) - NUTELLA_HOME = home_dir[0..-4] + # Initialize nutella home and temporary folder constants + NUTELLA_HOME = File.dirname(__FILE__)[0..-4] NUTELLA_TMP = "#{NUTELLA_HOME}.tmp/" # If the nutella configuration file (config.json) is empty (or doesn't exist) we're going to initialize it # with nutella constants and defaults - if Nutella.config.empty? - Nutella.init + if Config.file.empty? + Config.init end end diff --git a/test/config_files_management/test_config.rb b/test/config_files_management/test_config.rb new file mode 100644 index 0000000..0faeb2e --- /dev/null +++ b/test/config_files_management/test_config.rb @@ -0,0 +1,34 @@ +require 'helper' + +module Nutella + + class TestConfig < MiniTest::Test + + # def setup + # Dir.chdir NUTELLA_HOME + # Nutella.execute_command( 'new', ['test_app'] ) + # Dir.chdir "#{NUTELLA_HOME}test_app" + # end + # + # + # should 'return true if the dir is a nutella app' do + # assert Nutella.current_app.exist? + # end + # + # should 'return false if the dir is not a nutella app' do + # Dir.chdir NUTELLA_HOME + # refute Nutella.current_app.exist? + # end + # + # should 'return the correct version of nutella as read from the app configuration file' do + # assert_equal File.open("#{NUTELLA_HOME}VERSION", "rb").read, Nutella.current_app.config['nutella_version'] + # end + # + # + # def teardown + # FileUtils.rm_rf "#{NUTELLA_HOME}test_app" + # Dir.chdir NUTELLA_HOME + # end + + end +end \ No newline at end of file diff --git a/test/config/test_current_app_utils.rb b/test/config_files_management/test_current_app_utils.rb similarity index 100% rename from test/config/test_current_app_utils.rb rename to test/config_files_management/test_current_app_utils.rb diff --git a/test/config/test_persisted_hash.rb b/test/config_files_management/test_persisted_hash.rb similarity index 100% rename from test/config/test_persisted_hash.rb rename to test/config_files_management/test_persisted_hash.rb diff --git a/test/config/test_runlist.rb b/test/config_files_management/test_runlist.rb similarity index 100% rename from test/config/test_runlist.rb rename to test/config_files_management/test_runlist.rb From 95c6c81c8f42ea7fd7f5a1ba2cbe751d4199e927 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 28 Sep 2019 22:53:59 -0700 Subject: [PATCH 04/43] Switched to rspec, started wrting specs, cleaned up some old garbage tests --- .gitignore | 3 +- .rspec | 1 + Gemfile | 1 - Rakefile | 17 +-- lib/config_files_management/config.rb | 14 +-- lib/config_files_management/persisted_hash.rb | 1 - lib/nutella_framework.rb | 2 +- spec/config_files_management/config_spec.rb | 54 +++++++++ .../persisted_hash_spec.rb | 8 ++ spec/spec_helper.rb | 107 ++++++++++++++++++ test/commands/test_command_template.rb | 31 ----- test/config_files_management/test_config.rb | 34 ------ .../test_current_app_utils.rb | 34 ------ test/framework_apis/test_framework_api.rb | 74 ------------ test/helper.rb | 36 ------ test/logging/test_logging.rb | 16 --- 16 files changed, 185 insertions(+), 248 deletions(-) create mode 100644 .rspec create mode 100644 spec/config_files_management/config_spec.rb create mode 100644 spec/config_files_management/persisted_hash_spec.rb create mode 100644 spec/spec_helper.rb delete mode 100755 test/commands/test_command_template.rb delete mode 100644 test/config_files_management/test_config.rb delete mode 100755 test/config_files_management/test_current_app_utils.rb delete mode 100755 test/framework_apis/test_framework_api.rb delete mode 100755 test/helper.rb delete mode 100755 test/logging/test_logging.rb diff --git a/.gitignore b/.gitignore index 87bfedd..0f4d962 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# Nutella +# nutella config.json runlist.json broker/ @@ -16,6 +16,7 @@ doc/ .yardoc .bundle Gemfile.lock +spec/examples.txt # Mac crap .DS_Store diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/Gemfile b/Gemfile index e44e70c..cda2494 100755 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,6 @@ group :development do gem 'rdoc', '~> 4.0' gem 'bundler', '~> 2.0' gem 'jeweler', '~> 2.3' - gem 'simplecov', '~> 0.17' end group :test do diff --git a/Rakefile b/Rakefile index 89d5e6e..a3ce73a 100755 --- a/Rakefile +++ b/Rakefile @@ -25,20 +25,13 @@ Jeweler::Tasks.new do |gem| end Jeweler::RubygemsDotOrgTasks.new -require 'rake/testtask' -Rake::TestTask.new(:test) do |test| - test.libs << 'lib' << 'test' - test.pattern = 'test/**/test_*.rb' - test.verbose = true +require 'rspec/core' +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) do |spec| + spec.pattern = FileList['spec/**/*_spec.rb'] end -desc "Code coverage detail" -task :simplecov do - ENV['COVERAGE'] = "true" - Rake::Task['test'].execute -end - -task :default => :test +task :default => :spec require 'yard' YARD::Rake::YardocTask.new \ No newline at end of file diff --git a/lib/config_files_management/config.rb b/lib/config_files_management/config.rb index a76fc4a..3f2b47b 100755 --- a/lib/config_files_management/config.rb +++ b/lib/config_files_management/config.rb @@ -1,13 +1,7 @@ -require 'config/persisted_hash' +require 'config_files_management/persisted_hash' module Nutella class Config - # Calling this method returns a PersistedHash instance - # "linked" to the config.json file in the nutella home directory - def self.file - PersistedHash.new( "#{ENV['HOME']}/.nutella/config.json" ) - end - # This method initializes the nutella configuration file (config.json) with: # - config_dir: directory where the configuration files are stored in # - broker_dir: directory where the local broker is installed in @@ -17,5 +11,11 @@ def self.init file['broker_dir'] = "#{file['config_dir']}broker/" file['main_interface_port'] = 57880 end + + # Calling this method returns a PersistedHash instance + # "linked" to the config.json file in the nutella home directory + def self.file + PersistedHash.new( "#{ENV['HOME']}/.nutella/config.json" ) + end end end diff --git a/lib/config_files_management/persisted_hash.rb b/lib/config_files_management/persisted_hash.rb index 6b7fde4..7b2a9c7 100755 --- a/lib/config_files_management/persisted_hash.rb +++ b/lib/config_files_management/persisted_hash.rb @@ -102,7 +102,6 @@ def store_hash(hash) File.open(@file, 'w+') do |f| f.write(JSON.pretty_generate(hash)) end - File.chmod(0777, @file) end def load_hash diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index bea4a85..f70d70c 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -11,7 +11,7 @@ module Nutella - # Initialize nutella home and temporary folder constants + # Initialize nutella home to the folder where this source code is NUTELLA_HOME = File.dirname(__FILE__)[0..-4] NUTELLA_TMP = "#{NUTELLA_HOME}.tmp/" diff --git a/spec/config_files_management/config_spec.rb b/spec/config_files_management/config_spec.rb new file mode 100644 index 0000000..00a9e96 --- /dev/null +++ b/spec/config_files_management/config_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' +require 'config_files_management/config' +require 'securerandom' + +module Nutella + describe Config do + + before(:each) do + @ph = PersistedHash.new(SecureRandom.uuid) + allow(Config).to receive(:file).and_return(@ph) + end + + after(:each) do + @ph.remove_file + end + + + describe '.init' do + context "invoked once" do + it "initializes config correctly" do + Config.init + expect(@ph.length).to eq(3) + end + end + + context "invoked twice" do + it "initializes config file correctly" do + Config.init + Config.init + expect(@ph.length).to eq(3) + end + end + end + + + describe ".file" do + context "invoked after init" do + it "returns a valid persisted hash with configuration" do + Config.init + @ph = Config.file + expect(@ph.length).to eq(3) + end + end + + context "invoked before init" do + it "returns an empty hash" do + @ph = Config.file + expect(@ph.empty?).to be(true) + end + end + end + + end +end \ No newline at end of file diff --git a/spec/config_files_management/persisted_hash_spec.rb b/spec/config_files_management/persisted_hash_spec.rb new file mode 100644 index 0000000..89d682a --- /dev/null +++ b/spec/config_files_management/persisted_hash_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' +require 'config_files_management/persisted_hash' +require 'securerandom' + +module Nutella + describe PersistedHash do + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..0019f8b --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,107 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. + +require 'rspec' +# Requires supporting files with custom matchers and macros, etc, +# in ./support/ and its subdirectories. +# Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} + +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. + + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # So you don't have to type RSpec for every single test + config.expose_dsl_globally = true + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + # config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +end diff --git a/test/commands/test_command_template.rb b/test/commands/test_command_template.rb deleted file mode 100755 index d98d75a..0000000 --- a/test/commands/test_command_template.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'helper' - -module Nutella - - class TestCommandTemplate < MiniTest::Test - - # 1. Create a fake app - # New.new.run ['test_app'] - # Dir.chdir 'test_app' - # Nutella::Install.new.run ['basic-ruby-bot', 'bot1'] - # Dir.chdir '..' - - # 2. Perform tests - # should 'this is my first test' do - # Dir.chdir 'test_app' - # # Test code here - # Dir.chdir '..' - # end - # - # should 'this is my second test' do - # Dir.chdir 'test_app' - # # Test code here - # Dir.chdir '..' - # end - - # 3. Cleanup - # MiniTest.after_run { FileUtils.rm_r 'test_app' } - - end - -end diff --git a/test/config_files_management/test_config.rb b/test/config_files_management/test_config.rb deleted file mode 100644 index 0faeb2e..0000000 --- a/test/config_files_management/test_config.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'helper' - -module Nutella - - class TestConfig < MiniTest::Test - - # def setup - # Dir.chdir NUTELLA_HOME - # Nutella.execute_command( 'new', ['test_app'] ) - # Dir.chdir "#{NUTELLA_HOME}test_app" - # end - # - # - # should 'return true if the dir is a nutella app' do - # assert Nutella.current_app.exist? - # end - # - # should 'return false if the dir is not a nutella app' do - # Dir.chdir NUTELLA_HOME - # refute Nutella.current_app.exist? - # end - # - # should 'return the correct version of nutella as read from the app configuration file' do - # assert_equal File.open("#{NUTELLA_HOME}VERSION", "rb").read, Nutella.current_app.config['nutella_version'] - # end - # - # - # def teardown - # FileUtils.rm_rf "#{NUTELLA_HOME}test_app" - # Dir.chdir NUTELLA_HOME - # end - - end -end \ No newline at end of file diff --git a/test/config_files_management/test_current_app_utils.rb b/test/config_files_management/test_current_app_utils.rb deleted file mode 100755 index 1111bc5..0000000 --- a/test/config_files_management/test_current_app_utils.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'helper' - -module Nutella - - class TestCurrentAppUtils < MiniTest::Test - - # def setup - # Dir.chdir NUTELLA_HOME - # Nutella.execute_command( 'new', ['test_app'] ) - # Dir.chdir "#{NUTELLA_HOME}test_app" - # end - # - # - # should 'return true if the dir is a nutella app' do - # assert Nutella.current_app.exist? - # end - # - # should 'return false if the dir is not a nutella app' do - # Dir.chdir NUTELLA_HOME - # refute Nutella.current_app.exist? - # end - # - # should 'return the correct version of nutella as read from the app configuration file' do - # assert_equal File.open("#{NUTELLA_HOME}VERSION", "rb").read, Nutella.current_app.config['nutella_version'] - # end - # - # - # def teardown - # FileUtils.rm_rf "#{NUTELLA_HOME}test_app" - # Dir.chdir NUTELLA_HOME - # end - - end -end \ No newline at end of file diff --git a/test/framework_apis/test_framework_api.rb b/test/framework_apis/test_framework_api.rb deleted file mode 100755 index ea2b277..0000000 --- a/test/framework_apis/test_framework_api.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'helper' - -require_relative '../../nutella_lib/framework_core' - - - -module Nutella - class TestFrameworkApi < MiniTest::Test - - # nutella.init_as_f_component('localhost', 'my_component_id') - - - # should 'pub_sub_on_framework_channels' do - # cb_executed = false - # cb = lambda do |message, from| - # cb_executed = true - # puts "Received message from #{from['component_id']}/#{from['resource_id']}. Message: #{message}" - # end - # nutella.net.f.subscribe('demo0', cb) - # sleep 1 - # nutella.net.f.publish('demo0', 'test_message') - # # Make sure we wait for the message to be delivered - # sleep 1 - # assert cb_executed - # end - # - # - # should 'request_response_on_framework_channel' do - # nutella.set_resource_id 'my_resource_id_3' - # nutella.net.f.handle_requests('demo3', lambda do |message, from| - # puts "We received a request: message #{message}, from #{from['component_id']}/#{from['resource_id']}." - # #Then we are going to return some random JSON - # {my:'json'} - # end) - # response = nutella.net.f.sync_request('demo3', 'my request is a string') - # assert_equal({'my' => 'json'}, response) - # nutella.net.f.async_request( 'demo3', 'my request is a string', lambda do |response| - # assert_equal({'my' => 'json'}, response) - # end) - # sleep 2 - # end - - - # Framework-to-run (broadcasting) - - # should 'test_app_run_pub_sub_all' do - # nutella.set_resource_id 'my_resource_id_5' - # cb = lambda do |message, app_id, run_id, from| - # puts "Received message from run_id #{from['run_id']} on #{app_id}/#{run_id}. Message: #{message}" - # # nutella.net.f.unsubscribe_from_all_runs 'demo5' - # end - # nutella.net.f.subscribe_to_all_runs('demo5', cb) - # sleep 1 - # nutella.net.f.publish_to_all_runs('demo5', 'test_message') - # # Make sure we wait for the message to be delivered - # sleep 2 - # end - - # def test_app_run_req_res_all - # nutella.set_resource_id 'my_resource_id_6' - # nutella.net.f.handle_requests_on_all_runs('demo6', lambda do |message, app_id, run_id, from| - # puts "We received a request: message '#{message}', on app_id/run_id #{app_id}/#{run_id} from #{from}." - # 'response' # Return something - # end) - # sleep 1 - # nutella.net.f.async_request_to_all_runs('demo6', 'my request is a string', lambda do |response| - # puts response - # end) - # sleep 2 - # end - - end -end - diff --git a/test/helper.rb b/test/helper.rb deleted file mode 100755 index 81e65aa..0000000 --- a/test/helper.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'simplecov' - -module SimpleCov::Configuration - def clean_filters - @filters = [] - end -end - -SimpleCov.configure do - clean_filters - load_profile 'test_frameworks' -end - -ENV["COVERAGE"] && SimpleCov.start do - add_filter "/.rvm/" -end -require 'rubygems' -require 'bundler' -begin - Bundler.setup(:default, :development) -rescue Bundler::BundlerError => e - $stderr.puts e.message - $stderr.puts "Run `bundle install` to install missing gems" - exit e.status_code -end -require 'minitest/autorun' -require 'shoulda' - -$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -$LOAD_PATH.unshift(File.dirname(__FILE__)) -require 'nutella_framework' - -class MiniTest::Test -end - -MiniTest.autorun diff --git a/test/logging/test_logging.rb b/test/logging/test_logging.rb deleted file mode 100755 index b07b1a3..0000000 --- a/test/logging/test_logging.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'helper' - -module Nutella - class TestNutellaLogger < MiniTest::Test - - # should "log to console with color and error code" do -# assert_equal(453, console.error("This is the message that needs to be logged", 453)) -# end - - # shuold "log to appenders and not console" do -# log.error("This is an error message that needs to be logged", 354) -# end - - end -end - From 75bcebfb6a451bbc29da4915a3139ce83c613f2b Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 28 Sep 2019 22:57:50 -0700 Subject: [PATCH 05/43] Update version of ruby in travis file --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e06a01f..9acedf2 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: ruby rvm: - - 2.4.1 + - 2.6.3 notifications: email: false From 3f951357681f955a7217097c54c301957a3045ea Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 28 Sep 2019 23:01:37 -0700 Subject: [PATCH 06/43] Update travis file --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9acedf2..5f1b58f 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ language: ruby rvm: - 2.6.3 +before_install: + - gem update --system + - gem install bundler notifications: email: false From 71a1ae9dd023ea5d617f769c6f03a0e89d7b8095 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 29 Sep 2019 16:35:48 -0700 Subject: [PATCH 07/43] Fix specs for persisted hash --- .rspec | 2 + Gemfile | 1 + spec/config_files_management/config_spec.rb | 18 ++--- .../persisted_hash_spec.rb | 70 +++++++++++++++++++ 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/.rspec b/.rspec index c99d2e7..d016dc8 100644 --- a/.rspec +++ b/.rspec @@ -1 +1,3 @@ --require spec_helper +--format Fuubar +--color diff --git a/Gemfile b/Gemfile index cda2494..c098608 100755 --- a/Gemfile +++ b/Gemfile @@ -22,4 +22,5 @@ end group :test do gem 'rake' gem 'rspec', '~> 3.8' + gem 'fuubar', '~> 2.4' end diff --git a/spec/config_files_management/config_spec.rb b/spec/config_files_management/config_spec.rb index 00a9e96..38927ac 100644 --- a/spec/config_files_management/config_spec.rb +++ b/spec/config_files_management/config_spec.rb @@ -16,15 +16,15 @@ module Nutella describe '.init' do - context "invoked once" do - it "initializes config correctly" do + context 'invoked once' do + it 'initializes config correctly' do Config.init expect(@ph.length).to eq(3) end end - context "invoked twice" do - it "initializes config file correctly" do + context 'invoked twice' do + it 'initializes config file correctly' do Config.init Config.init expect(@ph.length).to eq(3) @@ -33,17 +33,17 @@ module Nutella end - describe ".file" do - context "invoked after init" do - it "returns a valid persisted hash with configuration" do + describe '.file' do + context 'invoked after init' do + it 'returns a valid persisted hash with configuration' do Config.init @ph = Config.file expect(@ph.length).to eq(3) end end - context "invoked before init" do - it "returns an empty hash" do + context 'invoked before init' do + it 'returns an empty hash' do @ph = Config.file expect(@ph.empty?).to be(true) end diff --git a/spec/config_files_management/persisted_hash_spec.rb b/spec/config_files_management/persisted_hash_spec.rb index 89d682a..de8b507 100644 --- a/spec/config_files_management/persisted_hash_spec.rb +++ b/spec/config_files_management/persisted_hash_spec.rb @@ -4,5 +4,75 @@ module Nutella describe PersistedHash do + before(:each) do + @ph = PersistedHash.new(SecureRandom.uuid) + end + + describe '#[]' do + context 'uninialized' do + it { expect(@ph['any_key']).to be_nil } + end + + context 'initialized' do + before(:example) do + allow(@ph).to receive(:load_hash).and_return({test_key: 'test_value'}) + end + it {expect(@ph[:test_key]).to eq('test_value')} + end + end + + describe '#[]=' do + context 'key is a string' do + it 'sets the value' do + @ph['key1']='value1' + expect(@ph['key1']).to eq('value1') + end + end + # uncomment when https://github.com/nutella-framework/nutella_framework/issues/101 is fixed + # context 'key is not a string' do + # it {expect {@ph[:key1]='value1'}.to raise_error(KeyError)} + # end + end + + describe '#delete' do + let (:key) {'a key'} + let (:value) {'a value'} + it 'deletes the key and returns the value' do + @ph[key]=value + v = @ph.delete(key) + expect(v).to eq(value) + expect(@ph['key']).to be_nil + end + end + + describe '#empty?' do + context 'is empty' do + it {expect(@ph.empty?).to be true} + end + context 'is not empty' do + it 'returns false' do + @ph['key']='value' + expect(@ph.empty?).to be false + end + end + end + + # Same as #has_key? + describe "#include?" do + let (:key) {'a key'} + context 'has the key' do + it {expect(@ph.include?(key)).to be false} + end + context 'doesn\'t have the key' do + it 'returns true' do + @ph[key]='value' + expect(@ph.include?(key)).to be true + end + end + end + + after(:each) do + @ph.remove_file + end end end \ No newline at end of file From 70cd8182d1d069c128c5ae22257b744268142efb Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 29 Sep 2019 16:45:14 -0700 Subject: [PATCH 08/43] Move things around --- .../framework_core.rb | 0 .../framework_log.rb | 0 .../framework_net.rb | 0 .../framework_persist.rb | 0 .../commands/test_cmd_cli_params_parsing.rb | 0 .../config_files_management/test_persisted_hash.rb | 0 .../config_files_management/test_runlist.rb | 0 .../bots}/binary-files-manager/bin_files_mngr.rb | 0 .../bots}/binary-files-manager/startup | 0 .../bots}/main_interface/main_interface_bot.rb | 0 .../bots}/main_interface/public/index.html | 0 .../bots}/main_interface/startup | 0 .../bots}/main_interface/views/index.erb | 0 .../bots}/main_interface/views/not_found_404.erb | 0 {framework_components => lib/bots}/order.json | 0 .../bots}/room-debugger/README.md | 0 .../bots}/room-debugger/css/bootstrap-theme.css | 0 .../bots}/room-debugger/css/bootstrap-theme.css.map | 0 .../bots}/room-debugger/css/bootstrap-theme.min.css | 0 .../bots}/room-debugger/css/bootstrap.css | 0 .../bots}/room-debugger/css/bootstrap.css.map | 0 .../bots}/room-debugger/css/bootstrap.min.css | 0 .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin .../fonts/glyphicons-halflings-regular.woff2 | Bin .../bots}/room-debugger/index.html | 0 .../bots}/room-debugger/js/bootstrap.js | 0 .../bots}/room-debugger/js/bootstrap.min.js | 0 .../bots}/room-debugger/js/jquery.min.js | 0 .../bots}/room-debugger/js/npm.js | 0 .../bots}/room-debugger/js/nutella_lib.js | 0 .../bots}/room-debugger/main.css | 0 .../bots}/room-debugger/main.js | 0 .../bots}/room-debugger/nutella.json | 0 .../bots}/room-debugger/package.json | 2 +- .../bots}/room-debugger/room_places_simulator.js | 0 .../bots}/runs_list_bot/runs_list_bot.rb | 0 .../bots}/runs_list_bot/startup | 0 {data => lib/templates}/index.html | 0 {data => lib/templates}/startup | 0 42 files changed, 1 insertion(+), 1 deletion(-) rename {nutella_lib => legacy_nutella_lib}/framework_core.rb (100%) rename {nutella_lib => legacy_nutella_lib}/framework_log.rb (100%) rename {nutella_lib => legacy_nutella_lib}/framework_net.rb (100%) rename {nutella_lib => legacy_nutella_lib}/framework_persist.rb (100%) rename {test => legacy_test}/commands/test_cmd_cli_params_parsing.rb (100%) rename {test => legacy_test}/config_files_management/test_persisted_hash.rb (100%) rename {test => legacy_test}/config_files_management/test_runlist.rb (100%) rename {framework_components => lib/bots}/binary-files-manager/bin_files_mngr.rb (100%) rename {framework_components => lib/bots}/binary-files-manager/startup (100%) rename {framework_components => lib/bots}/main_interface/main_interface_bot.rb (100%) rename {framework_components => lib/bots}/main_interface/public/index.html (100%) rename {framework_components => lib/bots}/main_interface/startup (100%) rename {framework_components => lib/bots}/main_interface/views/index.erb (100%) rename {framework_components => lib/bots}/main_interface/views/not_found_404.erb (100%) rename {framework_components => lib/bots}/order.json (100%) rename {framework_components => lib/bots}/room-debugger/README.md (100%) rename {framework_components => lib/bots}/room-debugger/css/bootstrap-theme.css (100%) rename {framework_components => lib/bots}/room-debugger/css/bootstrap-theme.css.map (100%) rename {framework_components => lib/bots}/room-debugger/css/bootstrap-theme.min.css (100%) rename {framework_components => lib/bots}/room-debugger/css/bootstrap.css (100%) rename {framework_components => lib/bots}/room-debugger/css/bootstrap.css.map (100%) rename {framework_components => lib/bots}/room-debugger/css/bootstrap.min.css (100%) rename {framework_components => lib/bots}/room-debugger/fonts/glyphicons-halflings-regular.eot (100%) rename {framework_components => lib/bots}/room-debugger/fonts/glyphicons-halflings-regular.svg (100%) rename {framework_components => lib/bots}/room-debugger/fonts/glyphicons-halflings-regular.ttf (100%) rename {framework_components => lib/bots}/room-debugger/fonts/glyphicons-halflings-regular.woff (100%) rename {framework_components => lib/bots}/room-debugger/fonts/glyphicons-halflings-regular.woff2 (100%) rename {framework_components => lib/bots}/room-debugger/index.html (100%) rename {framework_components => lib/bots}/room-debugger/js/bootstrap.js (100%) rename {framework_components => lib/bots}/room-debugger/js/bootstrap.min.js (100%) rename {framework_components => lib/bots}/room-debugger/js/jquery.min.js (100%) rename {framework_components => lib/bots}/room-debugger/js/npm.js (100%) rename {framework_components => lib/bots}/room-debugger/js/nutella_lib.js (100%) rename {framework_components => lib/bots}/room-debugger/main.css (100%) rename {framework_components => lib/bots}/room-debugger/main.js (100%) rename {framework_components => lib/bots}/room-debugger/nutella.json (100%) rename {framework_components => lib/bots}/room-debugger/package.json (90%) rename {framework_components => lib/bots}/room-debugger/room_places_simulator.js (100%) rename {framework_components => lib/bots}/runs_list_bot/runs_list_bot.rb (100%) rename {framework_components => lib/bots}/runs_list_bot/startup (100%) rename {data => lib/templates}/index.html (100%) rename {data => lib/templates}/startup (100%) diff --git a/nutella_lib/framework_core.rb b/legacy_nutella_lib/framework_core.rb similarity index 100% rename from nutella_lib/framework_core.rb rename to legacy_nutella_lib/framework_core.rb diff --git a/nutella_lib/framework_log.rb b/legacy_nutella_lib/framework_log.rb similarity index 100% rename from nutella_lib/framework_log.rb rename to legacy_nutella_lib/framework_log.rb diff --git a/nutella_lib/framework_net.rb b/legacy_nutella_lib/framework_net.rb similarity index 100% rename from nutella_lib/framework_net.rb rename to legacy_nutella_lib/framework_net.rb diff --git a/nutella_lib/framework_persist.rb b/legacy_nutella_lib/framework_persist.rb similarity index 100% rename from nutella_lib/framework_persist.rb rename to legacy_nutella_lib/framework_persist.rb diff --git a/test/commands/test_cmd_cli_params_parsing.rb b/legacy_test/commands/test_cmd_cli_params_parsing.rb similarity index 100% rename from test/commands/test_cmd_cli_params_parsing.rb rename to legacy_test/commands/test_cmd_cli_params_parsing.rb diff --git a/test/config_files_management/test_persisted_hash.rb b/legacy_test/config_files_management/test_persisted_hash.rb similarity index 100% rename from test/config_files_management/test_persisted_hash.rb rename to legacy_test/config_files_management/test_persisted_hash.rb diff --git a/test/config_files_management/test_runlist.rb b/legacy_test/config_files_management/test_runlist.rb similarity index 100% rename from test/config_files_management/test_runlist.rb rename to legacy_test/config_files_management/test_runlist.rb diff --git a/framework_components/binary-files-manager/bin_files_mngr.rb b/lib/bots/binary-files-manager/bin_files_mngr.rb similarity index 100% rename from framework_components/binary-files-manager/bin_files_mngr.rb rename to lib/bots/binary-files-manager/bin_files_mngr.rb diff --git a/framework_components/binary-files-manager/startup b/lib/bots/binary-files-manager/startup similarity index 100% rename from framework_components/binary-files-manager/startup rename to lib/bots/binary-files-manager/startup diff --git a/framework_components/main_interface/main_interface_bot.rb b/lib/bots/main_interface/main_interface_bot.rb similarity index 100% rename from framework_components/main_interface/main_interface_bot.rb rename to lib/bots/main_interface/main_interface_bot.rb diff --git a/framework_components/main_interface/public/index.html b/lib/bots/main_interface/public/index.html similarity index 100% rename from framework_components/main_interface/public/index.html rename to lib/bots/main_interface/public/index.html diff --git a/framework_components/main_interface/startup b/lib/bots/main_interface/startup similarity index 100% rename from framework_components/main_interface/startup rename to lib/bots/main_interface/startup diff --git a/framework_components/main_interface/views/index.erb b/lib/bots/main_interface/views/index.erb similarity index 100% rename from framework_components/main_interface/views/index.erb rename to lib/bots/main_interface/views/index.erb diff --git a/framework_components/main_interface/views/not_found_404.erb b/lib/bots/main_interface/views/not_found_404.erb similarity index 100% rename from framework_components/main_interface/views/not_found_404.erb rename to lib/bots/main_interface/views/not_found_404.erb diff --git a/framework_components/order.json b/lib/bots/order.json similarity index 100% rename from framework_components/order.json rename to lib/bots/order.json diff --git a/framework_components/room-debugger/README.md b/lib/bots/room-debugger/README.md similarity index 100% rename from framework_components/room-debugger/README.md rename to lib/bots/room-debugger/README.md diff --git a/framework_components/room-debugger/css/bootstrap-theme.css b/lib/bots/room-debugger/css/bootstrap-theme.css similarity index 100% rename from framework_components/room-debugger/css/bootstrap-theme.css rename to lib/bots/room-debugger/css/bootstrap-theme.css diff --git a/framework_components/room-debugger/css/bootstrap-theme.css.map b/lib/bots/room-debugger/css/bootstrap-theme.css.map similarity index 100% rename from framework_components/room-debugger/css/bootstrap-theme.css.map rename to lib/bots/room-debugger/css/bootstrap-theme.css.map diff --git a/framework_components/room-debugger/css/bootstrap-theme.min.css b/lib/bots/room-debugger/css/bootstrap-theme.min.css similarity index 100% rename from framework_components/room-debugger/css/bootstrap-theme.min.css rename to lib/bots/room-debugger/css/bootstrap-theme.min.css diff --git a/framework_components/room-debugger/css/bootstrap.css b/lib/bots/room-debugger/css/bootstrap.css similarity index 100% rename from framework_components/room-debugger/css/bootstrap.css rename to lib/bots/room-debugger/css/bootstrap.css diff --git a/framework_components/room-debugger/css/bootstrap.css.map b/lib/bots/room-debugger/css/bootstrap.css.map similarity index 100% rename from framework_components/room-debugger/css/bootstrap.css.map rename to lib/bots/room-debugger/css/bootstrap.css.map diff --git a/framework_components/room-debugger/css/bootstrap.min.css b/lib/bots/room-debugger/css/bootstrap.min.css similarity index 100% rename from framework_components/room-debugger/css/bootstrap.min.css rename to lib/bots/room-debugger/css/bootstrap.min.css diff --git a/framework_components/room-debugger/fonts/glyphicons-halflings-regular.eot b/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from framework_components/room-debugger/fonts/glyphicons-halflings-regular.eot rename to lib/bots/room-debugger/fonts/glyphicons-halflings-regular.eot diff --git a/framework_components/room-debugger/fonts/glyphicons-halflings-regular.svg b/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from framework_components/room-debugger/fonts/glyphicons-halflings-regular.svg rename to lib/bots/room-debugger/fonts/glyphicons-halflings-regular.svg diff --git a/framework_components/room-debugger/fonts/glyphicons-halflings-regular.ttf b/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from framework_components/room-debugger/fonts/glyphicons-halflings-regular.ttf rename to lib/bots/room-debugger/fonts/glyphicons-halflings-regular.ttf diff --git a/framework_components/room-debugger/fonts/glyphicons-halflings-regular.woff b/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from framework_components/room-debugger/fonts/glyphicons-halflings-regular.woff rename to lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff diff --git a/framework_components/room-debugger/fonts/glyphicons-halflings-regular.woff2 b/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from framework_components/room-debugger/fonts/glyphicons-halflings-regular.woff2 rename to lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff2 diff --git a/framework_components/room-debugger/index.html b/lib/bots/room-debugger/index.html similarity index 100% rename from framework_components/room-debugger/index.html rename to lib/bots/room-debugger/index.html diff --git a/framework_components/room-debugger/js/bootstrap.js b/lib/bots/room-debugger/js/bootstrap.js similarity index 100% rename from framework_components/room-debugger/js/bootstrap.js rename to lib/bots/room-debugger/js/bootstrap.js diff --git a/framework_components/room-debugger/js/bootstrap.min.js b/lib/bots/room-debugger/js/bootstrap.min.js similarity index 100% rename from framework_components/room-debugger/js/bootstrap.min.js rename to lib/bots/room-debugger/js/bootstrap.min.js diff --git a/framework_components/room-debugger/js/jquery.min.js b/lib/bots/room-debugger/js/jquery.min.js similarity index 100% rename from framework_components/room-debugger/js/jquery.min.js rename to lib/bots/room-debugger/js/jquery.min.js diff --git a/framework_components/room-debugger/js/npm.js b/lib/bots/room-debugger/js/npm.js similarity index 100% rename from framework_components/room-debugger/js/npm.js rename to lib/bots/room-debugger/js/npm.js diff --git a/framework_components/room-debugger/js/nutella_lib.js b/lib/bots/room-debugger/js/nutella_lib.js similarity index 100% rename from framework_components/room-debugger/js/nutella_lib.js rename to lib/bots/room-debugger/js/nutella_lib.js diff --git a/framework_components/room-debugger/main.css b/lib/bots/room-debugger/main.css similarity index 100% rename from framework_components/room-debugger/main.css rename to lib/bots/room-debugger/main.css diff --git a/framework_components/room-debugger/main.js b/lib/bots/room-debugger/main.js similarity index 100% rename from framework_components/room-debugger/main.js rename to lib/bots/room-debugger/main.js diff --git a/framework_components/room-debugger/nutella.json b/lib/bots/room-debugger/nutella.json similarity index 100% rename from framework_components/room-debugger/nutella.json rename to lib/bots/room-debugger/nutella.json diff --git a/framework_components/room-debugger/package.json b/lib/bots/room-debugger/package.json similarity index 90% rename from framework_components/room-debugger/package.json rename to lib/bots/room-debugger/package.json index 7f94a80..75b2e74 100644 --- a/framework_components/room-debugger/package.json +++ b/lib/bots/room-debugger/package.json @@ -8,7 +8,7 @@ "license": "MIT", "dependencies": { "bootstrap": "^3.3.4", - "jquery": "^2.1.4", + "jquery": "jquery": ">=3.4.0" "nutella_lib": "^0.5.10" } } diff --git a/framework_components/room-debugger/room_places_simulator.js b/lib/bots/room-debugger/room_places_simulator.js similarity index 100% rename from framework_components/room-debugger/room_places_simulator.js rename to lib/bots/room-debugger/room_places_simulator.js diff --git a/framework_components/runs_list_bot/runs_list_bot.rb b/lib/bots/runs_list_bot/runs_list_bot.rb similarity index 100% rename from framework_components/runs_list_bot/runs_list_bot.rb rename to lib/bots/runs_list_bot/runs_list_bot.rb diff --git a/framework_components/runs_list_bot/startup b/lib/bots/runs_list_bot/startup similarity index 100% rename from framework_components/runs_list_bot/startup rename to lib/bots/runs_list_bot/startup diff --git a/data/index.html b/lib/templates/index.html similarity index 100% rename from data/index.html rename to lib/templates/index.html diff --git a/data/startup b/lib/templates/startup similarity index 100% rename from data/startup rename to lib/templates/startup From 987ad916a6076f38ae2e5ecb6db63543548ba53a Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 29 Sep 2019 19:32:05 -0700 Subject: [PATCH 09/43] Refactored commands, eliminated logger, started testing commends --- Gemfile | 4 +- .../commands/test_cmd_cli_params_parsing.rb | 56 ------------------- .../test_persisted_hash.rb | 48 ---------------- .../config_files_management/test_runlist.rb | 46 --------------- lib/cli/{nutella_cli.rb => cli.rb} | 11 ++-- lib/cli/cli_utils.rb | 41 ++++++++++++++ lib/cli/commands/broker.rb | 2 +- lib/cli/commands/checkup.rb | 2 +- lib/cli/commands/compile.rb | 2 +- lib/cli/commands/dependencies.rb | 2 +- lib/cli/commands/help.rb | 8 +-- lib/cli/commands/install.rb | 6 +- lib/cli/commands/meta/run_command.rb | 6 +- lib/cli/commands/meta/template_command.rb | 4 +- lib/cli/commands/new.rb | 2 +- lib/cli/commands/reset.rb | 2 +- lib/cli/commands/runs.rb | 2 +- lib/cli/commands/start.rb | 4 +- lib/cli/commands/stop.rb | 2 +- lib/cli/commands/template.rb | 2 +- lib/cli/commands/util/components_starter.rb | 2 +- lib/logging/nutella_logger-remote.rb | 31 ---------- lib/logging/nutella_logger.rb | 42 -------------- lib/logging/nutella_logging.rb | 35 ------------ lib/nutella_framework.rb | 2 - spec/cli/cli_utils_spec.rb | 0 spec/cli/commands/help_spec.rb | 10 ++++ 27 files changed, 83 insertions(+), 291 deletions(-) delete mode 100755 legacy_test/commands/test_cmd_cli_params_parsing.rb delete mode 100755 legacy_test/config_files_management/test_persisted_hash.rb delete mode 100755 legacy_test/config_files_management/test_runlist.rb rename lib/cli/{nutella_cli.rb => cli.rb} (89%) create mode 100755 lib/cli/cli_utils.rb delete mode 100755 lib/logging/nutella_logger-remote.rb delete mode 100755 lib/logging/nutella_logger.rb delete mode 100755 lib/logging/nutella_logging.rb create mode 100644 spec/cli/cli_utils_spec.rb create mode 100644 spec/cli/commands/help_spec.rb diff --git a/Gemfile b/Gemfile index c098608..5e89aa2 100755 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'http://rubygems.org' gem 'semantic', '~> 1.4' -gem 'logging', '~> 2.2' +gem "ansi", "~> 1.5" gem 'git', '~> 1.2' gem 'sinatra', '~>1.4' gem 'sinatra-cross_origin', '~> 0.3.2' @@ -24,3 +24,5 @@ group :test do gem 'rspec', '~> 3.8' gem 'fuubar', '~> 2.4' end + + diff --git a/legacy_test/commands/test_cmd_cli_params_parsing.rb b/legacy_test/commands/test_cmd_cli_params_parsing.rb deleted file mode 100755 index 1adeff3..0000000 --- a/legacy_test/commands/test_cmd_cli_params_parsing.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'helper' - -module Nutella - - class TestCmdCLIParamsParsing < MiniTest::Test - - def setup - @run_cmd = RunCommand.new - end - - - should 'parse app long argument' do - params = @run_cmd.parse_cli_parameters ['--with=bot1,bot2,bot3'] - assert_equal %w(bot1 bot2 bot3), params[:with] - end - - should 'parse without long argument' do - params = @run_cmd.parse_cli_parameters ['--without=botA,botB,botC'] - assert_equal %w(botA botB botC), params[:without] - end - - should 'parse with long argument' do - params = @run_cmd.parse_cli_parameters ['--with=botX,botY,botZ'] - assert_equal %w(botX botY botZ), params[:with] - end - - should 'parse one short argument' do - params = @run_cmd.parse_cli_parameters ['-w=bot1,bot2,bot3'] - assert_equal %w(bot1 bot2 bot3), params[:with] - end - - should 'parse two long arguments' do - params = @run_cmd.parse_cli_parameters %w(--with=bot1,bot2,bot3 --without=botA,botB,botC) - assert_equal %w(bot1 bot2 bot3), params[:with] - assert_equal %w(botA botB botC), params[:without] - end - - should 'parse two short arguments' do - params = @run_cmd.parse_cli_parameters %w(-wo=bot1,bot2,bot3 -w=botA,botB,botC) - assert_equal %w(bot1 bot2 bot3), params[:without] - assert_equal %w(botA botB botC), params[:with] - end - - should 'parse one short and one long argument' do - params = @run_cmd.parse_cli_parameters %w(--with=bot1,bot2,bot3 -wo=botA,botB,botC) - assert_equal %w(bot1 bot2 bot3), params[:with] - assert_equal %w(botA botB botC), params[:without] - end - - should 'raise an exception when trying to parse params that do not exist' do - assert_raises (StandardError) { @run_cmd.parse_cli_parameters %w(--wit=bot1,bot2,bot3 -o=botA,botB,botC) } - end - - end - -end \ No newline at end of file diff --git a/legacy_test/config_files_management/test_persisted_hash.rb b/legacy_test/config_files_management/test_persisted_hash.rb deleted file mode 100755 index 7472f9d..0000000 --- a/legacy_test/config_files_management/test_persisted_hash.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'helper' - -module Nutella - - class TestNutellaConfig < MiniTest::Test - - def setup - @config = PersistedHash.new( 'runlist.json' ) - end - - - should 'set a key value' do - assert_equal 'value1', @config['key1']='value1' - end - - should 'return \'nil\' if a key doesn\'t exist' do - assert_nil @config['fakekey'] - end - - should 'return the value associated with a key whenever that key exists' do - @config['key2']='value2' - assert_equal 'value2', @config['key2'] - end - - should 'return true if a key exists' do - @config['key3']='value3' - assert @config.has_key?('key3') - end - - should 'return false if a key doesn\'t exist' do - refute @config.has_key?('key4') - end - - should 'access nested hashes' do - @config['key5']={'k55' => 'v55'} - assert_equal 'v55', @config['key5']['k55'] - end - - - def teardown - @config.send(:remove_file) - end - - end - -end - - diff --git a/legacy_test/config_files_management/test_runlist.rb b/legacy_test/config_files_management/test_runlist.rb deleted file mode 100755 index 0d479bc..0000000 --- a/legacy_test/config_files_management/test_runlist.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'helper' - -module Nutella - - class TestRunList < MiniTest::Test - - def setup - @runlist = RunListHash.new( 'runlist.json' ) - end - - should 'return true if the list is empty' do - assert @runlist.empty? - end - - should 'return false if the list is not empty' do - assert @runlist.add?('app_id_a', 'run_id_1', '/just/a/random/path') - refute @runlist.empty? - end - - should 'return empty array if the list is empty' do - assert_empty @runlist.all_runs - end - - should 'return an array of runs in the list if not empty' do - refute_nil @runlist.add?( 'app_a', 'run1', '/path/to/my/run1' ) - refute_nil @runlist.add?( 'app_a', 'run2', '/path/to/my/run2' ) - assert_equal %w{run1 run2}, @runlist.runs_for_app('app_a') - end - - should 'return false if trying to add the same element twice' do - assert @runlist.add?( 'app_a', 'run1', '/path/to/my/run1' ) - refute @runlist.add?( 'app_a', 'run1', '/path/to/my/run1' ) - end - - should 'return properly when deleting an item' do - assert @runlist.add?('app_a', 'run1', '/path/to/my/run1' ) - assert @runlist.delete?('app_a', 'run1') - refute @runlist.delete?('app_a', 'run1') - end - - def teardown - @runlist.remove_file - end - - end -end \ No newline at end of file diff --git a/lib/cli/nutella_cli.rb b/lib/cli/cli.rb similarity index 89% rename from lib/cli/nutella_cli.rb rename to lib/cli/cli.rb index 92752fd..11c007d 100755 --- a/lib/cli/nutella_cli.rb +++ b/lib/cli/cli.rb @@ -1,10 +1,9 @@ -# Require all commands by iterating through all the files -# in the commands directory -Dir["#{File.dirname(__FILE__)}/../commands/*.rb"].each do |file| - # noinspection RubyResolve - require "cli/commands/#{File.basename(file, File.extname(file))}" +require_relative 'cli_utils' +Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| + require_relative "commands/#{File.basename(file, File.extname(file))}" end + module Nutella class NutellaCLI @@ -46,7 +45,7 @@ def self.run # This method executes a particular command # @param command [String] the name of the command # @param args [Array] command line parameters passed to the command - def self.execute_command (command, args=nil) + def self.execute_command(command, args=nil) # Check that the command exists and if it does, # execute its run method passing the args parameters if command_exists?(command) diff --git a/lib/cli/cli_utils.rb b/lib/cli/cli_utils.rb new file mode 100755 index 0000000..4a35b1f --- /dev/null +++ b/lib/cli/cli_utils.rb @@ -0,0 +1,41 @@ +require 'singleton' +require 'ansi/code' + +module Nutella + + class CLIUtils + include Singleton + + def debug(message) + puts(ANSI.cyan + message + ANSI.reset) + end + + def info(message) + puts(message) + end + + def success(message) + puts(ANSI.green + message + ANSI.reset) + + end + + def warn(message) + puts(ANSI.yellow + message + ANSI.reset) + end + + def error(message) + puts(ANSI.red + message + ANSI.reset) + end + + end + +end + + +module Kernel + + def console + Nutella::CLIUtils.instance + end + +end diff --git a/lib/cli/commands/broker.rb b/lib/cli/commands/broker.rb index 5c06ab3..d0af770 100755 --- a/lib/cli/commands/broker.rb +++ b/lib/cli/commands/broker.rb @@ -1,4 +1,4 @@ -require 'commands/meta/command' +require_relative 'meta/command' require 'socket' module Nutella diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index 403ce51..d039d25 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -1,4 +1,4 @@ -require 'commands/meta/command' +require_relative 'meta/command' require 'semantic' module Nutella diff --git a/lib/cli/commands/compile.rb b/lib/cli/commands/compile.rb index 0ab3499..21b676f 100755 --- a/lib/cli/commands/compile.rb +++ b/lib/cli/commands/compile.rb @@ -1,4 +1,4 @@ -require 'commands/meta/run_command' +require_relative 'meta/run_command' require 'tmux/tmux' module Nutella diff --git a/lib/cli/commands/dependencies.rb b/lib/cli/commands/dependencies.rb index c11cff0..b929bc5 100755 --- a/lib/cli/commands/dependencies.rb +++ b/lib/cli/commands/dependencies.rb @@ -1,4 +1,4 @@ -require 'commands/meta/run_command' +require_relative 'meta/run_command' require 'tmux/tmux' module Nutella diff --git a/lib/cli/commands/help.rb b/lib/cli/commands/help.rb index 07790d7..86be415 100755 --- a/lib/cli/commands/help.rb +++ b/lib/cli/commands/help.rb @@ -1,17 +1,17 @@ -require 'commands/meta/command' +require_relative 'meta/command' module Nutella class Help < Command - @description = 'Displays what every command does and how to use it' + @description = 'Displays what every command does' def run(args=nil) message='' - Dir["#{File.dirname(__FILE__)}/*.rb"].each do |file| + Dir["#{File.dirname(__FILE__)}/*.rb"].sort.each do |file| message += add_cmd_help(file) end console.info message - console.success 'For more details on individual commands, see https://github.com/nutella-framework/nutella_framework/wiki/Nutella-Command-Line-Interface' + console.info 'For more details on individual commands, see http://nutella-framework.github.io' end private diff --git a/lib/cli/commands/install.rb b/lib/cli/commands/install.rb index 6c23458..88e86a9 100755 --- a/lib/cli/commands/install.rb +++ b/lib/cli/commands/install.rb @@ -1,4 +1,4 @@ -require 'commands/meta/template_command' +require_relative 'meta/template_command' require 'git' require 'net/http' @@ -76,7 +76,7 @@ def is_template_in_db?( template_name ) end - def add_local_template ( template, template_dir, prj_dir, dest_dir_name) + def add_local_template( template, template_dir, prj_dir, dest_dir_name) template_nutella_file_json = JSON.parse(IO.read("#{template_dir}/nutella.json")) # If destination is not specified, set it to the template name @@ -108,7 +108,7 @@ def add_local_template ( template, template_dir, prj_dir, dest_dir_name) end - def add_remote_template ( template, prj_dir, dest_dir) + def add_remote_template( template, prj_dir, dest_dir) template_name = template[template.rindex('/')+1 .. template.length-5] template_dir = "#{Nutella::NUTELLA_TMP}/#{template_name}" add_local_template( template, template_dir, prj_dir, dest_dir ) diff --git a/lib/cli/commands/meta/run_command.rb b/lib/cli/commands/meta/run_command.rb index f37240b..88ca08a 100755 --- a/lib/cli/commands/meta/run_command.rb +++ b/lib/cli/commands/meta/run_command.rb @@ -1,5 +1,5 @@ -require 'commands/meta/command' -require 'commands/util/components_list' +require_relative 'command' +require_relative '../util/components_list' require 'slop' module Nutella @@ -7,7 +7,7 @@ module Nutella # It is mostly a commodity class for code reuse. class RunCommand < Command - def run (args=nil) + def run(args=nil) console.error 'Running the generic RunCommand!!! WAT? https://www.destroyallsoftware.com/talks/wat' end diff --git a/lib/cli/commands/meta/template_command.rb b/lib/cli/commands/meta/template_command.rb index f9cc2d7..cd580fb 100755 --- a/lib/cli/commands/meta/template_command.rb +++ b/lib/cli/commands/meta/template_command.rb @@ -1,4 +1,4 @@ -require 'commands/meta/command' +require_relative 'command' require 'json' module Nutella @@ -7,7 +7,7 @@ module Nutella class TemplateCommand < Command - def run (args=nil) + def run(args=nil) console.error 'Running generic TemplateCommand!!! WAT?' end diff --git a/lib/cli/commands/new.rb b/lib/cli/commands/new.rb index 90bf1dd..7deb6f5 100755 --- a/lib/cli/commands/new.rb +++ b/lib/cli/commands/new.rb @@ -1,4 +1,4 @@ -require 'commands/meta/command' +require_relative 'meta/command' require 'fileutils' module Nutella diff --git a/lib/cli/commands/reset.rb b/lib/cli/commands/reset.rb index 4bc22f1..1dca0bc 100644 --- a/lib/cli/commands/reset.rb +++ b/lib/cli/commands/reset.rb @@ -1,4 +1,4 @@ -require 'commands/meta/command' +require_relative 'meta/command' module Nutella class Reset < Command diff --git a/lib/cli/commands/runs.rb b/lib/cli/commands/runs.rb index e8c446c..baa84eb 100755 --- a/lib/cli/commands/runs.rb +++ b/lib/cli/commands/runs.rb @@ -1,4 +1,4 @@ -require 'commands/meta/command' +require_relative 'meta/command' module Nutella diff --git a/lib/cli/commands/start.rb b/lib/cli/commands/start.rb index 9d213b9..e48f4cd 100755 --- a/lib/cli/commands/start.rb +++ b/lib/cli/commands/start.rb @@ -1,5 +1,5 @@ -require 'commands/meta/run_command' -require 'commands/util/components_starter' +require_relative 'meta/run_command' +require_relative 'util/components_starter' module Nutella class Start < RunCommand diff --git a/lib/cli/commands/stop.rb b/lib/cli/commands/stop.rb index 25319ec..426d705 100755 --- a/lib/cli/commands/stop.rb +++ b/lib/cli/commands/stop.rb @@ -1,4 +1,4 @@ -require 'commands/meta/run_command' +require_relative 'meta/run_command' require 'tmux/tmux' module Nutella diff --git a/lib/cli/commands/template.rb b/lib/cli/commands/template.rb index f88ef36..db8ba07 100755 --- a/lib/cli/commands/template.rb +++ b/lib/cli/commands/template.rb @@ -1,4 +1,4 @@ -require 'commands/meta/template_command' +require_relative 'meta/template_command' module Nutella diff --git a/lib/cli/commands/util/components_starter.rb b/lib/cli/commands/util/components_starter.rb index 7dff158..217cd1e 100755 --- a/lib/cli/commands/util/components_starter.rb +++ b/lib/cli/commands/util/components_starter.rb @@ -1,4 +1,4 @@ -require 'commands/util/components_list' +require_relative 'components_list' require 'tmux/tmux' # Utility functions to start components diff --git a/lib/logging/nutella_logger-remote.rb b/lib/logging/nutella_logger-remote.rb deleted file mode 100755 index 406ed04..0000000 --- a/lib/logging/nutella_logger-remote.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Nutella - - class NutellaLoggerRemote < NutellaLogger - - def debug(message, code=nil) - @log.debug(message) - code - end - - def info(message, code=nil) - @log.info(message) - code - end - - def success(message, code=nil) - @log.info(ANSI.green + message + ANSI.reset) - code - end - - def warn(message, code=nil) - @log.warn(ANSI.yellow + message + ANSI.reset) - code - end - - def error(message, code=nil) - @log.error(ANSI.red + message + ANSI.reset) - code - end - - end -end \ No newline at end of file diff --git a/lib/logging/nutella_logger.rb b/lib/logging/nutella_logger.rb deleted file mode 100755 index 2a477d4..0000000 --- a/lib/logging/nutella_logger.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'ansi/code' -require 'logging' - -module Nutella - - class NutellaLogger - - def initialize(name) - @log = Logging.logger[name] - @log.add_appenders(Logging.appenders.stdout( - :layout => Logging.layouts.pattern(:pattern => '%m\n') - ) - ) - end - - def debug(message, code=nil) - @log.debug(ANSI.cyan + message + ANSI.reset) - code - end - - def info(message, code=nil) - @log.info(message) - code - end - - def success(message, code=nil) - @log.info(ANSI.green + message + ANSI.reset) - code - end - - def warn(message, code=nil) - @log.warn(ANSI.yellow + message + ANSI.reset) - code - end - - def error(message, code=nil) - @log.error(ANSI.red + message + ANSI.reset) - code - end - - end -end \ No newline at end of file diff --git a/lib/logging/nutella_logging.rb b/lib/logging/nutella_logging.rb deleted file mode 100755 index ca61c3e..0000000 --- a/lib/logging/nutella_logging.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'singleton' -require 'logging/nutella_logger' -require 'logging/nutella_logger-remote' - -module Nutella - - class NutellaLogging - include Singleton - - def initialize - console = NutellaLogger.new('console') - log = NutellaLoggerRemote.new('log') - @loggers = {:log => log, :console => console} - end - - def logger(name) - @loggers[name] - end - - end - -end - - -module Kernel - - def console - Nutella::NutellaLogging.instance.logger(:console) - end - - def log - Nutella::NutellaLogging.instance.logger(:log) - end - -end diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index f70d70c..fd99dfe 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -4,8 +4,6 @@ require 'config_files_management/config' require 'cli/nutella_cli' # require 'logging/nutella_logging' -# require 'cli/nutella_core' - # require 'config/runlist' # require 'config/current_app_utils' diff --git a/spec/cli/cli_utils_spec.rb b/spec/cli/cli_utils_spec.rb new file mode 100644 index 0000000..e69de29 diff --git a/spec/cli/commands/help_spec.rb b/spec/cli/commands/help_spec.rb new file mode 100644 index 0000000..73ca182 --- /dev/null +++ b/spec/cli/commands/help_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' +require 'cli/cli' + +module Nutella + describe Help do + it 'outpurs the comends description' do + expect{ NutellaCLI.execute_command('help') }.to output.to_stdout + end + end +end \ No newline at end of file From bfca4d766a90e756fdde2d32ca9ca07abafa95a3 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 29 Sep 2019 19:41:28 -0700 Subject: [PATCH 10/43] Remove framework library components --- legacy_nutella_lib/framework_core.rb | 60 --- legacy_nutella_lib/framework_log.rb | 49 --- legacy_nutella_lib/framework_net.rb | 538 ------------------------ legacy_nutella_lib/framework_persist.rb | 125 ------ 4 files changed, 772 deletions(-) delete mode 100755 legacy_nutella_lib/framework_core.rb delete mode 100755 legacy_nutella_lib/framework_log.rb delete mode 100755 legacy_nutella_lib/framework_net.rb delete mode 100755 legacy_nutella_lib/framework_persist.rb diff --git a/legacy_nutella_lib/framework_core.rb b/legacy_nutella_lib/framework_core.rb deleted file mode 100755 index b704dea..0000000 --- a/legacy_nutella_lib/framework_core.rb +++ /dev/null @@ -1,60 +0,0 @@ -require 'nutella_lib' -require 'config/runlist' - -# APIs sub-modules -require_relative 'framework_net' -require_relative 'framework_log' -require_relative 'framework_persist' - - -module Nutella - - # Accessor to the framework APIs sub-module - def Nutella.f - Nutella::Framework - end - - - # Framework-level APIs sub-module - module Framework - - # Initializes this component as a framework component - # @param [String] broker_hostname - # @param [String] component_id - def self.init( broker_hostname, component_id ) - Nutella.app_id = nil - Nutella.run_id = nil - Nutella.component_id = component_id - Nutella.resource_id = nil - Nutella.mongo_host = broker_hostname - Nutella.mqtt = SimpleMQTTClient.new broker_hostname - # Start pinging - Nutella.net.start_pinging - end - - # Accessors for sub-modules - def self.net; Nutella::Framework::Net; end - def self.log; Nutella::Framework::Log; end - def self.persist; Nutella::Framework::Persist; end - - - # Utility functions - - # Extracts the component name from the folder where the code for this component is located - # - # @return [String] the component name - def self.extract_component_id - Nutella.extract_component_id - end - - # Sets the resource id - # - # @param [String] resource_id the resource id (i.e. the particular instance of this component) - def self.set_resource_id( resource_id ) - Nutella.set_resource_id resource_id - end - - - end - -end diff --git a/legacy_nutella_lib/framework_log.rb b/legacy_nutella_lib/framework_log.rb deleted file mode 100755 index 5f03f76..0000000 --- a/legacy_nutella_lib/framework_log.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'ansi' - -module Nutella - - module Framework - - module Log - - def self.debug(message, code=nil) - puts( ANSI.cyan + message + ANSI.reset ) - Nutella.f.net.publish( 'logging', log_to_json(message, code, __method__) ) - code - end - - def self.info(message, code=nil) - puts( message ) - Nutella.f.net.publish( 'logging', log_to_json(message, code, __method__) ) - code - end - - def self.success(message, code=nil) - puts( ANSI.green + message + ANSI.reset ) - Nutella.f.net.publish( 'logging', log_to_json(message, code, __method__) ) - code - end - - def self.warn(message, code=nil) - puts( ANSI.yellow + message + ANSI.reset ) - Nutella.f.net.publish( 'logging', log_to_json(message, code, __method__) ) - code - end - - def self.error(message, code=nil) - puts( ANSI.red + message + ANSI.reset ) - Nutella.f.net.publish( 'logging', log_to_json(message, code, __method__) ) - code - end - - private - - def self.log_to_json( message, code, level) - code.nil? ? {level: level, message: message} : {level: level, message: message, code: code} - end - - end - - end - -end \ No newline at end of file diff --git a/legacy_nutella_lib/framework_net.rb b/legacy_nutella_lib/framework_net.rb deleted file mode 100755 index 9dd1d65..0000000 --- a/legacy_nutella_lib/framework_net.rb +++ /dev/null @@ -1,538 +0,0 @@ -module Nutella - - module Framework - - module Net - - # @!group Framework-level communication APIs - - # Subscribes to a channel or to a set of channels at the framework-level. - # - # @param [String] channel the framework-level channel or filter we are subscribing to. Can contain wildcard(s) - # @param [Proc] callback a lambda expression that is fired whenever a message is received. - # The passed callback takes the following parameters: - # - [String] message: the received message. Messages that are not JSON are discarded. - # - [String] channel: the framework-level channel the message was received on (optional, only for wildcard subscriptions) - # - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - def self.subscribe (channel, callback) - Nutella::Net.subscribe_to(channel, callback, nil, nil) - end - - - # Un-subscribes from a framework-level channel - # - # @param [String] channel the framework-level channel we want to unsubscribe from. Can contain wildcard(s). - def self.unsubscribe( channel ) - Nutella::Net.unsubscribe_to(channel, nil, nil) - end - - - # Publishes a message to an framework-level channel - # - # @param [String] channel the framework-level channel we want to publish the message to. *CANNOT* contain wildcard(s)! - # @param [Object] message the message we are publishing. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - def self.publish(channel, message=nil) - Nutella::Net.publish_to(channel, message, nil, nil) - end - - - # Performs a synchronous request at the framework-level - # - # @param [String] channel the framework-level channel we want to make the request to. *CANNOT* contain wildcard(s)! - # @param [Object] message the body of request. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - def self.sync_request ( channel, message=nil ) - Nutella::Net.sync_request_to(channel, message, nil, nil) - end - - - # Performs an asynchronous request at the framework-level - # - # @param [String] channel the framework-level channel we want to make the request to. *CANNOT* contain wildcard(s)! - # @param [Object] message the body of request. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - # @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response). - def self.async_request ( channel, message=nil, callback ) - Nutella::Net.async_request_to(channel, message, callback, nil, nil) - end - - - # Handles requests on a certain framework-level channel - # - # @param [String] channel tha framework-level channel we want to listen for requests on. Can contain wildcard(s). - # @param [Proc] callback a lambda expression that is fired whenever a message is received. - # The passed callback takes the following parameters: - # - [String] the received message (payload). Messages that are not JSON are discarded. - # - [Hash] the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - # - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - def self.handle_requests( channel, callback ) - Nutella::Net.handle_requests_on(channel, callback, nil, nil) - end - - # @!endgroup - - # ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - # @!group Framework-level APIs to communicate at the run-level - - # Allows framework-level APIs to subscribe to a run-level channel within a specific run - # - # @param [String] app_id the specific application we are subscribing to - # @param [String] run_id the specific run we are subscribing to - # @param [String] channel the run-level channel we are subscribing to. Can be wildcard. - # @param [Proc] callback the callback that is fired whenever a message is received on the channel. - # The passed callback takes the following parameters: - # - [String] message: the received message. Messages that are not JSON are discarded. - # - [String] channel: the framework-level channel the message was received on (optional, only for wildcard subscriptions) - # - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - def self.subscribe_to_run( app_id, run_id, channel, callback ) - Nutella::Net.subscribe_to(channel, callback, app_id, run_id) - end - - - # Allows framework-level APIs to unsubscribe from a run-level channel within a specific run - # - # @param [String] app_id the specific application we are un-subscribing from - # @param [String] run_id the specific run we are un-subscribing from - # @param [String] channel the run-level channel we want to unsubscribe from. Can contain wildcard(s). - def self.unsubscribe_to_run( app_id, run_id, channel ) - Nutella::Net.unsubscribe_to(channel, app_id, run_id) - end - - - # Allows framework-level APIs to publish to a run-level channel within a specific run - # - # @param [String] app_id the specific application we are publishing to - # @param [String] run_id the specific run we are publishing to - # @param [String] channel the run-level channel we want to publish the message to. *CANNOT* contain wildcard(s)! - # @param [String] message the message we are publishing. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - def self.publish_to_run( app_id, run_id, channel, message ) - Nutella::Net.publish_to(channel, message, app_id, run_id) - end - - - # Allows framework-level APIs to make a synchronous request to a run-level channel within a specific run - # - # @param [String] app_id the specific application we are making the request to - # @param [String] run_id the specific run we are making the request to - # @param [String] channel the channel we want to make the request to. *CANNOT* contain wildcard(s)! - # @param [Object] request the body of request. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - def self.sync_request_to_run( app_id, run_id, channel, request) - Nutella::Net.sync_request_to(channel, request, app_id, run_id) - end - - - # Allows framework-level APIs to make an asynchronous request to a run-level channel within a specific run - # - # @param [String] app_id the specific application we are making the request to - # @param [String] run_id the specific run we are making the request to - # @param [String] channel the channel we want to make the request to. *CANNOT* contain wildcard(s)! - # @param [Object] request the body of request. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - # @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response). - def self.async_request_to_run( app_id, run_id, channel, request, callback) - Nutella::Net.async_request_to(channel, request, callback, app_id, run_id) - end - - - # Allows framework-level APIs to handle requests on a run-level channel within a specific run - # - # @param [String] app_id the specific application requests are coming from - # @param [String] run_id the specific run requests are coming from - # @param [String] channel we want to listen for requests on. Can contain wildcard(s). - # @param [Proc] callback a lambda expression that is fired whenever a message is received. - # The passed callback takes the following parameters: - # - [String] the received message (payload). Messages that are not JSON are discarded. - # - [Hash] the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - # - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - def self.handle_requests_on_run( app_id, run_id, channel, callback ) - Nutella::Net.handle_requests_on(channel, callback, app_id, run_id) - end - - # @!endgroup - - # ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - # @!group Framework-level APIs to communicate at the run-level (broadcast) - - # Allows framework-level APIs to subscribe to a run-level channel *for ALL runs* - # - # @param [String] channel the run-level channel we are subscribing to. Can be wildcard. - # @param [Proc] callback the callback that is fired whenever a message is received on the channel. - # The passed callback takes the following parameters: - # - [String] message: the received message. Messages that are not JSON are discarded. - # - [String] app_id: the app_id of the channel the message was sent on - # - [String] run_id: the run_id of the channel the message was sent on - # - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - def self.subscribe_to_all_runs( channel, callback ) - # Check the passed callback has the right number of arguments - raise 'You need to pass a callback with 4 parameters (payload, app_id, run_id, from) when subscribing to all runs!' if callback.parameters.length!=4 && callback.parameters.length!=5 - # Pad channel - padded_channel = Nutella::Net.pad_channel(channel, '+', '+') - mqtt_cb = lambda do |mqtt_message, mqtt_channel| - begin - type, from, payload, _ = Nutella::Net.extract_fields_from_message mqtt_message - app_id, run_id = self.extract_run_id_and_app_id mqtt_channel - if channel.include?('#') - unpadded_channel = un_pad_wildcard_channel(mqtt_channel) - callback.call(unpadded_channel, payload, app_id, run_id, from) if type=='publish' - else - callback.call(payload, app_id, run_id, from) if type=='publish' - end - rescue JSON::ParserError - # Make sure the message is JSON, if not drop the message - return - rescue ArgumentError - # Check the passed callback has the right number of arguments - STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'payload', 'app_id', 'run_id' and 'from'" - end - end - # Add to subscriptions, save mqtt callback and subscribe - Nutella::Net.subscriptions.push padded_channel - Nutella::Net.callbacks.push mqtt_cb - Nutella.mqtt.subscribe( padded_channel, mqtt_cb ) - # Notify subscription - Nutella::Net.publish_to('subscriptions', {'type' => 'subscribe', 'channel' => padded_channel}, nil, nil) - end - - def self.un_pad_wildcard_channel(channel) - regex = Regexp.new '/nutella/apps/[^"\/"]+/runs/[^"\/"]+/' - channel.gsub(regex, '') - end - - - # Allows framework-level APIs to unsubscribe from a run-level channel *for ALL runs* - # - # @param [String] channel the run-level channel we want to unsubscribe from. Can contain wildcard(s). - def self.unsubscribe_from_all_runs( channel ) - Nutella::Net.unsubscribe_to(channel, '+', '+') - end - - - # Allows framework-level APIs to publish a message to a run-level channel *for ALL runs* - # - # @param [String] channel the run-level channel we want to publish the message to. *CANNOT* contain wildcard(s)! - # @param [Object] message the message we are publishing. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - def self.publish_to_all_runs( channel, message ) - Nutella.runlist.all_runs.each do |app_id, _| - Nutella.runlist.runs_for_app(app_id).each do |run_id| - Nutella::Net.publish_to(channel, message, app_id, run_id) - end - end - end - - - # Allows framework-level APIs to send a request to a run-level channel *for ALL runs* - # - # @param [String] channel the run-level channel we want to make the request to. *CANNOT* contain wildcard(s)! - # @param [Object] request the body of request. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - # @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response). - def self.async_request_to_all_runs(channel, request, callback) - Nutella.runlist.all_runs.each do |app_id, _| - Nutella.runlist.runs_for_app(app_id).each do |run_id| - Nutella::Net.async_request_to(channel, request, callback, app_id, run_id) - end - end - end - - - # Allows framework-level APIs to handle requests to a run-level channel *for ALL runs* - # - # @param [String] channel tha run-level channel we want to listen for requests on. Can contain wildcard(s). - # @param [Proc] callback a lambda expression that is fired whenever a message is received. - # The passed callback takes the following parameters: - # - [String] the received message (request). Messages that are not JSON are discarded. - # - [String] app_id: the app_id of the channel the request was sent on - # - [String] run_id: the run_id of the channel the request was sent on - # - [Hash] the sender's identifiers (from containing, run_id, app_id, component_id and optionally resource_id) - # - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - def self.handle_requests_on_all_runs(channel, callback) - # Check the passed callback has the right number of arguments - raise 'You need to pass a callback with 4 parameters (request, run_id, from) when handling requests!' if callback.parameters.length!=4 - # Pad channel - padded_channel = Nutella::Net.pad_channel(channel, '+', '+') - mqtt_cb = lambda do |request, mqtt_channel| - begin - # Extract nutella fields - type, from, payload, id = Nutella::Net.extract_fields_from_message request - app_id, run_id = self.extract_run_id_and_app_id mqtt_channel - # Only handle requests that have proper id set - return if type!='request' || id.nil? - # Execute callback and send response - m = Nutella::Net.prepare_message_for_response( callback.call( payload, app_id, run_id, from), id ) - Nutella.mqtt.publish( mqtt_channel, m ) - rescue JSON::ParserError - # Make sure that request contains JSON, if not drop the message - return - rescue ArgumentError - # Check the passed callback has the right number of arguments - STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'request', 'app_id', 'run_id' and 'from'" - end - end - # Subscribe to the channel - Nutella.mqtt.subscribe( padded_channel, mqtt_cb ) - # Notify subscription - Nutella::Net.publish_to('subscriptions', {'type' => 'handle_requests', 'channel' => padded_channel}, nil, nil) - end - - def self.catch_requests_on_all_runs_wildcard(callback) - # Pad channel - padded_channel = Nutella::Net.pad_channel("#", '+', '+') - mqtt_cb = lambda do |request, mqtt_channel| - begin - # Extract nutella fields - type, from, payload, id = Nutella::Net.extract_fields_from_message request - app_id, run_id = self.extract_run_id_and_app_id mqtt_channel - # Only handle requests that have proper id set - return if type!='request' || id.nil? - # Execute callback and send response - regex = Regexp.new '/nutella/apps/[^"\/"]+/runs/[^"\/"]+/' - unpadded_channel = mqtt_channel.gsub(regex, '') - callback.call( unpadded_channel, payload, app_id, run_id, from) - rescue JSON::ParserError - # Make sure that request contains JSON, if not drop the message - return - rescue ArgumentError - # Check the passed callback has the right number of arguments - STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'request', 'app_id', 'run_id' and 'from'" - end - end - # Subscribe to the channel - Nutella.mqtt.subscribe( padded_channel, mqtt_cb ) - end - - # @!endgroup - - # ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - # @!group Framework-level APIs to communicate at the application-level - - - # Allows framework-level APIs to subscribe to an app-level channel within a specific run - # - # @param [String] app_id the specific application we are subscribing to - # @param [String] channel the run-level channel we are subscribing to. Can be wildcard. - # @param [Proc] callback the callback that is fired whenever a message is received on the channel. - # The passed callback takes the following parameters: - # - [String] message: the received message. Messages that are not JSON are discarded. - # - [String] channel: the framework-level channel the message was received on (optional, only for wildcard subscriptions) - # - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - def self.subscribe_to_app(app_id, channel, callback) - Nutella::Net.subscribe_to(channel, callback, app_id, nil) - end - - - # Allows framework-level APIs to unsubscribe from an app-level channel within a specific run - # - # @param [String] app_id the specific application we are un-subscribing from - # @param [String] run_id the specific run we are un-subscribing from - # @param [String] channel the run-level channel we want to unsubscribe from. Can contain wildcard(s). - def self.unsubscribe_to_app( app_id, channel ) - Nutella::Net.unsubscribe_to(channel, app_id, nil) - end - - - # Allows framework-level APIs to publish to an app-level channel within a specific run - # - # @param [String] app_id the specific application we are publishing to - # @param [String] channel the run-level channel we want to publish the message to. *CANNOT* contain wildcard(s)! - # @param [String] message the message we are publishing. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - def self.publish_to_app(app_id, channel, message) - Nutella::Net.publish_to(channel, message, app_id, nil) - end - - - # Allows framework-level APIs to make a synchronous request to a run-level channel within a specific run - # - # @param [String] app_id the specific application we are making the request to - # @param [String] run_id the specific run we are making the request to - # @param [String] channel the channel we want to make the request to. *CANNOT* contain wildcard(s)! - # @param [Object] request the body of request. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - def self.sync_request_to_app( app_id, channel, request) - Nutella::Net.sync_request_to(channel, request, app_id, nil) - end - - - # Allows framework-level APIs to make an asynchronous request to a run-level channel within a specific run - # - # @param [String] app_id the specific application we are making the request to - # @param [String] run_id the specific run we are making the request to - # @param [String] channel the channel we want to make the request to. *CANNOT* contain wildcard(s)! - # @param [Object] request the body of request. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - # @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response). - def self.async_request_to_app( app_id, channel, request, callback) - Nutella::Net.async_request_to(channel, request, callback, app_id, nil) - end - - - # Allows framework-level APIs to handle requests on a run-level channel within a specific run - # - # @param [String] app_id the specific application requests are coming from - # @param [String] run_id the specific run requests are coming from - # @param [String] channel we want to listen for requests on. Can contain wildcard(s). - # @param [Proc] callback a lambda expression that is fired whenever a message is received. - # The passed callback takes the following parameters: - # - [String] the received message (payload). Messages that are not JSON are discarded. - # - [Hash] the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - # - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - def self.handle_requests_on_app(app_id, channel, callback) - Nutella::Net.handle_requests_on(channel, callback, app_id, nil) - end - - - - - # @!endgroup - - # ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - # @!group Framework-level APIs to communicate at the application-level (broadcast) - - - # Allows framework-level APIs to subscribe to an app-level channel *for ALL apps* - # - # @param [String] channel the app-level channel we are subscribing to. Can be wildcard. - # @param [Proc] callback the callback that is fired whenever a message is received on the channel. - # The passed callback takes the following parameters: - # - [String] message: the received message. Messages that are not JSON are discarded. - # - [String] app_id: the app_id of the channel the message was sent on - # - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id) - def self.subscribe_to_all_apps(channel, callback) - # Check the passed callback has the right number of arguments - raise 'You need to pass a callback with 3 parameters (payload, app_id, from) when subscribing to all apps!' if callback.parameters.length!=3 - # Pad channel - padded_channel = Nutella::Net.pad_channel(channel, '+', nil) - mqtt_cb = lambda do |mqtt_message, mqtt_channel| - begin - type, from, payload, _ = Nutella::Net.extract_fields_from_message mqtt_message - app_id = self.extract_app_id mqtt_channel - callback.call(payload, app_id, from) if type=='publish' - rescue JSON::ParserError - # Make sure the message is JSON, if not drop the message - return - rescue ArgumentError - # Check the passed callback has the right number of arguments - STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'payload', 'app_id' and 'from'" - end - end - # Add to subscriptions, save mqtt callback and subscribe - Nutella::Net.subscriptions.push padded_channel - Nutella::Net.callbacks.push mqtt_cb - Nutella.mqtt.subscribe( padded_channel, mqtt_cb ) - # Notify subscription - Nutella::Net.publish_to('subscriptions', {'type' => 'subscribe', 'channel' => padded_channel}, nil, nil) - end - - - # Allows framework-level APIs to unsubscribe from an app-level channel *for ALL apps* - # - # @param [String] channel the run-level channel we want to unsubscribe from. Can contain wildcard(s). - def self.unsubscribe_from_all_apps( channel ) - Nutella::Net.unsubscribe_to(channel, '+', nil) - end - - - # Allows framework-level APIs to publish a message to an app-level channel *for ALL apps* - # - # @param [String] channel the app-level channel we want to publish the message to. *CANNOT* contain wildcard(s)! - # @param [Object] message the message we are publishing. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - def self.publish_to_all_apps(channel, message) - Nutella.runlist.all_runs.each do |app_id, _| - Nutella::Net.publish_to(channel, message, app_id, nil) - end - end - - - # Allows framework-level APIs to send a request to a run-level channel *for ALL runs* - # - # @param [String] channel the app-level channel we want to make the request to. *CANNOT* contain wildcard(s)! - # @param [Object] request the body of request. This can be, - # nil/empty (default), a string, a hash and, in general, anything with a .to_json method. - # @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response). - def self.async_request_to_all_apps(channel, request, callback) - Nutella.runlist.all_runs.each do |app_id, _| - Nutella::Net.async_request_to(channel, request, callback, app_id, nil) - end - end - - - # Allows framework-level APIs to handle requests to a run-level channel *for ALL runs* - # - # @param [String] channel tha app-level channel we want to listen for requests on. Can contain wildcard(s). - # @param [Proc] callback a lambda expression that is fired whenever a message is received. - # The passed callback takes the following parameters: - # - [String] the received message (request). Messages that are not JSON are discarded. - # - [String] app_id: the app_id of the channel the request was sent on - # - [Hash] the sender's identifiers (from containing, run_id, app_id, component_id and optionally resource_id) - # - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT. - def self.handle_requests_on_all_apps(channel, callback) - # Check the passed callback has the right number of arguments - raise 'You need to pass a callback with 3 parameters (request, run_id, from) when handling requests!' if callback.parameters.length!=3 - # Pad channel - padded_channel = Nutella::Net.pad_channel(channel, '+', nil) - mqtt_cb = lambda do |request, mqtt_channel| - begin - # Extract nutella fields - type, from, payload, id = Nutella::Net.extract_fields_from_message request - app_id = self.extract_app_id mqtt_channel - # Only handle requests that have proper id set - return if type!='request' || id.nil? - # Execute callback and send response - m = Nutella::Net.prepare_message_for_response( callback.call( payload, app_id, from), id ) - Nutella.mqtt.publish( mqtt_channel, m ) - rescue JSON::ParserError - # Make sure that request contains JSON, if not drop the message - return - rescue ArgumentError - # Check the passed callback has the right number of arguments - STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'request', 'app_id' and 'from'" - end - end - # Subscribe to the channel - Nutella.mqtt.subscribe( padded_channel, mqtt_cb ) - # Notify subscription - Nutella::Net.publish_to('subscriptions', {'type' => 'handle_requests', 'channel' => padded_channel}, nil, nil) - end - - # @!endgroup - - - # ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - - # Listens for incoming messages. All this function - # does is to put the thread to sleep and wait for something to - # happen over the network to wake up. - def self.listen - Nutella::Net.listen - end - - - private - - - def self.extract_run_id_and_app_id( mqtt_channel ) - sp = mqtt_channel.sub('/nutella/apps/', '').split('/') - return sp[0], sp[2] - end - - - def self.extract_app_id( mqtt_channel ) - sp = mqtt_channel.sub('/nutella/apps/', '').split('/') - return sp[0] - end - - end # net - - end # framework - -end # nutella diff --git a/legacy_nutella_lib/framework_persist.rb b/legacy_nutella_lib/framework_persist.rb deleted file mode 100755 index 18abd11..0000000 --- a/legacy_nutella_lib/framework_persist.rb +++ /dev/null @@ -1,125 +0,0 @@ -require 'util/mongo_persisted_collection' -require 'util/json_file_persisted_collection' -require 'util/mongo_persisted_hash' -require 'util/json_file_persisted_hash' -require 'fileutils' - - -module Nutella - - module Framework - - # Implements basic persistence for framework-level components - module Persist - - # @!group Framework-level APIs - - # This method returns a MongoDB-backed store (i.e. persistence) - # for a collection (i.e. an Array) - # @param [String] name the name of the store - # @return [MongoPersistedCollection] a MongoDB-backed collection store - def self.get_mongo_collection_store( name ) - MongoPersistedCollection.new Nutella.mongo_host, 'nutella', name - end - - # This method returns a MongoDB-backed store (i.e. persistence) - # for a single object (i.e. an Hash) - # @param [String] name the name of the store - # @return [MongoPersistedHash] a MongoDB-backed Hash store - def self.get_mongo_object_store( name ) - MongoPersistedHash.new Nutella.mongo_host, 'nutella', 'fr_persisted_hashes', name - end - - # This method returns a JSON-file-backed store (i.e. persistence) - # for a collection (i.e. an Array) - # @param [String] name the name of the store - # @return [JSONFilePersistedCollection] a JSON-file-backed collection store - def self.get_json_collection_store( name ) - dir_path = "#{ENV['HOME']}/.nutella/data/#{Nutella.component_id}" - file_path = "#{dir_path}/#{name}.json" - FileUtils.mkdir_p dir_path - JSONFilePersistedCollection.new file_path - end - - # This method returns a JSON-file-backed store (i.e. persistence) - # for a single object (i.e. an Hash) - # @param [String] name the name of the store - # @return [JSONFilePersistedHash] a JSON-file-backed Hash store - def self.get_json_object_store( name ) - dir_path = "#{ENV['HOME']}/.nutella/data/#{Nutella.component_id}" - file_path = "#{dir_path}/#{name}.json" - FileUtils.mkdir_p dir_path - JSONFilePersistedHash.new file_path - end - - # @!endgroup - - # ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - # @!group Run-level APIs - - # This method returns a MongoDB-backed store - # for a collection at the run level - # @param [String] app_id - # @param [String] run_id - # @param [String] name the name of the store - # @return [MongoPersistedCollection] a MongoDB-backed collection store - def self.get_run_mongo_collection_store( app_id, run_id, name ) - if @run_stores.nil? - @run_stores = {} - end - unless @run_stores.include? [app_id, run_id, name] - @run_stores[[app_id, run_id, name]] = MongoPersistedCollection.new Nutella.mongo_host, app_id, "#{run_id}/#{name}" - end - @run_stores[[app_id, run_id, name]] - end - - # This method returns a MongoDB-backed store - # for a single object at the run level - # @param [String] app_id - # @param [String] run_id - # @param [String] name the name of the store - # @return [MongoPersistedHash] a MongoDB-backed Hash store - def self.get_run_mongo_object_store( app_id, run_id, name ) - if @run_stores.nil? - @run_stores = {} - end - unless @run_stores.include? [app_id, run_id, name] - @run_stores[[app_id, run_id, name]] = MongoPersistedHash.new Nutella.mongo_host, app_id, 'run_persisted_hashes', "#{run_id}/#{name}" - end - @run_stores[[app_id, run_id, name]] - end - - # This method returns a JSON-file-backed store - # for a collection at the run level - # @param [String] app_id - # @param [String] run_id - # @param [String] name the name of the store - # @return [JSONFilePersistedCollection] a JSON-file-backed collection store - def self.get_run_json_collection_store( app_id, run_id, name ) - dir_path = "#{ENV['HOME']}/.nutella/data/#{Nutella.component_id}/#{app_id}/#{run_id}" - file_path = "#{dir_path}/#{name}.json" - FileUtils.mkdir_p dir_path - JSONFilePersistedCollection.new file_path - end - - # This method returns a JSON-file-backed store - # for a single object at the run level - # @param [String] app_id - # @param [String] run_id - # @param [String] name the name of the store - # @return [JSONFilePersistedHash] a JSON-file-backed Hash store - def self.get_run_json_object_store( app_id, run_id, name ) - dir_path = "#{ENV['HOME']}/.nutella/data/#{Nutella.component_id}/#{app_id}/#{run_id}" - file_path = "#{dir_path}/#{name}.json" - FileUtils.mkdir_p dir_path - JSONFilePersistedHash.new file_path - end - - - # @!endgroup - - end - end - -end \ No newline at end of file From 1e920c9e0614b14b9cafa9f8be7b8eea96746d89 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 13 Oct 2019 19:26:22 -0700 Subject: [PATCH 11/43] Started implementing new server command --- .../bots/commands_server/commands_server.rb | 0 lib/cli/cli.rb | 1 + lib/cli/commands/checkup.rb | 19 +- lib/cli/commands/compile.rb | 1 - lib/cli/commands/dependencies.rb | 1 - lib/cli/commands/meta/command.rb | 2 - lib/cli/commands/meta/run_command.rb | 2 +- lib/cli/commands/server.rb | 29 +++ lib/cli/commands/start.rb | 2 +- lib/cli/commands/stop.rb | 1 - lib/cli/commands/util/components_starter.rb | 204 ------------------ .../config.rb | 5 +- .../current_app_utils.rb | 0 .../persisted_hash.rb | 0 .../runlist.rb | 0 lib/tmux/tmux.rb | 76 ------- .../commands => }/util/components_list.rb | 0 lib/util/framework_components_starter.rb | 109 ++++++++++ lib/util/immortal.rb | 4 + lib/util/mongo.rb | 60 ++++++ lib/util/mqtt_broker.rb | 59 +++++ lib/util/pid.rb | 29 +++ spec/cli/commands/checkup_spec.rb | 10 + spec/cli/commands/help_spec.rb | 2 +- spec/cli/commands/server_spec.rb | 12 ++ .../config_spec.rb | 3 +- .../persisted_hash_spec.rb | 3 +- 27 files changed, 333 insertions(+), 301 deletions(-) rename spec/cli/cli_utils_spec.rb => lib/bots/commands_server/commands_server.rb (100%) create mode 100644 lib/cli/commands/server.rb delete mode 100755 lib/cli/commands/util/components_starter.rb rename lib/{config_files_management => config}/config.rb (87%) rename lib/{config_files_management => config}/current_app_utils.rb (100%) rename lib/{config_files_management => config}/persisted_hash.rb (100%) rename lib/{config_files_management => config}/runlist.rb (100%) delete mode 100755 lib/tmux/tmux.rb rename lib/{cli/commands => }/util/components_list.rb (100%) create mode 100755 lib/util/framework_components_starter.rb create mode 100644 lib/util/immortal.rb create mode 100644 lib/util/mongo.rb create mode 100644 lib/util/mqtt_broker.rb create mode 100644 lib/util/pid.rb create mode 100644 spec/cli/commands/checkup_spec.rb create mode 100644 spec/cli/commands/server_spec.rb rename spec/{config_files_management => config}/config_spec.rb (96%) rename spec/{config_files_management => config}/persisted_hash_spec.rb (97%) diff --git a/spec/cli/cli_utils_spec.rb b/lib/bots/commands_server/commands_server.rb similarity index 100% rename from spec/cli/cli_utils_spec.rb rename to lib/bots/commands_server/commands_server.rb diff --git a/lib/cli/cli.rb b/lib/cli/cli.rb index 11c007d..97a0390 100755 --- a/lib/cli/cli.rb +++ b/lib/cli/cli.rb @@ -1,4 +1,5 @@ require_relative 'cli_utils' +# Require all the commands Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| require_relative "commands/#{File.basename(file, File.extname(file))}" end diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index d039d25..eb4ac48 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -1,4 +1,5 @@ require_relative 'meta/command' +require 'config/config' require 'semantic' module Nutella @@ -23,7 +24,7 @@ def run( args=nil ) end # Set ready flag in config.json - Nutella.config['ready'] = true + Config.file['ready'] = true # Output success message console.success 'All systems go! You are ready to use nutella!' @@ -37,7 +38,7 @@ def broker_exists # Check if Docker image for the broker was already pulled if `docker images matteocollina/mosca:v2.3.0 --format "{{.ID}}"` != "" # If so, check that a broker configuration exists and create one if it doesn't - Nutella.config['broker'] = '127.0.0.1' if Nutella.config['broker'].nil? + Config.file['broker'] = '127.0.0.1' if Config.file['broker'].nil? true else false @@ -49,7 +50,7 @@ def install_local_broker # Docker pull to install system "docker pull matteocollina/mosca:v2.3.0 > /dev/null 2>&1" # Write broker setting inside config.json - Nutella.config['broker'] = '127.0.0.1' + Config.file['broker'] = '127.0.0.1' end @@ -72,11 +73,11 @@ def all_dependencies_installed? end semver end - # Tmux version lambda - tmux_semver = lambda do - out = `tmux -V` - out.slice!(0,5) - Semantic::Version.new "#{out[0..2]}.0" + # Immortal version lambda + immortal_semver = lambda do + out = `immortal -v` + out.gsub("\n",'') + Semantic::Version.new out end # Mongo version lambda mongo_semver = lambda do @@ -85,7 +86,7 @@ def all_dependencies_installed? Semantic::Version.new out[0..4] end # Check versions - return true if check_version?('docker', '17.0.0', docker_semver) && check_version?('git', '1.8.0', git_semver) && check_version?('tmux', '1.8.0', tmux_semver) && check_version?('mongodb', '2.6.9', mongo_semver) + return true if check_version?('docker', '17.0.0', docker_semver) && check_version?('git', '1.8.0', git_semver) && check_version?('immortal', '0.23.0', immortal_semver) && check_version?('mongodb', '2.6.9', mongo_semver) # If even one of the checks fails, return false false end diff --git a/lib/cli/commands/compile.rb b/lib/cli/commands/compile.rb index 21b676f..9cc1070 100755 --- a/lib/cli/commands/compile.rb +++ b/lib/cli/commands/compile.rb @@ -1,5 +1,4 @@ require_relative 'meta/run_command' -require 'tmux/tmux' module Nutella class Compile < RunCommand diff --git a/lib/cli/commands/dependencies.rb b/lib/cli/commands/dependencies.rb index b929bc5..fd7b5dd 100755 --- a/lib/cli/commands/dependencies.rb +++ b/lib/cli/commands/dependencies.rb @@ -1,5 +1,4 @@ require_relative 'meta/run_command' -require 'tmux/tmux' module Nutella class Dependencies < RunCommand diff --git a/lib/cli/commands/meta/command.rb b/lib/cli/commands/meta/command.rb index e12c0f9..4d3f047 100755 --- a/lib/cli/commands/meta/command.rb +++ b/lib/cli/commands/meta/command.rb @@ -1,5 +1,3 @@ - - module Nutella # Nutella command diff --git a/lib/cli/commands/meta/run_command.rb b/lib/cli/commands/meta/run_command.rb index 88ca08a..758f040 100755 --- a/lib/cli/commands/meta/run_command.rb +++ b/lib/cli/commands/meta/run_command.rb @@ -1,5 +1,5 @@ require_relative 'command' -require_relative '../util/components_list' +require 'util/components_list' require 'slop' module Nutella diff --git a/lib/cli/commands/server.rb b/lib/cli/commands/server.rb new file mode 100644 index 0000000..32db2fa --- /dev/null +++ b/lib/cli/commands/server.rb @@ -0,0 +1,29 @@ +require_relative 'meta/command' +require 'util/mqtt_broker' +require 'util/mongo' +require 'util/framework_components_starter' + +module Nutella + class Server < RunCommand + @description = 'Starts the MQTT broker and the framework level bots' + + def run(args=nil) + if MQTTBroker.start + console.success('MQTT broker started') + else + console.error('Failed to start MQTT broker') + end + if Mongo.start + console.success('Mongo started') + else + console.error('Failed to start Mongo') + end + if FrameworkComponentsStarter.start + console.success('Framework level components started') + else + console.error('Failed to start Framework level components') + end + end + end + +end \ No newline at end of file diff --git a/lib/cli/commands/start.rb b/lib/cli/commands/start.rb index e48f4cd..60b421c 100755 --- a/lib/cli/commands/start.rb +++ b/lib/cli/commands/start.rb @@ -1,5 +1,5 @@ require_relative 'meta/run_command' -require_relative 'util/components_starter' +# require 'util/components_starter' module Nutella class Start < RunCommand diff --git a/lib/cli/commands/stop.rb b/lib/cli/commands/stop.rb index 426d705..fe3d89a 100755 --- a/lib/cli/commands/stop.rb +++ b/lib/cli/commands/stop.rb @@ -1,5 +1,4 @@ require_relative 'meta/run_command' -require 'tmux/tmux' module Nutella class Stop < RunCommand diff --git a/lib/cli/commands/util/components_starter.rb b/lib/cli/commands/util/components_starter.rb deleted file mode 100755 index 217cd1e..0000000 --- a/lib/cli/commands/util/components_starter.rb +++ /dev/null @@ -1,204 +0,0 @@ -require_relative 'components_list' -require 'tmux/tmux' - -# Utility functions to start components -class ComponentsStarter - - # Starts the internal broker if it's not started already - # @return [boolean] true if the broker is correctly started, false otherwise - def self.start_internal_broker - # Check if the broker has been started already, if it is, return - return true if broker_started? - # Check that broker is not running 'unsupervised' (i.e. check port 1883), if it is, return - return true unless broker_port_free? - # Broker is not running so we try to start the internal broker - cid = `docker run -p 1883:1883 -p 1884:80 -d -v #{Nutella.config['broker_dir']}:/db matteocollina/mosca:v2.3.0` - # Wait a bit to give the chance to the broker to actually start up - sleep 1 - # All went well so we return true - true - end - - - # Starts mongodb if it's not started already. - # This operation is only necessary on mac because Ubuntu automatically - # installs mongo as a service and runs it. - # @return [boolean] true if mongo has been correctly started, false otherwise - def self.start_mongo_db - pid_file_path = "#{Nutella.config['config_dir']}.mongo_pid" - # Check if the process with pid indicated in the pidfile is alive - return true if sanitize_pid_file pid_file_path - # Check that mongo is not running 'unsupervised' (i.e. check port 27017), if it is, return - return true unless mongo_port_free? - # Mongo is not running and there is no pid file so we try to start it and create a new pid file. - # Note that the pid file is created by the `startup` script, not here. - pid = fork - exec("mongod --config /usr/local/etc/mongod.conf > /dev/null 2>&1 & \necho $! > #{pid_file_path}") if pid.nil? - # Wait a bit to give the chance to mongo to actually start up - sleep 1 - # All went well so we return true - true - end - - - # Starts all framework components. If order.json is present, components are started - # in that order. - # @return [boolean] true if all components are started correctly, false otherwise - def self.start_framework_components - nutella_components_dir = "#{Nutella::NUTELLA_HOME}framework_components" - if File.exist? "#{nutella_components_dir}/order.json" - components_list = JSON.parse IO.read "#{nutella_components_dir}/order.json" - else - components_list = ComponentsList.components_in_dir nutella_components_dir - end - components_list.each do |component| - if File.exist? "#{nutella_components_dir}/#{component}/startup" - unless start_framework_component "#{nutella_components_dir}/#{component}" - return false - end - end - end - true - end - - - # Starts the application level bots - # @return [boolean] true if all bots are started correctly, false otherwise - def self.start_app_bots( app_id, app_path ) - app_bots_list = Nutella.current_app.config['app_bots'] - bots_dir = "#{app_path}/bots/" - # If app bots have been started already, then do nothing - unless Nutella::Tmux.session_exist? Nutella::Tmux.app_bot_session_name app_id - # Start all app bots in the list into a new tmux session - tmux = Nutella::Tmux.new app_id, nil - ComponentsList.for_each_component_in_dir bots_dir do |bot| - unless app_bots_list.nil? || !app_bots_list.include?( bot ) - # If there is no 'startup' script output a warning (because - # startup is mandatory) and skip the bot - unless File.exist?("#{bots_dir}#{bot}/startup") - console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." - next - end - # Create a new window in the session for this run - tmux.new_app_bot_window bot - end - end - end - true - end - - - def self.start_run_bots( bots_list, app_path, app_id, run_id ) - # Create a new tmux instance for this run - tmux = Nutella::Tmux.new app_id, run_id - # Fetch bots dir - bots_dir = "#{app_path}/bots/" - # Start the appropriate bots - bots_list.each { |bot| start_run_level_bot(bots_dir, bot, tmux) } - true - end - - - #--- Private class methods -------------- - - - # Cleans the pid file of a given process - # @param [String] pid_file_path the file storing the pid file of the process - # @return [Boolean] true if the pid file exists AND the process with that pid is still alive - def self.sanitize_pid_file( pid_file_path ) - # Does the pid file exist? - # If it does we try to see if the process with that pid is still alive - if File.exist? pid_file_path - pid_file = File.open(pid_file_path, 'rb') - pid = pid_file.read.to_i - pid_file.close - begin - # If this statement doesn't throw an exception then a process with - # this pid is still alive so we do nothing and just return true - Process.getpgid pid - return true - rescue - # If there is an exception, there is no process with this pid - # so we have a stale pid file that we need to remove - File.delete pid_file_path - return false - end - end - # If there is no pid file, there is no process running - false - end - private_class_method :sanitize_pid_file - - - # Checks if the broker is running already - # @return [boolean] true if there is a container for the broker running already - def self.broker_started? - `docker ps --filter ancestor=matteocollina/mosca:v2.3.0 --format "{{.ID}}"` != "" - end - private_class_method :broker_started? - - # Checks if port 1883 (MQTT broker port) is free - # or some other service is already listening on it - # @return [boolean] true if there is no broker listening on port 1883, false otherwise - def self.broker_port_free? - begin - s = TCPServer.new('0.0.0.0', 1883) - s.close - rescue - return false - end - true - end - private_class_method :broker_port_free? - - - # Checks if port 27017 (MongoDB standard port) is free - # or some other service is already listening on it - # @return [boolean] true if there is no mongo listening on port 27017, false otherwise - def self.mongo_port_free? - begin - s = TCPServer.new('0.0.0.0', 27017) - s.close - rescue - return false - end - true - end - private_class_method :mongo_port_free? - - - # Starts a single framework component - # @return [boolean] true if the component has been started successfully, false otherwise - def self.start_framework_component( component_dir ) - pid_file_path = "#{component_dir}/.pid" - return true if sanitize_pid_file pid_file_path - # Component is not running and there is no pid file so we try to start it - # and create a new pid file. Note that the pid file is created by - # the startup script! - # Framework components are started without any parameters passed to them because they have - # full access to config, runlist and framework APIs using 'require_relative' - command = "#{component_dir}/startup" - pid = fork - exec(command) if pid.nil? - # Give it a second so they can start properly - sleep 1 - # All went well so we return true - true - end - private_class_method :start_framework_component - - - # Starts a run level bot - def self.start_run_level_bot( bots_dir, bot, tmux ) - # If there is no 'startup' script output a warning (because - # startup is mandatory) and skip the bot - unless File.exist?("#{bots_dir}#{bot}/startup") - console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." - return - end - # Create a new window in the session for this run - tmux.new_bot_window bot - end - private_class_method :start_run_level_bot - -end \ No newline at end of file diff --git a/lib/config_files_management/config.rb b/lib/config/config.rb similarity index 87% rename from lib/config_files_management/config.rb rename to lib/config/config.rb index 3f2b47b..7cad5e8 100755 --- a/lib/config_files_management/config.rb +++ b/lib/config/config.rb @@ -1,10 +1,11 @@ -require 'config_files_management/persisted_hash' +require_relative 'persisted_hash' module Nutella class Config # This method initializes the nutella configuration file (config.json) with: # - config_dir: directory where the configuration files are stored in # - broker_dir: directory where the local broker is installed in + # - immortal_dir: directory used to store immortal yaml files # - main_interface_port: the port used to serve interfaces def self.init file['config_dir'] = "#{ENV['HOME']}/.nutella/" @@ -18,4 +19,4 @@ def self.file PersistedHash.new( "#{ENV['HOME']}/.nutella/config.json" ) end end -end +end \ No newline at end of file diff --git a/lib/config_files_management/current_app_utils.rb b/lib/config/current_app_utils.rb similarity index 100% rename from lib/config_files_management/current_app_utils.rb rename to lib/config/current_app_utils.rb diff --git a/lib/config_files_management/persisted_hash.rb b/lib/config/persisted_hash.rb similarity index 100% rename from lib/config_files_management/persisted_hash.rb rename to lib/config/persisted_hash.rb diff --git a/lib/config_files_management/runlist.rb b/lib/config/runlist.rb similarity index 100% rename from lib/config_files_management/runlist.rb rename to lib/config/runlist.rb diff --git a/lib/tmux/tmux.rb b/lib/tmux/tmux.rb deleted file mode 100755 index 3bfc7f4..0000000 --- a/lib/tmux/tmux.rb +++ /dev/null @@ -1,76 +0,0 @@ -module Nutella - - class Tmux - - def initialize( app_id, run_id ) - @app_id = app_id - @run_id = run_id - end - - # Creates a new window (and session if necessary) to start a run-level bot - def new_bot_window( bot ) - # Create session name - sn = Tmux.session_name(@app_id, @run_id) - # Create session - create_tmux_window(sn, bot) - # Start bot - `tmux send-keys "cd bots/#{bot};./startup #{Nutella.config['broker']} #{@app_id} #{@run_id}" C-m` - end - - # Creates a new window (and session if necessary) to start an app-level bot - def new_app_bot_window( bot ) - # Create session name - sn = Tmux.app_bot_session_name(@app_id) - # Create session - create_tmux_window(sn, bot) - # Start bot - `tmux send-keys "cd bots/#{bot};./startup #{Nutella.config['broker']} #{@app_id}" C-m` - end - - # Removes a run-level session associated to a particular run - def self.kill_run_session( app_id, run_id ) - `tmux kill-session -t #{session_name(app_id, run_id)} > /dev/null 2>&1` - end - - # Removes the app-level session associated to a particular application - def self.kill_app_session( app_id ) - `tmux kill-session -t #{app_bot_session_name( app_id )} > /dev/null 2>&1` - end - - # Returns true if a tmux session with a certain id exists - def self.session_exist?( session_id ) - system( "tmux has-session -t #{session_id} > /dev/null 2>&1" ) - end - - # Builds a session name for run-level session - def self.session_name( app_id, run_id ) - "#{app_id}/#{run_id}" - end - - # Builds a session name for an app-level session - def self.app_bot_session_name( app_id ) - "#{app_id}-app-bots" - end - - - private - - def create_tmux_window( session_name, bot ) - # If a session already exists, simply create a new window (-n) for 'bot'. - # -k destroys the window if it can't be created - # -P prints info about creation of window - # If there is no sessions, let's create one (-s) and, at the same time, create a new window for the bot - if defined? @windows - `tmux new-window -kP -n #{bot} &> /dev/null` - @windows.push bot - else - `tmux new-session -d -s #{session_name} -n #{bot} &> /dev/null` - @windows = [bot] - end - # Select the last window we launched - `tmux select-window -t #{session_name}:#{@windows.length-1} &> /dev/null` - end - - end - -end diff --git a/lib/cli/commands/util/components_list.rb b/lib/util/components_list.rb similarity index 100% rename from lib/cli/commands/util/components_list.rb rename to lib/util/components_list.rb diff --git a/lib/util/framework_components_starter.rb b/lib/util/framework_components_starter.rb new file mode 100755 index 0000000..6dba5aa --- /dev/null +++ b/lib/util/framework_components_starter.rb @@ -0,0 +1,109 @@ +# require_relative 'components_list' + +module Nutella + # Utility functions to start components + class FrameworkComponentsStarter + include PidFile + + def self.start + FrameworkComponentsStarter.new.start_framework_components + end + + # Starts all framework components. If order.json is present, components are started + # in that order. + # @return [boolean] true if all components are started correctly, false otherwise + def start_framework_components + nutella_components_dir = "#{NUTELLA_HOME}framework_components" + if File.exist? "#{nutella_components_dir}/order.json" + components_list = JSON.parse IO.read "#{nutella_components_dir}/order.json" + else + components_list = ComponentsList.components_in_dir nutella_components_dir + end + components_list.each do |component| + if File.exist? "#{nutella_components_dir}/#{component}/startup" + unless start_framework_component "#{nutella_components_dir}/#{component}" + return false + end + end + end + true + end + + + # Starts the application level bots + # @return [boolean] true if all bots are started correctly, false otherwise + def self.start_app_bots( app_id, app_path ) + app_bots_list = Nutella.current_app.config['app_bots'] + bots_dir = "#{app_path}/bots/" + # If app bots have been started already, then do nothing + unless Nutella::Tmux.session_exist? Nutella::Tmux.app_bot_session_name app_id + # Start all app bots in the list into a new tmux session + tmux = Nutella::Tmux.new app_id, nil + ComponentsList.for_each_component_in_dir bots_dir do |bot| + unless app_bots_list.nil? || !app_bots_list.include?( bot ) + # If there is no 'startup' script output a warning (because + # startup is mandatory) and skip the bot + unless File.exist?("#{bots_dir}#{bot}/startup") + console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." + next + end + # Create a new window in the session for this run + tmux.new_app_bot_window bot + end + end + end + true + end + + + def self.start_run_bots( bots_list, app_path, app_id, run_id ) + # Create a new tmux instance for this run + tmux = Nutella::Tmux.new app_id, run_id + # Fetch bots dir + bots_dir = "#{app_path}/bots/" + # Start the appropriate bots + bots_list.each { |bot| start_run_level_bot(bots_dir, bot, tmux) } + true + end + + + #--- Private class methods -------------- + + + # Starts a single framework component + # @return [boolean] true if the component has been started successfully, false otherwise + def self.start_framework_component( component_dir ) + pid_file_path = "#{component_dir}/.pid" + return true if sanitize_pid_file pid_file_path + # Component is not running and there is no pid file so we try to start it + # and create a new pid file. Note that the pid file is created by + # the startup script! + # Framework components are started without any parameters passed to them because they have + # full access to config, runlist and framework APIs using 'require_relative' + command = "#{component_dir}/startup" + pid = fork + exec(command) if pid.nil? + # Give it a second so they can start properly + sleep 1 + # All went well so we return true + true + end + private_class_method :start_framework_component + + + # Starts a run level bot + def self.start_run_level_bot( bots_dir, bot, tmux ) + # If there is no 'startup' script output a warning (because + # startup is mandatory) and skip the bot + unless File.exist?("#{bots_dir}#{bot}/startup") + console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." + return + end + # Create a new window in the session for this run + tmux.new_bot_window bot + end + private_class_method :start_run_level_bot + + end + +end diff --git a/lib/util/immortal.rb b/lib/util/immortal.rb new file mode 100644 index 0000000..32c008d --- /dev/null +++ b/lib/util/immortal.rb @@ -0,0 +1,4 @@ +# This module provides a simple wrapper around immortal because, you know... +module Immortal + +end \ No newline at end of file diff --git a/lib/util/mongo.rb b/lib/util/mongo.rb new file mode 100644 index 0000000..a02004c --- /dev/null +++ b/lib/util/mongo.rb @@ -0,0 +1,60 @@ +require 'socket' +require 'util/pid' + +module Nutella + class Mongo + include PidFile + + def self.start + Mongo.new.start_mongo_db + end + + # Starts mongodb if it's not started already. + # This operation is only necessary on mac because Ubuntu automatically + # installs mongo as a service and runs it. + # @return [boolean] true if mongo has been correctly started, false otherwise + def start_mongo_db + pid_file_path = "#{Config.file['config_dir']}.mongo_pid" + # Check if the process with pid indicated in the pidfile is alive + return true if PidFile.sanitize pid_file_path + # Check that mongo is not running 'unsupervised' (i.e. check port 27017), if it is, return + return true unless mongo_port_free? + # Mongo is not running and there is no pid file so we try to start it and create a new pid file. + # Note that the pid file is created by the `startup` script, not here. + pid = fork + exec("mongod --config /usr/local/etc/mongod.conf > /dev/null 2>&1 & \necho $! > #{pid_file_path}") if pid.nil? + # Wait until mongo is up + wait_for_mongo + # All went well so we return true + true + end + + private + + # Checks if port 27017 (MongoDB standard port) is free + # or some other service is already listening on it + # @return [boolean] true if there is no mongo listening on port 27017, false otherwise + def mongo_port_free? + begin + s = TCPServer.new('0.0.0.0', 27017) + s.close + rescue + return false + end + true + end + + # Checks if there is connectivity to localhost:27017. If not, + # it waits 1/4 second and then tries again + def wait_for_mongo + begin + s = TCPSocket.open('localhost', 27017) + s.close + rescue Errno::ECONNREFUSED + sleep 0.25 + wait_for_mongo + end + end + + end +end diff --git a/lib/util/mqtt_broker.rb b/lib/util/mqtt_broker.rb new file mode 100644 index 0000000..14a4410 --- /dev/null +++ b/lib/util/mqtt_broker.rb @@ -0,0 +1,59 @@ +require 'socket' +require 'config/config' + +module Nutella + class MQTTBroker + + def self.start + MQTTBroker.new.start_internal_broker + end + + def start_internal_broker + # Check if the broker has been started already, if it is, return + return true if broker_started? + # Check that broker is not running 'unsupervised' (i.e. check port 1883), if it is, return + return true unless broker_port_free? + # Broker is not running so we try to start it broker + # TODO need to check that this is actually successfull... + `docker run -p 1883:1883 -p 1884:80 -d -v #{Config.file['broker_dir']}:/db matteocollina/mosca:v2.3.0` + # Wait until the broker is up + wait_for_broker + # All went well so we return true + true + end + + private + + # Checks if the broker is running already + # @return [boolean] true if there is a container for the broker running already + def broker_started? + `docker ps --filter ancestor=matteocollina/mosca:v2.3.0 --format "{{.ID}}"` != "" + end + + # Checks if port 1883 (MQTT broker port) is free + # or some other service is already listening on it + # @return [boolean] true if there is no broker listening on port 1883, false otherwise + def broker_port_free? + begin + s = TCPServer.new('0.0.0.0', 1883) + s.close + rescue + return false + end + true + end + + # Checks if there is connectivity to localhost:1883. If not, + # it waits 1/4 second and then tries again + def wait_for_broker + begin + s = TCPSocket.open('localhost', 1883) + s.close + rescue Errno::ECONNREFUSED + sleep 0.25 + wait_for_broker + end + end + + end +end diff --git a/lib/util/pid.rb b/lib/util/pid.rb new file mode 100644 index 0000000..85f173a --- /dev/null +++ b/lib/util/pid.rb @@ -0,0 +1,29 @@ +module PidFile + + # Cleans the pid file of a given process + # @param [String] pid_file_path the file storing the pid file of the process + # @return [Boolean] true if the pid file exists AND the process with that pid is still alive + def PidFile.sanitize( pid_file_path ) + # Does the pid file exist? + # If it does we try to see if the process with that pid is still alive + if File.exist? pid_file_path + pid_file = File.open(pid_file_path, 'rb') + pid = pid_file.read.to_i + pid_file.close + begin + # If this statement doesn't throw an exception then a process with + # this pid is still alive so we do nothing and just return true + Process.getpgid pid + return true + rescue + # If there is an exception, there is no process with this pid + # so we have a stale pid file that we need to remove + File.delete pid_file_path + return false + end + end + # If there is no pid file, there is no process running + false + end + +end \ No newline at end of file diff --git a/spec/cli/commands/checkup_spec.rb b/spec/cli/commands/checkup_spec.rb new file mode 100644 index 0000000..7b6f9c2 --- /dev/null +++ b/spec/cli/commands/checkup_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' +require 'cli/cli' + +module Nutella + describe Checkup do + skip 'executes correctly' do + NutellaCLI.execute_command('checkup') + end + end +end \ No newline at end of file diff --git a/spec/cli/commands/help_spec.rb b/spec/cli/commands/help_spec.rb index 73ca182..69032ff 100644 --- a/spec/cli/commands/help_spec.rb +++ b/spec/cli/commands/help_spec.rb @@ -3,7 +3,7 @@ module Nutella describe Help do - it 'outpurs the comends description' do + it 'outputs the commands description' do expect{ NutellaCLI.execute_command('help') }.to output.to_stdout end end diff --git a/spec/cli/commands/server_spec.rb b/spec/cli/commands/server_spec.rb new file mode 100644 index 0000000..0bf5226 --- /dev/null +++ b/spec/cli/commands/server_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' +require 'cli/cli' + +module Nutella + describe Server do + skip 'Starts the MQTT broker' do + NutellaCLI.execute_command('server') + # TODO resume from here... need to implement + # framework components start using immortal... + end + end +end \ No newline at end of file diff --git a/spec/config_files_management/config_spec.rb b/spec/config/config_spec.rb similarity index 96% rename from spec/config_files_management/config_spec.rb rename to spec/config/config_spec.rb index 38927ac..c4ab1b6 100644 --- a/spec/config_files_management/config_spec.rb +++ b/spec/config/config_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' -require 'config_files_management/config' + +require 'config/config' require 'securerandom' module Nutella diff --git a/spec/config_files_management/persisted_hash_spec.rb b/spec/config/persisted_hash_spec.rb similarity index 97% rename from spec/config_files_management/persisted_hash_spec.rb rename to spec/config/persisted_hash_spec.rb index de8b507..942699e 100644 --- a/spec/config_files_management/persisted_hash_spec.rb +++ b/spec/config/persisted_hash_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' -require 'config_files_management/persisted_hash' + +require 'config/persisted_hash' require 'securerandom' module Nutella From f9ecc987a6e10e9128c174f4c36f4903e0338931 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 26 Oct 2019 16:15:02 -0700 Subject: [PATCH 12/43] Basic supervisor working --- .gitignore | 2 + Gemfile | 14 ++-- README.md | 10 +-- lib/bots/binary-files-manager/startup | 5 -- lib/bots/commands_server/commands_server.rb | 18 +++++ lib/bots/commands_server/startup | 4 ++ lib/bots/main_interface/main_interface_bot.rb | 9 ++- lib/bots/main_interface/startup | 5 -- lib/bots/order.json | 5 -- lib/bots/runs_list_bot/startup | 5 -- lib/cli/cli.rb | 2 +- lib/cli/commands/checkup.rb | 17 ++++- lib/nutella_framework.rb | 10 +-- lib/util/components_list.rb | 3 + lib/util/framework_components_starter.rb | 57 +++++---------- lib/util/immortal.rb | 4 -- lib/util/supervisor.rb | 69 +++++++++++++++++++ spec/cli/commands/server_spec.rb | 4 +- 18 files changed, 147 insertions(+), 96 deletions(-) delete mode 100755 lib/bots/binary-files-manager/startup create mode 100755 lib/bots/commands_server/startup delete mode 100755 lib/bots/main_interface/startup delete mode 100755 lib/bots/order.json delete mode 100755 lib/bots/runs_list_bot/startup delete mode 100644 lib/util/immortal.rb create mode 100644 lib/util/supervisor.rb diff --git a/.gitignore b/.gitignore index 0f4d962..1e5460c 100755 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ runlist.json broker/ .tmp/ .pid +.mongo_pid +stdout.log # Test app test_app/ diff --git a/Gemfile b/Gemfile index 5e89aa2..e470bb2 100755 --- a/Gemfile +++ b/Gemfile @@ -1,16 +1,16 @@ source 'http://rubygems.org' -gem 'semantic', '~> 1.4' gem "ansi", "~> 1.5" +gem 'bson', '~> 3.0' gem 'git', '~> 1.2' +gem 'nutella_lib','~>0.4', '>=0.4.24' +gem 'nokogiri', '~>1.6' +gem 'slop', '~>4.0' +gem 'semantic', '~> 1.4' gem 'sinatra', '~>1.4' gem 'sinatra-cross_origin', '~> 0.3.2' gem 'thin', '~>1.6' -gem 'nokogiri', '~>1.6' -gem 'slop', '~>4.0' -gem 'nutella_lib','~>0.4', '>=0.4.24' -gem 'activesupport', '~>4.2' -gem 'bson', '~> 3.0' +gem "xmlrpc", "~> 0.3.0" group :development do gem 'yard', '~> 0.9.11' @@ -24,5 +24,3 @@ group :test do gem 'rspec', '~> 3.8' gem 'fuubar', '~> 2.4' end - - diff --git a/README.md b/README.md index bb130dd..5eb733b 100755 --- a/README.md +++ b/README.md @@ -7,19 +7,19 @@ nutella is a framework to build and run Macroworlds. The original prototype was # Installing nutella works on OSX and Linux (tested on Ubuntu) and it depends on a couple other things to work correctly. You will need: -1. _ruby_ (version >= 2.1.0). Do yourself a favor and use [RVM](https://rvm.io/rvm/install) to install Ruby. +1. _ruby_ (version >= 2.3.0). Do yourself a favor and use [RVM](https://rvm.io/rvm/install) to install Ruby. 1. _git_ (version >= 1.8.0). Should come with the OS, yay! -1. _tmux_ (version >= 1.8.0). Do yourself a favor and use [Homebrew](http://brew.sh/) to install tmux, if you are on OSX. -1. _Docker_ (version >= 17.03.0). We use Docker to run the broker that handles all communications between all the pieces of the framework. Do yourself a favor and use [Docker for mac](https://store.docker.com/editions/community/docker-ce-desktop-mac), if you are on OSX. +1. _supervisor_ (version >= 4.1.0). You can use [Homebrew](http://brew.sh/) to install supervisor on OSX. +1. _docker_ (version >= 17.03.0). We use Docker to run the broker that handles all communications between all the pieces of the framework. Do yourself a favor and use [Docker for mac](https://store.docker.com/editions/community/docker-ce-desktop-mac), if you are on OSX. -Once you have all of installed, simply do: +Once you have all of thi stuff installed you can do: ``` gem install nutella_framework ``` Once the installation is complete you should be able to type `nutella` in your shell and get a welcome message. ## nutella checkup -If you are reading this you probably already saw the warning: "Looks like this is a fresh installation of nutella. Please run `nutella checkup` to check all dependencies are installed". +If you are reading this you probably already saw the warning: "Looks like this is a fresh installation of nutella. Please run `nutella checkup` to check all dependencies are installed correctly". **Please follow the prompt!** ``` nutella checkup diff --git a/lib/bots/binary-files-manager/startup b/lib/bots/binary-files-manager/startup deleted file mode 100755 index f31332d..0000000 --- a/lib/bots/binary-files-manager/startup +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -BASEDIR=$(dirname $0) -ruby $BASEDIR/bin_files_mngr.rb > /dev/null 2>&1 & -echo $! > $BASEDIR/.pid diff --git a/lib/bots/commands_server/commands_server.rb b/lib/bots/commands_server/commands_server.rb index e69de29..681d1a0 100644 --- a/lib/bots/commands_server/commands_server.rb +++ b/lib/bots/commands_server/commands_server.rb @@ -0,0 +1,18 @@ +$stdout.sync = true ## nutella woudl do this +# $stdout.sync = true +puts "Hi, I'm a basic ruby bot and all I do is idle and print stuff" +$stderr.puts "certainly first param is set #{ARGV[0]}" + + +begin + i = 0 + while true + puts "#{i} A log line!" + i = i + 1 + sleep 1 + end +rescue SignalException => e + puts "HEY I WAS KILLED!!!" + $stderr.puts "AND I COMPLAIN IN STDERR" + puts e +end diff --git a/lib/bots/commands_server/startup b/lib/bots/commands_server/startup new file mode 100755 index 0000000..281e528 --- /dev/null +++ b/lib/bots/commands_server/startup @@ -0,0 +1,4 @@ +#!/bin/sh + +BASEDIR=$(dirname $0) +ruby $BASEDIR/commands_server.rb diff --git a/lib/bots/main_interface/main_interface_bot.rb b/lib/bots/main_interface/main_interface_bot.rb index c334ce7..b70c5d8 100755 --- a/lib/bots/main_interface/main_interface_bot.rb +++ b/lib/bots/main_interface/main_interface_bot.rb @@ -2,16 +2,15 @@ require 'sinatra' require 'nokogiri' -require_relative '../../lib/config/runlist' -require_relative '../../lib/config/config' -require_relative '../../nutella_lib/framework_core' +require_relative '../../config/config' +require_relative '../../config/runlist' # Set Sinatra to run in production mode set :environment, :production # Set Sinatra's port to nutella's main_interface_port -set :port, Nutella.config['main_interface_port'] +set :port, Nutella::Config.file['main_interface_port'] # Disable X-Frame-Options header to allow iframes set :protection, :except => :frame_options @@ -63,7 +62,7 @@ # If the index file doesn't exist, render error page return erb( :not_found_404, :locals => {:not_found_type => 'idx'} ) unless File.exist? index_file_path # If the index file exists, compose query string and redirect - index_with_query_url = "#{request.path}/index.html?broker=#{Nutella.config['broker']}&app_id=#{app_id}&run_id=#{run_id}" + index_with_query_url = "#{request.path}/index.html?broker=#{Nutella::Config.file['broker']}&app_id=#{app_id}&run_id=#{run_id}" redirect index_with_query_url end diff --git a/lib/bots/main_interface/startup b/lib/bots/main_interface/startup deleted file mode 100755 index 927e060..0000000 --- a/lib/bots/main_interface/startup +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -BASEDIR=$(dirname $0) -ruby $BASEDIR/main_interface_bot.rb > /dev/null 2>&1 & -echo $! > $BASEDIR/.pid diff --git a/lib/bots/order.json b/lib/bots/order.json deleted file mode 100755 index 6f5a176..0000000 --- a/lib/bots/order.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "runs_list_bot", - "main_interface", - "binary-files-manager" -] \ No newline at end of file diff --git a/lib/bots/runs_list_bot/startup b/lib/bots/runs_list_bot/startup deleted file mode 100755 index a1b52e7..0000000 --- a/lib/bots/runs_list_bot/startup +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -BASEDIR=$(dirname $0) -ruby $BASEDIR/runs_list_bot.rb > /dev/null 2>&1 & -echo $! > $BASEDIR/.pid diff --git a/lib/cli/cli.rb b/lib/cli/cli.rb index 97a0390..9b7e56f 100755 --- a/lib/cli/cli.rb +++ b/lib/cli/cli.rb @@ -71,7 +71,7 @@ def self.print_nutella_logo console.info("Welcome to nutella version #{nutella_version}! For a complete lists of available commands type 'nutella help'\n") # If nutella is not ready to be used (i.e. nobody has invoked the "nutella checkup" command yet), # append warning/reminder message - if Nutella.config['ready'].nil? + if Config.file['ready'].nil? console.warn 'Looks like this is a fresh installation of nutella. Please run \'nutella checkup\' to check all dependencies are installed.' end end diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index eb4ac48..a8b7e23 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -22,6 +22,9 @@ def run( args=nil ) return end end + + # Check that supervisor is properly configured + return unless supervisor_configured_correctly? # Set ready flag in config.json Config.file['ready'] = true @@ -74,8 +77,8 @@ def all_dependencies_installed? semver end # Immortal version lambda - immortal_semver = lambda do - out = `immortal -v` + supervisor_semver = lambda do + out = `supervisorctl version` out.gsub("\n",'') Semantic::Version.new out end @@ -86,7 +89,7 @@ def all_dependencies_installed? Semantic::Version.new out[0..4] end # Check versions - return true if check_version?('docker', '17.0.0', docker_semver) && check_version?('git', '1.8.0', git_semver) && check_version?('immortal', '0.23.0', immortal_semver) && check_version?('mongodb', '2.6.9', mongo_semver) + return true if check_version?('docker', '17.0.0', docker_semver) && check_version?('git', '1.8.0', git_semver) && check_version?('supervisor', '4.1.0', supervisor_semver) && check_version?('mongodb', '2.6.9', mongo_semver) # If even one of the checks fails, return false false end @@ -110,6 +113,14 @@ def check_version?(dep, req_version, lambda) end end + + def supervisor_configured_correctly? + # TODO Check that supervisor's MAC_CONFIG_DIR exists, if not create + # TODO Make sure the MAC_CONFIG_DIR is inluded in supervisor's MAC_CONFIG_FILE, if not include + # TODO Make sure that [inet_http_server] (rpc server) is enabled in MAC_CONFIG_FILE + true + end + end end diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index fd99dfe..302f313 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -1,11 +1,5 @@ -# This is the entry point to the gem. The code here gets executed BEFORE -# anything else. For this reason, this is a great place to import all the -# nutella modules. -require 'config_files_management/config' -require 'cli/nutella_cli' -# require 'logging/nutella_logging' -# require 'config/runlist' -# require 'config/current_app_utils' +require 'config/config' +require 'cli/cli' module Nutella diff --git a/lib/util/components_list.rb b/lib/util/components_list.rb index a8ffa59..4fdc4cb 100755 --- a/lib/util/components_list.rb +++ b/lib/util/components_list.rb @@ -1,3 +1,6 @@ +module Nutella + +end # Utility methods to list components class ComponentsList diff --git a/lib/util/framework_components_starter.rb b/lib/util/framework_components_starter.rb index 6dba5aa..f6bce17 100755 --- a/lib/util/framework_components_starter.rb +++ b/lib/util/framework_components_starter.rb @@ -1,34 +1,35 @@ -# require_relative 'components_list' +require 'util/supervisor' module Nutella # Utility functions to start components class FrameworkComponentsStarter - include PidFile def self.start - FrameworkComponentsStarter.new.start_framework_components + FrameworkComponentsStarter.new.start end - # Starts all framework components. If order.json is present, components are started - # in that order. + # Starts all framework components. # @return [boolean] true if all components are started correctly, false otherwise - def start_framework_components - nutella_components_dir = "#{NUTELLA_HOME}framework_components" - if File.exist? "#{nutella_components_dir}/order.json" - components_list = JSON.parse IO.read "#{nutella_components_dir}/order.json" - else - components_list = ComponentsList.components_in_dir nutella_components_dir - end - components_list.each do |component| - if File.exist? "#{nutella_components_dir}/#{component}/startup" - unless start_framework_component "#{nutella_components_dir}/#{component}" - return false - end + def start + supervisor = Supervisor.new + nutella_components_dir = "#{Nutella::NUTELLA_HOME}lib/bots" + framework_components.each do |c| + if File.exist? "#{nutella_components_dir}/#{c}/startup" + supervisor.add("nutella_f_#{c}", "#{nutella_components_dir}/#{c}/startup") end end + framework_components.each do |c| + supervisor.start("nutella_f_#{c}") + end true end + # Finds the framework level components that need to be started + def framework_components + d = "#{Nutella::NUTELLA_HOME}lib/bots" + Dir.entries(d).select {|entry| File.directory?(File.join(d, entry)) && !(entry =='.' || entry == '..') } + end + # Starts the application level bots # @return [boolean] true if all bots are started correctly, false otherwise @@ -69,28 +70,6 @@ def self.start_run_bots( bots_list, app_path, app_id, run_id ) #--- Private class methods -------------- - - # Starts a single framework component - # @return [boolean] true if the component has been started successfully, false otherwise - def self.start_framework_component( component_dir ) - pid_file_path = "#{component_dir}/.pid" - return true if sanitize_pid_file pid_file_path - # Component is not running and there is no pid file so we try to start it - # and create a new pid file. Note that the pid file is created by - # the startup script! - # Framework components are started without any parameters passed to them because they have - # full access to config, runlist and framework APIs using 'require_relative' - command = "#{component_dir}/startup" - pid = fork - exec(command) if pid.nil? - # Give it a second so they can start properly - sleep 1 - # All went well so we return true - true - end - private_class_method :start_framework_component - - # Starts a run level bot def self.start_run_level_bot( bots_dir, bot, tmux ) # If there is no 'startup' script output a warning (because diff --git a/lib/util/immortal.rb b/lib/util/immortal.rb deleted file mode 100644 index 32c008d..0000000 --- a/lib/util/immortal.rb +++ /dev/null @@ -1,4 +0,0 @@ -# This module provides a simple wrapper around immortal because, you know... -module Immortal - -end \ No newline at end of file diff --git a/lib/util/supervisor.rb b/lib/util/supervisor.rb new file mode 100644 index 0000000..0bb6a4e --- /dev/null +++ b/lib/util/supervisor.rb @@ -0,0 +1,69 @@ +require 'xmlrpc/client' + +module Nutella + MAC_CONFIG_FILE="/usr/local/etc/supervisord.ini" + MAC_CONFIG_DIR="/usr/local/etc/supervisor.d" + + # This class wraps supervisor and makes it easier to interact with + class Supervisor + + def initialize + @rpc = XMLRPC::Client.new2("http://localhost:9001/RPC2") + end + + # Adds a process to supervision + def add(name, command) + write_config_file(name, command) + # TODO validate that the reload config RPC actually does a reread operation... + @rpc.call("supervisor.reloadConfig") + # TODO Validate what was removed based on returned value? + end + + # Adds a group of process to supervision + def add_group(processes) + processes.each { |name, command| write_config_file(name, command) } + @rpc.call("supervisor.reloadConfig") + end + + # Removes a process from supervision + def remove(name) + delete_config_file(name) + @rpc.call("supervisor.reloadConfig") + end + + # Removes a group of processes from supervision + def remove_group(processes) + processes.each { |name| delete_config_file(name) } + @rpc.call("supervisor.reloadConfig") + end + + # Starts a process, retuns false if error + def start(name) + res = @rpc.call("supervisor.startProcess", name) + puts res + end + + # Stops a process, retuns false if error + def stop(name) + @rpc.call("supervisor.stopProcess", name) + end + + + private + + def write_config_file(name, command) + File.open("#{MAC_CONFIG_DIR}/#{name}.ini", 'w') do |f| + f.puts "[program:#{name}]" + f.puts "command=#{command}" + f.puts "stdout_logfile=#{command[0..-8]+'stdout.log'}" + f.puts "autostart=false" + f.puts "redirect_stderr=true" + end + end + + def delete_config_file(name) + File.delete("#{MAC_CONFIG_DIR}/#{name}.ini") + end + + end +end \ No newline at end of file diff --git a/spec/cli/commands/server_spec.rb b/spec/cli/commands/server_spec.rb index 0bf5226..7e87bc2 100644 --- a/spec/cli/commands/server_spec.rb +++ b/spec/cli/commands/server_spec.rb @@ -1,12 +1,10 @@ require 'spec_helper' -require 'cli/cli' +require 'nutella_framework' module Nutella describe Server do skip 'Starts the MQTT broker' do NutellaCLI.execute_command('server') - # TODO resume from here... need to implement - # framework components start using immortal... end end end \ No newline at end of file From 61ba5a94eb3451be98cc559743cf4c5c5b3c8029 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 26 Oct 2019 16:23:23 -0700 Subject: [PATCH 13/43] newgemspec --- nutella_framework.gemspec | 230 +++++++++++++++----------------------- 1 file changed, 93 insertions(+), 137 deletions(-) diff --git a/nutella_framework.gemspec b/nutella_framework.gemspec index 0ba80ea..43d313f 100755 --- a/nutella_framework.gemspec +++ b/nutella_framework.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Alessandro Gnoli".freeze] - s.date = "2019-03-03" + s.date = "2019-10-26" s.description = "utella is a framework to create and run RoomApps".freeze s.email = "tebemis@gmail.com".freeze s.executables = ["nutella".freeze] @@ -21,6 +21,7 @@ Gem::Specification.new do |s| ] s.files = [ ".document", + ".rspec", ".travis.yml", "Gemfile", "LICENSE", @@ -28,178 +29,133 @@ Gem::Specification.new do |s| "Rakefile", "VERSION", "bin/nutella", - "data/index.html", - "data/startup", - "example_framework_components/example_framework_bot/example_framework_bot.rb", - "example_framework_components/example_framework_bot/startup", - "example_framework_components/example_framework_web_interface/index.html", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.npmignore", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/.travis.yml", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/LICENSE", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/README.md", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js.map", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/browser_hello_world.html", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/node_hello_world.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/gulpfile.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/package.json", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core_browser.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_log.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_net.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_persist.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_core_browser.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_log.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_net.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i_browser.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib_browser.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_log.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_net.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_persist.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/src/util/net.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/nutella.test.js", - "example_framework_components/example_framework_web_interface/node_modules/nutella_lib/test/runner.html", - "example_framework_components/example_framework_web_interface/package.json", - "framework_components/binary-files-manager/bin_files_mngr.rb", - "framework_components/binary-files-manager/startup", - "framework_components/main_interface/main_interface_bot.rb", - "framework_components/main_interface/public/index.html", - "framework_components/main_interface/startup", - "framework_components/main_interface/views/index.erb", - "framework_components/main_interface/views/not_found_404.erb", - "framework_components/order.json", - "framework_components/room-debugger/README.md", - "framework_components/room-debugger/css/bootstrap-theme.css", - "framework_components/room-debugger/css/bootstrap-theme.css.map", - "framework_components/room-debugger/css/bootstrap-theme.min.css", - "framework_components/room-debugger/css/bootstrap.css", - "framework_components/room-debugger/css/bootstrap.css.map", - "framework_components/room-debugger/css/bootstrap.min.css", - "framework_components/room-debugger/fonts/glyphicons-halflings-regular.eot", - "framework_components/room-debugger/fonts/glyphicons-halflings-regular.svg", - "framework_components/room-debugger/fonts/glyphicons-halflings-regular.ttf", - "framework_components/room-debugger/fonts/glyphicons-halflings-regular.woff", - "framework_components/room-debugger/fonts/glyphicons-halflings-regular.woff2", - "framework_components/room-debugger/index.html", - "framework_components/room-debugger/js/bootstrap.js", - "framework_components/room-debugger/js/bootstrap.min.js", - "framework_components/room-debugger/js/jquery.min.js", - "framework_components/room-debugger/js/npm.js", - "framework_components/room-debugger/js/nutella_lib.js", - "framework_components/room-debugger/main.css", - "framework_components/room-debugger/main.js", - "framework_components/room-debugger/nutella.json", - "framework_components/room-debugger/package.json", - "framework_components/room-debugger/room_places_simulator.js", - "framework_components/runs_list_bot/runs_list_bot.rb", - "framework_components/runs_list_bot/startup", - "lib/commands/broker.rb", - "lib/commands/checkup.rb", - "lib/commands/compile.rb", - "lib/commands/dependencies.rb", - "lib/commands/help.rb", - "lib/commands/install.rb", - "lib/commands/meta/command.rb", - "lib/commands/meta/run_command.rb", - "lib/commands/meta/template_command.rb", - "lib/commands/new.rb", - "lib/commands/reset.rb", - "lib/commands/runs.rb", - "lib/commands/start.rb", - "lib/commands/stop.rb", - "lib/commands/template.rb", - "lib/commands/util/components_list.rb", - "lib/commands/util/components_starter.rb", + "lib/bots/binary-files-manager/bin_files_mngr.rb", + "lib/bots/commands_server/commands_server.rb", + "lib/bots/commands_server/startup", + "lib/bots/main_interface/main_interface_bot.rb", + "lib/bots/main_interface/public/index.html", + "lib/bots/main_interface/views/index.erb", + "lib/bots/main_interface/views/not_found_404.erb", + "lib/bots/room-debugger/README.md", + "lib/bots/room-debugger/css/bootstrap-theme.css", + "lib/bots/room-debugger/css/bootstrap-theme.css.map", + "lib/bots/room-debugger/css/bootstrap-theme.min.css", + "lib/bots/room-debugger/css/bootstrap.css", + "lib/bots/room-debugger/css/bootstrap.css.map", + "lib/bots/room-debugger/css/bootstrap.min.css", + "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.eot", + "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.svg", + "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.ttf", + "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff", + "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff2", + "lib/bots/room-debugger/index.html", + "lib/bots/room-debugger/js/bootstrap.js", + "lib/bots/room-debugger/js/bootstrap.min.js", + "lib/bots/room-debugger/js/jquery.min.js", + "lib/bots/room-debugger/js/npm.js", + "lib/bots/room-debugger/js/nutella_lib.js", + "lib/bots/room-debugger/main.css", + "lib/bots/room-debugger/main.js", + "lib/bots/room-debugger/nutella.json", + "lib/bots/room-debugger/package.json", + "lib/bots/room-debugger/room_places_simulator.js", + "lib/bots/runs_list_bot/runs_list_bot.rb", + "lib/cli/cli.rb", + "lib/cli/cli_utils.rb", + "lib/cli/commands/broker.rb", + "lib/cli/commands/checkup.rb", + "lib/cli/commands/compile.rb", + "lib/cli/commands/dependencies.rb", + "lib/cli/commands/help.rb", + "lib/cli/commands/install.rb", + "lib/cli/commands/meta/command.rb", + "lib/cli/commands/meta/run_command.rb", + "lib/cli/commands/meta/template_command.rb", + "lib/cli/commands/new.rb", + "lib/cli/commands/reset.rb", + "lib/cli/commands/runs.rb", + "lib/cli/commands/server.rb", + "lib/cli/commands/start.rb", + "lib/cli/commands/stop.rb", + "lib/cli/commands/template.rb", "lib/config/config.rb", "lib/config/current_app_utils.rb", "lib/config/persisted_hash.rb", "lib/config/runlist.rb", - "lib/core/nutella_cli.rb", - "lib/core/nutella_core.rb", - "lib/logging/nutella_logger-remote.rb", - "lib/logging/nutella_logger.rb", - "lib/logging/nutella_logging.rb", "lib/nutella_framework.rb", - "lib/tmux/tmux.rb", + "lib/templates/index.html", + "lib/templates/startup", + "lib/util/components_list.rb", + "lib/util/framework_components_starter.rb", + "lib/util/mongo.rb", + "lib/util/mqtt_broker.rb", + "lib/util/pid.rb", + "lib/util/supervisor.rb", "nutella_framework.gemspec", - "nutella_lib/framework_core.rb", - "nutella_lib/framework_log.rb", - "nutella_lib/framework_net.rb", - "nutella_lib/framework_persist.rb", - "test/commands/test_cmd_cli_params_parsing.rb", - "test/commands/test_command_template.rb", - "test/config/test_current_app_utils.rb", - "test/config/test_persisted_hash.rb", - "test/config/test_runlist.rb", - "test/framework_apis/test_framework_api.rb", - "test/helper.rb", - "test/logging/test_logging.rb" + "spec/cli/commands/checkup_spec.rb", + "spec/cli/commands/help_spec.rb", + "spec/cli/commands/server_spec.rb", + "spec/config/config_spec.rb", + "spec/config/persisted_hash_spec.rb", + "spec/spec_helper.rb" ] s.homepage = "https://github.com/nutella-framework/nutella_framework".freeze s.licenses = ["MIT".freeze] - s.rubygems_version = "2.7.6".freeze + s.rubygems_version = "3.0.6".freeze s.summary = "A rails-inspired framework for RoomApps".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_runtime_dependency(%q.freeze, ["~> 1.4"]) - s.add_runtime_dependency(%q.freeze, ["~> 2.2"]) + s.add_runtime_dependency(%q.freeze, ["~> 1.5"]) + s.add_runtime_dependency(%q.freeze, ["~> 3.0"]) s.add_runtime_dependency(%q.freeze, ["~> 1.2"]) + s.add_runtime_dependency(%q.freeze, ["~> 0.4", ">= 0.4.24"]) + s.add_runtime_dependency(%q.freeze, ["~> 1.6"]) + s.add_runtime_dependency(%q.freeze, ["~> 4.0"]) + s.add_runtime_dependency(%q.freeze, ["~> 1.4"]) s.add_runtime_dependency(%q.freeze, ["~> 1.4"]) s.add_runtime_dependency(%q.freeze, ["~> 0.3.2"]) s.add_runtime_dependency(%q.freeze, ["~> 1.6"]) - s.add_runtime_dependency(%q.freeze, ["~> 1.6"]) - s.add_runtime_dependency(%q.freeze, ["~> 4.0"]) - s.add_runtime_dependency(%q.freeze, [">= 0.4.24", "~> 0.4"]) - s.add_runtime_dependency(%q.freeze, ["~> 4.2"]) - s.add_runtime_dependency(%q.freeze, ["~> 3.0"]) - s.add_development_dependency(%q.freeze, ["~> 3.0"]) + s.add_runtime_dependency(%q.freeze, ["~> 0.3.0"]) s.add_development_dependency(%q.freeze, ["~> 0.9.11"]) s.add_development_dependency(%q.freeze, ["~> 4.0"]) - s.add_development_dependency(%q.freeze, ["~> 1.0"]) - s.add_development_dependency(%q.freeze, ["~> 2.0"]) - s.add_development_dependency(%q.freeze, ["~> 0.9"]) + s.add_development_dependency(%q.freeze, ["~> 2.0"]) + s.add_development_dependency(%q.freeze, ["~> 2.3"]) else - s.add_dependency(%q.freeze, ["~> 1.4"]) - s.add_dependency(%q.freeze, ["~> 2.2"]) + s.add_dependency(%q.freeze, ["~> 1.5"]) + s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 1.2"]) + s.add_dependency(%q.freeze, ["~> 0.4", ">= 0.4.24"]) + s.add_dependency(%q.freeze, ["~> 1.6"]) + s.add_dependency(%q.freeze, ["~> 4.0"]) + s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 0.3.2"]) s.add_dependency(%q.freeze, ["~> 1.6"]) - s.add_dependency(%q.freeze, ["~> 1.6"]) - s.add_dependency(%q.freeze, ["~> 4.0"]) - s.add_dependency(%q.freeze, [">= 0.4.24", "~> 0.4"]) - s.add_dependency(%q.freeze, ["~> 4.2"]) - s.add_dependency(%q.freeze, ["~> 3.0"]) - s.add_dependency(%q.freeze, ["~> 3.0"]) + s.add_dependency(%q.freeze, ["~> 0.3.0"]) s.add_dependency(%q.freeze, ["~> 0.9.11"]) s.add_dependency(%q.freeze, ["~> 4.0"]) - s.add_dependency(%q.freeze, ["~> 1.0"]) - s.add_dependency(%q.freeze, ["~> 2.0"]) - s.add_dependency(%q.freeze, ["~> 0.9"]) + s.add_dependency(%q.freeze, ["~> 2.0"]) + s.add_dependency(%q.freeze, ["~> 2.3"]) end else - s.add_dependency(%q.freeze, ["~> 1.4"]) - s.add_dependency(%q.freeze, ["~> 2.2"]) + s.add_dependency(%q.freeze, ["~> 1.5"]) + s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 1.2"]) + s.add_dependency(%q.freeze, ["~> 0.4", ">= 0.4.24"]) + s.add_dependency(%q.freeze, ["~> 1.6"]) + s.add_dependency(%q.freeze, ["~> 4.0"]) + s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 0.3.2"]) s.add_dependency(%q.freeze, ["~> 1.6"]) - s.add_dependency(%q.freeze, ["~> 1.6"]) - s.add_dependency(%q.freeze, ["~> 4.0"]) - s.add_dependency(%q.freeze, [">= 0.4.24", "~> 0.4"]) - s.add_dependency(%q.freeze, ["~> 4.2"]) - s.add_dependency(%q.freeze, ["~> 3.0"]) - s.add_dependency(%q.freeze, ["~> 3.0"]) + s.add_dependency(%q.freeze, ["~> 0.3.0"]) s.add_dependency(%q.freeze, ["~> 0.9.11"]) s.add_dependency(%q.freeze, ["~> 4.0"]) - s.add_dependency(%q.freeze, ["~> 1.0"]) - s.add_dependency(%q.freeze, ["~> 2.0"]) - s.add_dependency(%q.freeze, ["~> 0.9"]) + s.add_dependency(%q.freeze, ["~> 2.0"]) + s.add_dependency(%q.freeze, ["~> 2.3"]) end end From 61b25eb31334cd254f0b9715a8c98af5e4a713b5 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 26 Oct 2019 16:38:48 -0700 Subject: [PATCH 14/43] fixup --- lib/cli/commands/checkup.rb | 1 + nutella_framework-0.8.0.gem | Bin 0 -> 363520 bytes 2 files changed, 1 insertion(+) create mode 100644 nutella_framework-0.8.0.gem diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index a8b7e23..aae0b95 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -80,6 +80,7 @@ def all_dependencies_installed? supervisor_semver = lambda do out = `supervisorctl version` out.gsub("\n",'') + puts Semantic::Version.new out Semantic::Version.new out end # Mongo version lambda diff --git a/nutella_framework-0.8.0.gem b/nutella_framework-0.8.0.gem new file mode 100644 index 0000000000000000000000000000000000000000..a5c686815c8b151514da32068d1c60def79d99d6 GIT binary patch literal 363520 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 m*%K`lcelLxn<&T4 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�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

<1fJ^wsuvh$18mw5{RiMC-L~BpWFav(!qR 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 j#P^u~UY1cl$ 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 literal 0 HcmV?d00001 From 5cbc59513ed6c7596bc3f566ecb6996a38972faa Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 26 Oct 2019 16:59:04 -0700 Subject: [PATCH 15/43] Moved all commands that are going to be implemented on the commands server --- lib/{cli => bots/commands_server}/commands/broker.rb | 2 -- lib/{cli => bots/commands_server}/commands/compile.rb | 0 lib/{cli => bots/commands_server}/commands/dependencies.rb | 0 lib/{cli => bots/commands_server}/commands/install.rb | 0 lib/{cli => bots/commands_server}/commands/new.rb | 0 lib/{cli => bots/commands_server}/commands/reset.rb | 0 lib/{cli => bots/commands_server}/commands/runs.rb | 0 lib/{cli => bots/commands_server}/commands/start.rb | 0 lib/{cli => bots/commands_server}/commands/stop.rb | 0 lib/{cli => bots/commands_server}/commands/template.rb | 0 10 files changed, 2 deletions(-) rename lib/{cli => bots/commands_server}/commands/broker.rb (99%) rename lib/{cli => bots/commands_server}/commands/compile.rb (100%) rename lib/{cli => bots/commands_server}/commands/dependencies.rb (100%) rename lib/{cli => bots/commands_server}/commands/install.rb (100%) rename lib/{cli => bots/commands_server}/commands/new.rb (100%) rename lib/{cli => bots/commands_server}/commands/reset.rb (100%) rename lib/{cli => bots/commands_server}/commands/runs.rb (100%) rename lib/{cli => bots/commands_server}/commands/start.rb (100%) rename lib/{cli => bots/commands_server}/commands/stop.rb (100%) rename lib/{cli => bots/commands_server}/commands/template.rb (100%) diff --git a/lib/cli/commands/broker.rb b/lib/bots/commands_server/commands/broker.rb similarity index 99% rename from lib/cli/commands/broker.rb rename to lib/bots/commands_server/commands/broker.rb index d0af770..16ffec9 100755 --- a/lib/cli/commands/broker.rb +++ b/lib/bots/commands_server/commands/broker.rb @@ -53,5 +53,3 @@ def change_broker( new_broker ) end end - - diff --git a/lib/cli/commands/compile.rb b/lib/bots/commands_server/commands/compile.rb similarity index 100% rename from lib/cli/commands/compile.rb rename to lib/bots/commands_server/commands/compile.rb diff --git a/lib/cli/commands/dependencies.rb b/lib/bots/commands_server/commands/dependencies.rb similarity index 100% rename from lib/cli/commands/dependencies.rb rename to lib/bots/commands_server/commands/dependencies.rb diff --git a/lib/cli/commands/install.rb b/lib/bots/commands_server/commands/install.rb similarity index 100% rename from lib/cli/commands/install.rb rename to lib/bots/commands_server/commands/install.rb diff --git a/lib/cli/commands/new.rb b/lib/bots/commands_server/commands/new.rb similarity index 100% rename from lib/cli/commands/new.rb rename to lib/bots/commands_server/commands/new.rb diff --git a/lib/cli/commands/reset.rb b/lib/bots/commands_server/commands/reset.rb similarity index 100% rename from lib/cli/commands/reset.rb rename to lib/bots/commands_server/commands/reset.rb diff --git a/lib/cli/commands/runs.rb b/lib/bots/commands_server/commands/runs.rb similarity index 100% rename from lib/cli/commands/runs.rb rename to lib/bots/commands_server/commands/runs.rb diff --git a/lib/cli/commands/start.rb b/lib/bots/commands_server/commands/start.rb similarity index 100% rename from lib/cli/commands/start.rb rename to lib/bots/commands_server/commands/start.rb diff --git a/lib/cli/commands/stop.rb b/lib/bots/commands_server/commands/stop.rb similarity index 100% rename from lib/cli/commands/stop.rb rename to lib/bots/commands_server/commands/stop.rb diff --git a/lib/cli/commands/template.rb b/lib/bots/commands_server/commands/template.rb similarity index 100% rename from lib/cli/commands/template.rb rename to lib/bots/commands_server/commands/template.rb From 23ec4c18cefafa7e16c9aa3ab0405c0f6f80b4ec Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 26 Oct 2019 17:17:05 -0700 Subject: [PATCH 16/43] Move commands around --- .../commands_server/commands/meta/command.rb | 17 +++++++++++++++++ .../commands/meta/run_command.rb | 0 .../commands/meta/template_command.rb | 0 3 files changed, 17 insertions(+) create mode 100755 lib/bots/commands_server/commands/meta/command.rb rename lib/{cli => bots/commands_server}/commands/meta/run_command.rb (100%) rename lib/{cli => bots/commands_server}/commands/meta/template_command.rb (100%) diff --git a/lib/bots/commands_server/commands/meta/command.rb b/lib/bots/commands_server/commands/meta/command.rb new file mode 100755 index 0000000..4d3f047 --- /dev/null +++ b/lib/bots/commands_server/commands/meta/command.rb @@ -0,0 +1,17 @@ +module Nutella + + # Nutella command + class Command + + class << self; + attr_accessor :description + end + + # Commands overload this method to execute + def run( args=nil ) + console.error 'Running the generic command!!! WAT? https://www.destroyallsoftware.com/talks/wat' + end + + end + +end diff --git a/lib/cli/commands/meta/run_command.rb b/lib/bots/commands_server/commands/meta/run_command.rb similarity index 100% rename from lib/cli/commands/meta/run_command.rb rename to lib/bots/commands_server/commands/meta/run_command.rb diff --git a/lib/cli/commands/meta/template_command.rb b/lib/bots/commands_server/commands/meta/template_command.rb similarity index 100% rename from lib/cli/commands/meta/template_command.rb rename to lib/bots/commands_server/commands/meta/template_command.rb From edd48de743276ed1e8c6c38aa66147c5b44125bd Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 26 Oct 2019 19:33:39 -0700 Subject: [PATCH 17/43] Update supervisor --- .gitignore | 2 +- lib/bots/commands_server/persist.log | 335 ++++++++++++++++++++++ lib/cli/cli.rb | 2 +- lib/cli/commands/checkup.rb | 3 +- lib/cli/commands/server.rb | 4 +- lib/cli/{cli_utils.rb => logger.rb} | 4 +- lib/nutella_framework.rb | 2 +- lib/{config => util}/config.rb | 0 lib/{config => util}/current_app_utils.rb | 0 lib/util/framework_components_starter.rb | 121 ++++---- lib/util/mqtt_broker.rb | 2 +- lib/{config => util}/persisted_hash.rb | 0 lib/{config => util}/runlist.rb | 0 lib/util/supervisor.rb | 21 +- spec/cli/commands/checkup_spec.rb | 1 + spec/cli/commands/server_spec.rb | 1 + spec/config/config_spec.rb | 2 +- spec/config/persisted_hash_spec.rb | 2 +- 18 files changed, 425 insertions(+), 77 deletions(-) create mode 100644 lib/bots/commands_server/persist.log rename lib/cli/{cli_utils.rb => logger.rb} (91%) rename lib/{config => util}/config.rb (100%) rename lib/{config => util}/current_app_utils.rb (100%) rename lib/{config => util}/persisted_hash.rb (100%) rename lib/{config => util}/runlist.rb (100%) diff --git a/.gitignore b/.gitignore index 1e5460c..b585aa8 100755 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ broker/ .tmp/ .pid .mongo_pid -stdout.log +*.log # Test app test_app/ diff --git a/lib/bots/commands_server/persist.log b/lib/bots/commands_server/persist.log new file mode 100644 index 0000000..cc1e7ac --- /dev/null +++ b/lib/bots/commands_server/persist.log @@ -0,0 +1,335 @@ +Hi, I'm a basic ruby bot and all I do is idle and print stuff +certainly first param is set +0 A log line! +1 A log line! +2 A log line! +3 A log line! +4 A log line! +5 A log line! +6 A log line! +7 A log line! +8 A log line! +9 A log line! +10 A log line! +11 A log line! +12 A log line! +13 A log line! +14 A log line! +15 A log line! +16 A log line! +17 A log line! +18 A log line! +19 A log line! +20 A log line! +21 A log line! +22 A log line! +23 A log line! +24 A log line! +25 A log line! +26 A log line! +27 A log line! +28 A log line! +29 A log line! +30 A log line! +31 A log line! +32 A log line! +33 A log line! +34 A log line! +35 A log line! +36 A log line! +37 A log line! +38 A log line! +39 A log line! +40 A log line! +41 A log line! +42 A log line! +43 A log line! +44 A log line! +45 A log line! +46 A log line! +47 A log line! +48 A log line! +49 A log line! +50 A log line! +51 A log line! +52 A log line! +53 A log line! +54 A log line! +55 A log line! +56 A log line! +57 A log line! +58 A log line! +59 A log line! +60 A log line! +61 A log line! +62 A log line! +63 A log line! +64 A log line! +65 A log line! +66 A log line! +67 A log line! +68 A log line! +69 A log line! +70 A log line! +71 A log line! +72 A log line! +73 A log line! +74 A log line! +75 A log line! +76 A log line! +77 A log line! +78 A log line! +79 A log line! +80 A log line! +81 A log line! +82 A log line! +83 A log line! +84 A log line! +85 A log line! +86 A log line! +87 A log line! +88 A log line! +89 A log line! +90 A log line! +91 A log line! +92 A log line! +93 A log line! +94 A log line! +95 A log line! +96 A log line! +97 A log line! +98 A log line! +99 A log line! +100 A log line! +101 A log line! +102 A log line! +103 A log line! +104 A log line! +105 A log line! +106 A log line! +107 A log line! +108 A log line! +109 A log line! +110 A log line! +111 A log line! +112 A log line! +113 A log line! +114 A log line! +115 A log line! +116 A log line! +117 A log line! +118 A log line! +119 A log line! +120 A log line! +121 A log line! +122 A log line! +123 A log line! +124 A log line! +125 A log line! +126 A log line! +127 A log line! +128 A log line! +129 A log line! +130 A log line! +131 A log line! +132 A log line! +133 A log line! +134 A log line! +135 A log line! +136 A log line! +137 A log line! +138 A log line! +139 A log line! +140 A log line! +141 A log line! +142 A log line! +143 A log line! +144 A log line! +145 A log line! +146 A log line! +147 A log line! +148 A log line! +149 A log line! +150 A log line! +151 A log line! +152 A log line! +153 A log line! +154 A log line! +155 A log line! +156 A log line! +157 A log line! +158 A log line! +159 A log line! +160 A log line! +161 A log line! +162 A log line! +163 A log line! +164 A log line! +165 A log line! +166 A log line! +167 A log line! +168 A log line! +169 A log line! +170 A log line! +171 A log line! +172 A log line! +173 A log line! +174 A log line! +175 A log line! +176 A log line! +177 A log line! +178 A log line! +179 A log line! +180 A log line! +181 A log line! +182 A log line! +183 A log line! +184 A log line! +185 A log line! +186 A log line! +187 A log line! +188 A log line! +189 A log line! +190 A log line! +191 A log line! +192 A log line! +193 A log line! +194 A log line! +195 A log line! +196 A log line! +197 A log line! +198 A log line! +199 A log line! +200 A log line! +201 A log line! +202 A log line! +203 A log line! +204 A log line! +205 A log line! +206 A log line! +207 A log line! +208 A log line! +209 A log line! +210 A log line! +211 A log line! +212 A log line! +213 A log line! +214 A log line! +215 A log line! +216 A log line! +217 A log line! +218 A log line! +219 A log line! +220 A log line! +221 A log line! +222 A log line! +223 A log line! +224 A log line! +225 A log line! +226 A log line! +227 A log line! +228 A log line! +229 A log line! +230 A log line! +231 A log line! +232 A log line! +233 A log line! +234 A log line! +235 A log line! +236 A log line! +237 A log line! +238 A log line! +239 A log line! +240 A log line! +241 A log line! +242 A log line! +243 A log line! +244 A log line! +245 A log line! +246 A log line! +247 A log line! +248 A log line! +249 A log line! +250 A log line! +251 A log line! +252 A log line! +253 A log line! +254 A log line! +255 A log line! +256 A log line! +257 A log line! +258 A log line! +259 A log line! +260 A log line! +261 A log line! +262 A log line! +263 A log line! +264 A log line! +265 A log line! +266 A log line! +267 A log line! +268 A log line! +269 A log line! +270 A log line! +271 A log line! +272 A log line! +273 A log line! +274 A log line! +275 A log line! +276 A log line! +277 A log line! +278 A log line! +279 A log line! +280 A log line! +281 A log line! +282 A log line! +283 A log line! +284 A log line! +285 A log line! +286 A log line! +287 A log line! +288 A log line! +289 A log line! +290 A log line! +291 A log line! +292 A log line! +293 A log line! +294 A log line! +295 A log line! +296 A log line! +297 A log line! +298 A log line! +299 A log line! +300 A log line! +301 A log line! +302 A log line! +303 A log line! +304 A log line! +305 A log line! +306 A log line! +307 A log line! +308 A log line! +309 A log line! +310 A log line! +311 A log line! +312 A log line! +313 A log line! +314 A log line! +315 A log line! +316 A log line! +317 A log line! +318 A log line! +319 A log line! +320 A log line! +321 A log line! +322 A log line! +323 A log line! +324 A log line! +325 A log line! +326 A log line! +327 A log line! +328 A log line! +329 A log line! +330 A log line! +331 A log line! +332 A log line! diff --git a/lib/cli/cli.rb b/lib/cli/cli.rb index 9b7e56f..39668c2 100755 --- a/lib/cli/cli.rb +++ b/lib/cli/cli.rb @@ -1,4 +1,4 @@ -require_relative 'cli_utils' +require_relative 'logger' # Require all the commands Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| require_relative "commands/#{File.basename(file, File.extname(file))}" diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index aae0b95..6499d0b 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -1,5 +1,5 @@ require_relative 'meta/command' -require 'config/config' +require 'util/config' require 'semantic' module Nutella @@ -80,7 +80,6 @@ def all_dependencies_installed? supervisor_semver = lambda do out = `supervisorctl version` out.gsub("\n",'') - puts Semantic::Version.new out Semantic::Version.new out end # Mongo version lambda diff --git a/lib/cli/commands/server.rb b/lib/cli/commands/server.rb index 32db2fa..40c1a6d 100644 --- a/lib/cli/commands/server.rb +++ b/lib/cli/commands/server.rb @@ -4,7 +4,7 @@ require 'util/framework_components_starter' module Nutella - class Server < RunCommand + class Server < Command @description = 'Starts the MQTT broker and the framework level bots' def run(args=nil) @@ -18,7 +18,7 @@ def run(args=nil) else console.error('Failed to start Mongo') end - if FrameworkComponentsStarter.start + if FrameworkComponents.start console.success('Framework level components started') else console.error('Failed to start Framework level components') diff --git a/lib/cli/cli_utils.rb b/lib/cli/logger.rb similarity index 91% rename from lib/cli/cli_utils.rb rename to lib/cli/logger.rb index 4a35b1f..a4c1119 100755 --- a/lib/cli/cli_utils.rb +++ b/lib/cli/logger.rb @@ -3,7 +3,7 @@ module Nutella - class CLIUtils + class CLILogger include Singleton def debug(message) @@ -35,7 +35,7 @@ def error(message) module Kernel def console - Nutella::CLIUtils.instance + Nutella::CLILogger.instance end end diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index 302f313..db105cc 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -1,4 +1,4 @@ -require 'config/config' +require 'util/config' require 'cli/cli' module Nutella diff --git a/lib/config/config.rb b/lib/util/config.rb similarity index 100% rename from lib/config/config.rb rename to lib/util/config.rb diff --git a/lib/config/current_app_utils.rb b/lib/util/current_app_utils.rb similarity index 100% rename from lib/config/current_app_utils.rb rename to lib/util/current_app_utils.rb diff --git a/lib/util/framework_components_starter.rb b/lib/util/framework_components_starter.rb index f6bce17..27d175a 100755 --- a/lib/util/framework_components_starter.rb +++ b/lib/util/framework_components_starter.rb @@ -1,88 +1,91 @@ require 'util/supervisor' module Nutella - # Utility functions to start components - class FrameworkComponentsStarter + # Utility functions to deal with framework components + class FrameworkComponents def self.start - FrameworkComponentsStarter.new.start + FrameworkComponents.new.start end # Starts all framework components. # @return [boolean] true if all components are started correctly, false otherwise def start - supervisor = Supervisor.new nutella_components_dir = "#{Nutella::NUTELLA_HOME}lib/bots" + # Todo, refactor so we don't reload 20 times framework_components.each do |c| - if File.exist? "#{nutella_components_dir}/#{c}/startup" - supervisor.add("nutella_f_#{c}", "#{nutella_components_dir}/#{c}/startup") - end + Supervisor.instance.add("nutella_f_#{c}", "#{nutella_components_dir}/#{c}/startup") end framework_components.each do |c| - supervisor.start("nutella_f_#{c}") + puts Supervisor.instance.start("nutella_f_#{c}") end true end - # Finds the framework level components that need to be started + private + + # Finds the framework level components def framework_components d = "#{Nutella::NUTELLA_HOME}lib/bots" - Dir.entries(d).select {|entry| File.directory?(File.join(d, entry)) && !(entry =='.' || entry == '..') } + Dir.entries(d) + .select {|entry| File.directory?(File.join(d, entry)) && !(entry =='.' || entry == '..') } + .select { |c| File.exist? "#{d}/#{c}/startup" } end + + end + +end + # # Starts the application level bots + # # @return [boolean] true if all bots are started correctly, false otherwise + # def self.start_app_bots( app_id, app_path ) + # app_bots_list = Nutella.current_app.config['app_bots'] + # bots_dir = "#{app_path}/bots/" + # # If app bots have been started already, then do nothing + # unless Nutella::Tmux.session_exist? Nutella::Tmux.app_bot_session_name app_id + # # Start all app bots in the list into a new tmux session + # tmux = Nutella::Tmux.new app_id, nil + # ComponentsList.for_each_component_in_dir bots_dir do |bot| + # unless app_bots_list.nil? || !app_bots_list.include?( bot ) + # # If there is no 'startup' script output a warning (because + # # startup is mandatory) and skip the bot + # unless File.exist?("#{bots_dir}#{bot}/startup") + # console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." + # next + # end + # # Create a new window in the session for this run + # tmux.new_app_bot_window bot + # end + # end + # end + # true + # end - # Starts the application level bots - # @return [boolean] true if all bots are started correctly, false otherwise - def self.start_app_bots( app_id, app_path ) - app_bots_list = Nutella.current_app.config['app_bots'] - bots_dir = "#{app_path}/bots/" - # If app bots have been started already, then do nothing - unless Nutella::Tmux.session_exist? Nutella::Tmux.app_bot_session_name app_id - # Start all app bots in the list into a new tmux session - tmux = Nutella::Tmux.new app_id, nil - ComponentsList.for_each_component_in_dir bots_dir do |bot| - unless app_bots_list.nil? || !app_bots_list.include?( bot ) - # If there is no 'startup' script output a warning (because - # startup is mandatory) and skip the bot - unless File.exist?("#{bots_dir}#{bot}/startup") - console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." - next - end - # Create a new window in the session for this run - tmux.new_app_bot_window bot - end - end - end - true - end + # def self.start_run_bots( bots_list, app_path, app_id, run_id ) + # # Create a new tmux instance for this run + # tmux = Nutella::Tmux.new app_id, run_id + # # Fetch bots dir + # bots_dir = "#{app_path}/bots/" + # # Start the appropriate bots + # bots_list.each { |bot| start_run_level_bot(bots_dir, bot, tmux) } + # true + # end - def self.start_run_bots( bots_list, app_path, app_id, run_id ) - # Create a new tmux instance for this run - tmux = Nutella::Tmux.new app_id, run_id - # Fetch bots dir - bots_dir = "#{app_path}/bots/" - # Start the appropriate bots - bots_list.each { |bot| start_run_level_bot(bots_dir, bot, tmux) } - true - end + # #--- Private class methods -------------- - #--- Private class methods -------------- + # # Starts a run level bot + # def self.start_run_level_bot( bots_dir, bot, tmux ) + # # If there is no 'startup' script output a warning (because + # # startup is mandatory) and skip the bot + # unless File.exist?("#{bots_dir}#{bot}/startup") + # console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." + # return + # end + # # Create a new window in the session for this run + # tmux.new_bot_window bot + # end + # private_class_method :start_run_level_bot - # Starts a run level bot - def self.start_run_level_bot( bots_dir, bot, tmux ) - # If there is no 'startup' script output a warning (because - # startup is mandatory) and skip the bot - unless File.exist?("#{bots_dir}#{bot}/startup") - console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." - return - end - # Create a new window in the session for this run - tmux.new_bot_window bot - end - private_class_method :start_run_level_bot - - end -end diff --git a/lib/util/mqtt_broker.rb b/lib/util/mqtt_broker.rb index 14a4410..39d2a76 100644 --- a/lib/util/mqtt_broker.rb +++ b/lib/util/mqtt_broker.rb @@ -1,5 +1,5 @@ require 'socket' -require 'config/config' +require 'util/config' module Nutella class MQTTBroker diff --git a/lib/config/persisted_hash.rb b/lib/util/persisted_hash.rb similarity index 100% rename from lib/config/persisted_hash.rb rename to lib/util/persisted_hash.rb diff --git a/lib/config/runlist.rb b/lib/util/runlist.rb similarity index 100% rename from lib/config/runlist.rb rename to lib/util/runlist.rb diff --git a/lib/util/supervisor.rb b/lib/util/supervisor.rb index 0bb6a4e..46649d0 100644 --- a/lib/util/supervisor.rb +++ b/lib/util/supervisor.rb @@ -1,46 +1,54 @@ require 'xmlrpc/client' +require 'singleton' module Nutella MAC_CONFIG_FILE="/usr/local/etc/supervisord.ini" MAC_CONFIG_DIR="/usr/local/etc/supervisor.d" # This class wraps supervisor and makes it easier to interact with + # Important note. The `reloadConfig` RPC is actually a `reread` which + # does NOT stop the supervisor class Supervisor + include Singleton def initialize + # TODO need to wrap all @rpc calls in exceptions!!!! @rpc = XMLRPC::Client.new2("http://localhost:9001/RPC2") end # Adds a process to supervision def add(name, command) write_config_file(name, command) - # TODO validate that the reload config RPC actually does a reread operation... @rpc.call("supervisor.reloadConfig") - # TODO Validate what was removed based on returned value? + @rpc.call("supervisor.addProcessGroup", name) end # Adds a group of process to supervision def add_group(processes) processes.each { |name, command| write_config_file(name, command) } @rpc.call("supervisor.reloadConfig") + processes.each { |name, _| @rpc.call("supervisor.addProcessGroup", name) } end # Removes a process from supervision def remove(name) + @rpc.call("supervisor.removeProcessGroup", name) delete_config_file(name) @rpc.call("supervisor.reloadConfig") end # Removes a group of processes from supervision def remove_group(processes) - processes.each { |name| delete_config_file(name) } + @rpc.call("supervisor.removeProcessGroup", name) + processes.each do |name| + delete_config_file(name) + end @rpc.call("supervisor.reloadConfig") end # Starts a process, retuns false if error def start(name) - res = @rpc.call("supervisor.startProcess", name) - puts res + @rpc.call("supervisor.startProcess", name) end # Stops a process, retuns false if error @@ -52,7 +60,8 @@ def stop(name) private def write_config_file(name, command) - File.open("#{MAC_CONFIG_DIR}/#{name}.ini", 'w') do |f| + file = "#{MAC_CONFIG_DIR}/#{name}.ini" + File.open(file, 'w') do |f| f.puts "[program:#{name}]" f.puts "command=#{command}" f.puts "stdout_logfile=#{command[0..-8]+'stdout.log'}" diff --git a/spec/cli/commands/checkup_spec.rb b/spec/cli/commands/checkup_spec.rb index 7b6f9c2..155a2a6 100644 --- a/spec/cli/commands/checkup_spec.rb +++ b/spec/cli/commands/checkup_spec.rb @@ -3,6 +3,7 @@ module Nutella describe Checkup do + # Skipping because this command relies heavily on shelling out skip 'executes correctly' do NutellaCLI.execute_command('checkup') end diff --git a/spec/cli/commands/server_spec.rb b/spec/cli/commands/server_spec.rb index 7e87bc2..a42abd2 100644 --- a/spec/cli/commands/server_spec.rb +++ b/spec/cli/commands/server_spec.rb @@ -3,6 +3,7 @@ module Nutella describe Server do + # Skipped because it interacts heavily with supervisor skip 'Starts the MQTT broker' do NutellaCLI.execute_command('server') end diff --git a/spec/config/config_spec.rb b/spec/config/config_spec.rb index c4ab1b6..6fddde0 100644 --- a/spec/config/config_spec.rb +++ b/spec/config/config_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -require 'config/config' +require 'util/config' require 'securerandom' module Nutella diff --git a/spec/config/persisted_hash_spec.rb b/spec/config/persisted_hash_spec.rb index 942699e..711838e 100644 --- a/spec/config/persisted_hash_spec.rb +++ b/spec/config/persisted_hash_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -require 'config/persisted_hash' +require 'util/persisted_hash' require 'securerandom' module Nutella From 11681a7f59ea1aea78ead8f150a0d3a72cdb1c86 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 26 Oct 2019 20:52:56 -0700 Subject: [PATCH 18/43] update gemspec and gitignore --- .gitignore | 1 + lib/bots/commands_server/commands_server.rb | 8 + lib/bots/commands_server/persist.log | 335 -------------------- nutella_framework-0.8.0.gem | Bin 363520 -> 0 bytes nutella_framework.gemspec | 38 +-- 5 files changed, 29 insertions(+), 353 deletions(-) delete mode 100644 lib/bots/commands_server/persist.log delete mode 100644 nutella_framework-0.8.0.gem diff --git a/.gitignore b/.gitignore index b585aa8..228f276 100755 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ doc/ .bundle Gemfile.lock spec/examples.txt +.gem # Mac crap .DS_Store diff --git a/lib/bots/commands_server/commands_server.rb b/lib/bots/commands_server/commands_server.rb index 681d1a0..e1123d7 100644 --- a/lib/bots/commands_server/commands_server.rb +++ b/lib/bots/commands_server/commands_server.rb @@ -1,3 +1,11 @@ +# Command server +# Connects to MQTT broker and listens for commands over (RPC) +# Executes the commands and returns the output +# require 'nutella_lib' +Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| + require_relative "commands/#{File.basename(file, File.extname(file))}" +end + $stdout.sync = true ## nutella woudl do this # $stdout.sync = true puts "Hi, I'm a basic ruby bot and all I do is idle and print stuff" diff --git a/lib/bots/commands_server/persist.log b/lib/bots/commands_server/persist.log deleted file mode 100644 index cc1e7ac..0000000 --- a/lib/bots/commands_server/persist.log +++ /dev/null @@ -1,335 +0,0 @@ -Hi, I'm a basic ruby bot and all I do is idle and print stuff -certainly first param is set -0 A log line! -1 A log line! -2 A log line! -3 A log line! -4 A log line! -5 A log line! -6 A log line! -7 A log line! -8 A log line! -9 A log line! -10 A log line! -11 A log line! -12 A log line! -13 A log line! -14 A log line! -15 A log line! -16 A log line! -17 A log line! -18 A log line! -19 A log line! -20 A log line! -21 A log line! -22 A log line! -23 A log line! -24 A log line! -25 A log line! -26 A log line! -27 A log line! -28 A log line! -29 A log line! -30 A log line! -31 A log line! -32 A log line! -33 A log line! -34 A log line! -35 A log line! -36 A log line! -37 A log line! -38 A log line! -39 A log line! -40 A log line! -41 A log line! -42 A log line! -43 A log line! -44 A log line! -45 A log line! -46 A log line! -47 A log line! -48 A log line! -49 A log line! -50 A log line! -51 A log line! -52 A log line! -53 A log line! -54 A log line! -55 A log line! -56 A log line! -57 A log line! -58 A log line! -59 A log line! -60 A log line! -61 A log line! -62 A log line! -63 A log line! -64 A log line! -65 A log line! -66 A log line! -67 A log line! -68 A log line! -69 A log line! -70 A log line! -71 A log line! -72 A log line! -73 A log line! -74 A log line! -75 A log line! -76 A log line! -77 A log line! -78 A log line! -79 A log line! -80 A log line! -81 A log line! -82 A log line! -83 A log line! -84 A log line! -85 A log line! -86 A log line! -87 A log line! -88 A log line! -89 A log line! -90 A log line! -91 A log line! -92 A log line! -93 A log line! -94 A log line! -95 A log line! -96 A log line! -97 A log line! -98 A log line! -99 A log line! -100 A log line! -101 A log line! -102 A log line! -103 A log line! -104 A log line! -105 A log line! -106 A log line! -107 A log line! -108 A log line! -109 A log line! -110 A log line! -111 A log line! -112 A log line! -113 A log line! -114 A log line! -115 A log line! -116 A log line! -117 A log line! -118 A log line! -119 A log line! -120 A log line! -121 A log line! -122 A log line! -123 A log line! -124 A log line! -125 A log line! -126 A log line! -127 A log line! -128 A log line! -129 A log line! -130 A log line! -131 A log line! -132 A log line! -133 A log line! -134 A log line! -135 A log line! -136 A log line! -137 A log line! -138 A log line! -139 A log line! -140 A log line! -141 A log line! -142 A log line! -143 A log line! -144 A log line! -145 A log line! -146 A log line! -147 A log line! -148 A log line! -149 A log line! -150 A log line! -151 A log line! -152 A log line! -153 A log line! -154 A log line! -155 A log line! -156 A log line! -157 A log line! -158 A log line! -159 A log line! -160 A log line! -161 A log line! -162 A log line! -163 A log line! -164 A log line! -165 A log line! -166 A log line! -167 A log line! -168 A log line! -169 A log line! -170 A log line! -171 A log line! -172 A log line! -173 A log line! -174 A log line! -175 A log line! -176 A log line! -177 A log line! -178 A log line! -179 A log line! -180 A log line! -181 A log line! -182 A log line! -183 A log line! -184 A log line! -185 A log line! -186 A log line! -187 A log line! -188 A log line! -189 A log line! -190 A log line! -191 A log line! -192 A log line! -193 A log line! -194 A log line! -195 A log line! -196 A log line! -197 A log line! -198 A log line! -199 A log line! -200 A log line! -201 A log line! -202 A log line! -203 A log line! -204 A log line! -205 A log line! -206 A log line! -207 A log line! -208 A log line! -209 A log line! -210 A log line! -211 A log line! -212 A log line! -213 A log line! -214 A log line! -215 A log line! -216 A log line! -217 A log line! -218 A log line! -219 A log line! -220 A log line! -221 A log line! -222 A log line! -223 A log line! -224 A log line! -225 A log line! -226 A log line! -227 A log line! -228 A log line! -229 A log line! -230 A log line! -231 A log line! -232 A log line! -233 A log line! -234 A log line! -235 A log line! -236 A log line! -237 A log line! -238 A log line! -239 A log line! -240 A log line! -241 A log line! -242 A log line! -243 A log line! -244 A log line! -245 A log line! -246 A log line! -247 A log line! -248 A log line! -249 A log line! -250 A log line! -251 A log line! -252 A log line! -253 A log line! -254 A log line! -255 A log line! -256 A log line! -257 A log line! -258 A log line! -259 A log line! -260 A log line! -261 A log line! -262 A log line! -263 A log line! -264 A log line! -265 A log line! -266 A log line! -267 A log line! -268 A log line! -269 A log line! -270 A log line! -271 A log line! -272 A log line! -273 A log line! -274 A log line! -275 A log line! -276 A log line! -277 A log line! -278 A log line! -279 A log line! -280 A log line! -281 A log line! -282 A log line! -283 A log line! -284 A log line! -285 A log line! -286 A log line! -287 A log line! -288 A log line! -289 A log line! -290 A log line! -291 A log line! -292 A log line! -293 A log line! -294 A log line! -295 A log line! -296 A log line! -297 A log line! -298 A log line! -299 A log line! -300 A log line! -301 A log line! -302 A log line! -303 A log line! -304 A log line! -305 A log line! -306 A log line! -307 A log line! -308 A log line! -309 A log line! -310 A log line! -311 A log line! -312 A log line! -313 A log line! -314 A log line! -315 A log line! -316 A log line! -317 A log line! -318 A log line! -319 A log line! -320 A log line! -321 A log line! -322 A log line! -323 A log line! -324 A log line! -325 A log line! -326 A log line! -327 A log line! -328 A log line! -329 A log line! -330 A log line! -331 A log line! -332 A log line! diff --git a/nutella_framework-0.8.0.gem b/nutella_framework-0.8.0.gem deleted file mode 100644 index a5c686815c8b151514da32068d1c60def79d99d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 363520 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 m*%K`lcelLxn<&T4 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�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

<1fJ^wsuvh$18mw5{RiMC-L~BpWFav(!qR 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 j#P^u~UY1cl$ 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 diff --git a/nutella_framework.gemspec b/nutella_framework.gemspec index 43d313f..be923cc 100755 --- a/nutella_framework.gemspec +++ b/nutella_framework.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Alessandro Gnoli".freeze] - s.date = "2019-10-26" + s.date = "2019-10-27" s.description = "utella is a framework to create and run RoomApps".freeze s.email = "tebemis@gmail.com".freeze s.executables = ["nutella".freeze] @@ -30,6 +30,19 @@ Gem::Specification.new do |s| "VERSION", "bin/nutella", "lib/bots/binary-files-manager/bin_files_mngr.rb", + "lib/bots/commands_server/commands/broker.rb", + "lib/bots/commands_server/commands/compile.rb", + "lib/bots/commands_server/commands/dependencies.rb", + "lib/bots/commands_server/commands/install.rb", + "lib/bots/commands_server/commands/meta/command.rb", + "lib/bots/commands_server/commands/meta/run_command.rb", + "lib/bots/commands_server/commands/meta/template_command.rb", + "lib/bots/commands_server/commands/new.rb", + "lib/bots/commands_server/commands/reset.rb", + "lib/bots/commands_server/commands/runs.rb", + "lib/bots/commands_server/commands/start.rb", + "lib/bots/commands_server/commands/stop.rb", + "lib/bots/commands_server/commands/template.rb", "lib/bots/commands_server/commands_server.rb", "lib/bots/commands_server/startup", "lib/bots/main_interface/main_interface_bot.rb", @@ -61,36 +74,25 @@ Gem::Specification.new do |s| "lib/bots/room-debugger/room_places_simulator.js", "lib/bots/runs_list_bot/runs_list_bot.rb", "lib/cli/cli.rb", - "lib/cli/cli_utils.rb", - "lib/cli/commands/broker.rb", "lib/cli/commands/checkup.rb", - "lib/cli/commands/compile.rb", - "lib/cli/commands/dependencies.rb", "lib/cli/commands/help.rb", - "lib/cli/commands/install.rb", "lib/cli/commands/meta/command.rb", - "lib/cli/commands/meta/run_command.rb", - "lib/cli/commands/meta/template_command.rb", - "lib/cli/commands/new.rb", - "lib/cli/commands/reset.rb", - "lib/cli/commands/runs.rb", "lib/cli/commands/server.rb", - "lib/cli/commands/start.rb", - "lib/cli/commands/stop.rb", - "lib/cli/commands/template.rb", - "lib/config/config.rb", - "lib/config/current_app_utils.rb", - "lib/config/persisted_hash.rb", - "lib/config/runlist.rb", + "lib/cli/logger.rb", "lib/nutella_framework.rb", "lib/templates/index.html", "lib/templates/startup", "lib/util/components_list.rb", + "lib/util/config.rb", + "lib/util/current_app_utils.rb", "lib/util/framework_components_starter.rb", "lib/util/mongo.rb", "lib/util/mqtt_broker.rb", + "lib/util/persisted_hash.rb", "lib/util/pid.rb", + "lib/util/runlist.rb", "lib/util/supervisor.rb", + "nutella_framework-0.8.0.gem", "nutella_framework.gemspec", "spec/cli/commands/checkup_spec.rb", "spec/cli/commands/help_spec.rb", From 993a4649812243b491de7fb2eee7e59ed1d5a1b8 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 27 Oct 2019 17:56:00 -0700 Subject: [PATCH 19/43] started working on internal supervisor --- .travis.yml | 2 +- Gemfile | 6 ++++- Rakefile | 1 + lib/bots/commands_server/commands_server.rb | 2 +- lib/supervisor/supervisor.proto | 16 +++++++++++ lib/supervisor/supervisor.rb | 30 +++++++++++++++++++++ lib/supervisor/supervisor_pb.rb | 21 +++++++++++++++ lib/supervisor/supervisor_services_pb.rb | 20 ++++++++++++++ lib/supervisor/test_client.rb | 6 +++++ lib/util/supervisor.rb | 13 +++++++-- nutella_framework.gemspec | 2 +- 11 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 lib/supervisor/supervisor.proto create mode 100644 lib/supervisor/supervisor.rb create mode 100644 lib/supervisor/supervisor_pb.rb create mode 100644 lib/supervisor/supervisor_services_pb.rb create mode 100644 lib/supervisor/test_client.rb diff --git a/.travis.yml b/.travis.yml index 5f1b58f..c98e99e 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: ruby rvm: - - 2.6.3 + - 2.3.8 before_install: - gem update --system - gem install bundler diff --git a/Gemfile b/Gemfile index e470bb2..85eb733 100755 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,12 @@ source 'http://rubygems.org' +ruby '2.3.8' + gem "ansi", "~> 1.5" gem 'bson', '~> 3.0' gem 'git', '~> 1.2' -gem 'nutella_lib','~>0.4', '>=0.4.24' +gem "grpc", "~> 1.24" +gem 'nutella_lib','~>0.5' gem 'nokogiri', '~>1.6' gem 'slop', '~>4.0' gem 'semantic', '~> 1.4' @@ -17,6 +20,7 @@ group :development do gem 'rdoc', '~> 4.0' gem 'bundler', '~> 2.0' gem 'jeweler', '~> 2.3' + gem "grpc-tools", "~> 1.24" end group :test do diff --git a/Rakefile b/Rakefile index a3ce73a..1164b11 100755 --- a/Rakefile +++ b/Rakefile @@ -21,6 +21,7 @@ Jeweler::Tasks.new do |gem| gem.description = %Q{utella is a framework to create and run RoomApps} gem.email = "tebemis@gmail.com" gem.authors = ["Alessandro Gnoli"] + gem.required_ruby_version = "~> 2.0" # dependencies defined in Gemfile end Jeweler::RubygemsDotOrgTasks.new diff --git a/lib/bots/commands_server/commands_server.rb b/lib/bots/commands_server/commands_server.rb index e1123d7..fabb5ef 100644 --- a/lib/bots/commands_server/commands_server.rb +++ b/lib/bots/commands_server/commands_server.rb @@ -1,7 +1,7 @@ # Command server # Connects to MQTT broker and listens for commands over (RPC) # Executes the commands and returns the output -# require 'nutella_lib' +require 'nutella_lib' Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| require_relative "commands/#{File.basename(file, File.extname(file))}" end diff --git a/lib/supervisor/supervisor.proto b/lib/supervisor/supervisor.proto new file mode 100644 index 0000000..1f7a64b --- /dev/null +++ b/lib/supervisor/supervisor.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +service Supervisor { + rpc StartProcess (StartProcessRequest) returns (StartProcessResponse); +} + +message StartProcessRequest { + string process_name = 1; + string process_command = 2; + string process_log = 3; +} + +message StartProcessResponse { + bool success = 1; + string process_pid = 2; +} \ No newline at end of file diff --git a/lib/supervisor/supervisor.rb b/lib/supervisor/supervisor.rb new file mode 100644 index 0000000..1e11119 --- /dev/null +++ b/lib/supervisor/supervisor.rb @@ -0,0 +1,30 @@ +# Super-simple process supervisor + +require_relative 'supervisor_services_pb' + +class SupervisorServer < Supervisor::Service + # say_hello implements the SayHello rpc method. + def start_process(start_process_req, _unused_call) + puts "Someone requested to start a process!!! #{start_process_req}" + res = StartProcessResponse.new(success: true) + puts "Responding with: #{res}" + res + end +end + +# Everything that is not an SIGINT (2), SIGTERM (15), +# or a hard SIGKILL (9) (and a SIGSTOP(19) I guess...) +# will trigger a process restart +begin + # Listen for commands over GRPC + # Start and stop processes + # Persist after each operation + s = GRPC::RpcServer.new + s.add_http2_port('0.0.0.0:50051', :this_port_is_insecure) + s.handle(SupervisorServer) + s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT']) +rescue SignalException => e + # Legit termination, exit +rescue + # everything else... restart +end \ No newline at end of file diff --git a/lib/supervisor/supervisor_pb.rb b/lib/supervisor/supervisor_pb.rb new file mode 100644 index 0000000..5d03b36 --- /dev/null +++ b/lib/supervisor/supervisor_pb.rb @@ -0,0 +1,21 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: supervisor.proto + +require 'google/protobuf' + +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("supervisor.proto", :syntax => :proto3) do + add_message "StartProcessRequest" do + optional :process_name, :string, 1 + optional :process_command, :string, 2 + optional :process_log, :string, 3 + end + add_message "StartProcessResponse" do + optional :success, :bool, 1 + optional :process_pid, :string, 2 + end + end +end + +StartProcessRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("StartProcessRequest").msgclass +StartProcessResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("StartProcessResponse").msgclass diff --git a/lib/supervisor/supervisor_services_pb.rb b/lib/supervisor/supervisor_services_pb.rb new file mode 100644 index 0000000..06c212f --- /dev/null +++ b/lib/supervisor/supervisor_services_pb.rb @@ -0,0 +1,20 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# Source: supervisor.proto for package '' + +require 'grpc' +require_relative 'supervisor_pb' + +module Supervisor + class Service + + include GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'Supervisor' + + rpc :StartProcess, StartProcessRequest, StartProcessResponse + end + + Stub = Service.rpc_stub_class +end diff --git a/lib/supervisor/test_client.rb b/lib/supervisor/test_client.rb new file mode 100644 index 0000000..7985515 --- /dev/null +++ b/lib/supervisor/test_client.rb @@ -0,0 +1,6 @@ +require 'grpc' +require_relative 'supervisor_services_pb' + +stub = Supervisor::Stub.new('localhost:50051', :this_channel_is_insecure) +res = stub.start_process(StartProcessRequest.new({process_name: "p123", process_log: "abc"})) +puts "Start process? #{res}" \ No newline at end of file diff --git a/lib/util/supervisor.rb b/lib/util/supervisor.rb index 46649d0..fe50f63 100644 --- a/lib/util/supervisor.rb +++ b/lib/util/supervisor.rb @@ -19,8 +19,12 @@ def initialize # Adds a process to supervision def add(name, command) write_config_file(name, command) - @rpc.call("supervisor.reloadConfig") - @rpc.call("supervisor.addProcessGroup", name) + begin + @rpc.call("supervisor.addProcessGroup", name) + rescue => exception + # Yup, we're swallowing this one because it returns true if it adds it and + # exceptions out if it can't... ^___^ + end end # Adds a group of process to supervision @@ -56,6 +60,11 @@ def stop(name) @rpc.call("supervisor.stopProcess", name) end + # Gets all the info about a process + def getInfo(name) + @rpc.call("supervisor.getProcessInfo", name) + end + private diff --git a/nutella_framework.gemspec b/nutella_framework.gemspec index be923cc..60094d6 100755 --- a/nutella_framework.gemspec +++ b/nutella_framework.gemspec @@ -92,7 +92,6 @@ Gem::Specification.new do |s| "lib/util/pid.rb", "lib/util/runlist.rb", "lib/util/supervisor.rb", - "nutella_framework-0.8.0.gem", "nutella_framework.gemspec", "spec/cli/commands/checkup_spec.rb", "spec/cli/commands/help_spec.rb", @@ -103,6 +102,7 @@ Gem::Specification.new do |s| ] s.homepage = "https://github.com/nutella-framework/nutella_framework".freeze s.licenses = ["MIT".freeze] + s.required_ruby_version = Gem::Requirement.new("~> 2.0".freeze) s.rubygems_version = "3.0.6".freeze s.summary = "A rails-inspired framework for RoomApps".freeze From 9ab34255c33d64dc01e00362ef26ddb8314f0fc8 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 27 Oct 2019 18:26:00 -0700 Subject: [PATCH 20/43] Cleanup for the weekend --- lib/bots/commands_server/Gemfile | 5 +++++ lib/supervisor/supervisor.rb | 25 ++++++++----------------- 2 files changed, 13 insertions(+), 17 deletions(-) create mode 100644 lib/bots/commands_server/Gemfile diff --git a/lib/bots/commands_server/Gemfile b/lib/bots/commands_server/Gemfile new file mode 100644 index 0000000..12551af --- /dev/null +++ b/lib/bots/commands_server/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "nutella_lib", "~> 0.5.0" diff --git a/lib/supervisor/supervisor.rb b/lib/supervisor/supervisor.rb index 1e11119..e6c362c 100644 --- a/lib/supervisor/supervisor.rb +++ b/lib/supervisor/supervisor.rb @@ -2,29 +2,20 @@ require_relative 'supervisor_services_pb' +# Defines the gRPC server class SupervisorServer < Supervisor::Service - # say_hello implements the SayHello rpc method. + # start_process implements the StartProcess rpc method. def start_process(start_process_req, _unused_call) puts "Someone requested to start a process!!! #{start_process_req}" res = StartProcessResponse.new(success: true) puts "Responding with: #{res}" + raise "ALE IS AN ASS" res end end -# Everything that is not an SIGINT (2), SIGTERM (15), -# or a hard SIGKILL (9) (and a SIGSTOP(19) I guess...) -# will trigger a process restart -begin - # Listen for commands over GRPC - # Start and stop processes - # Persist after each operation - s = GRPC::RpcServer.new - s.add_http2_port('0.0.0.0:50051', :this_port_is_insecure) - s.handle(SupervisorServer) - s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT']) -rescue SignalException => e - # Legit termination, exit -rescue - # everything else... restart -end \ No newline at end of file +# Runs the gRPC server +s = GRPC::RpcServer.new +s.add_http2_port('0.0.0.0:50051', :this_port_is_insecure) +s.handle(SupervisorServer) +s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT']) \ No newline at end of file From 235f11e0a973e0de4c9d0d4cce4a9d9f0e41ed35 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 9 Nov 2019 19:14:54 -0800 Subject: [PATCH 21/43] Update checkup to build docker image and install mongo via docker --- Dockerfile.rubyimage | 7 + Gemfile | 1 + .../binary-files-manager/bin_files_mngr.rb | 3 +- lib/bots/commands_server/commands/new.rb | 2 +- lib/bots/commands_server/commands/stop.rb | 4 +- lib/bots/commands_server/commands/template.rb | 4 +- lib/cli/cli.rb | 2 +- lib/cli/commands/checkup.rb | 164 ++++++++++++------ lib/nutella_framework.rb | 5 +- lib/supervisor/supervisor.proto | 16 -- lib/supervisor/supervisor.rb | 21 --- lib/supervisor/supervisor_pb.rb | 21 --- lib/supervisor/supervisor_services_pb.rb | 20 --- lib/supervisor/test_client.rb | 6 - lib/util/config.rb | 5 +- lib/util/framework_components_starter.rb | 6 +- lib/util/mongo.rb | 2 +- lib/util/mqtt_broker.rb | 2 +- lib/util/runlist.rb | 3 +- lib/util/supervisor.rb | 87 ---------- nutella_framework.gemspec | 20 ++- spec/cli/commands/checkup_spec.rb | 4 +- spec/config/config_spec.rb | 6 +- 23 files changed, 156 insertions(+), 255 deletions(-) create mode 100644 Dockerfile.rubyimage delete mode 100644 lib/supervisor/supervisor.proto delete mode 100644 lib/supervisor/supervisor.rb delete mode 100644 lib/supervisor/supervisor_pb.rb delete mode 100644 lib/supervisor/supervisor_services_pb.rb delete mode 100644 lib/supervisor/test_client.rb delete mode 100644 lib/util/supervisor.rb diff --git a/Dockerfile.rubyimage b/Dockerfile.rubyimage new file mode 100644 index 0000000..9194ca7 --- /dev/null +++ b/Dockerfile.rubyimage @@ -0,0 +1,7 @@ +# Builds an image to run framework level bots +FROM ruby:2.3.8 +RUN bundle config --global frozen 1 +WORKDIR /app +COPY Gemfile Gemfile.lock ./ +RUN gem install bundler +RUN bundle install \ No newline at end of file diff --git a/Gemfile b/Gemfile index 85eb733..63e204c 100755 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ ruby '2.3.8' gem "ansi", "~> 1.5" gem 'bson', '~> 3.0' +gem "docker-api", "~> 1.34" gem 'git', '~> 1.2' gem "grpc", "~> 1.24" gem 'nutella_lib','~>0.5' diff --git a/lib/bots/binary-files-manager/bin_files_mngr.rb b/lib/bots/binary-files-manager/bin_files_mngr.rb index e832941..e89373e 100755 --- a/lib/bots/binary-files-manager/bin_files_mngr.rb +++ b/lib/bots/binary-files-manager/bin_files_mngr.rb @@ -25,7 +25,8 @@ # Set data folder -data_folder = "#{ENV['HOME']}/.nutella/data/binary-files-manager" +# We should use nutella config!!!! +# data_folder = "#{ENV['HOME']}/.nutella/data/binary-files-manager" # If data folder doesn't exist, create it unless Dir.exists? data_folder diff --git a/lib/bots/commands_server/commands/new.rb b/lib/bots/commands_server/commands/new.rb index 7deb6f5..5663b9f 100755 --- a/lib/bots/commands_server/commands/new.rb +++ b/lib/bots/commands_server/commands/new.rb @@ -46,7 +46,7 @@ def create_dir_structure( app_id ) config_file_hash = { :name => app_id, :version => '0.1.0', - :nutella_version => File.open("#{Nutella::NUTELLA_HOME}VERSION", 'rb').read, + :nutella_version => File.open("#{Nutella::NUTELLA_SRC}VERSION", 'rb').read, :type => 'application', :description => 'A quick description of your application' } diff --git a/lib/bots/commands_server/commands/stop.rb b/lib/bots/commands_server/commands/stop.rb index fe3d89a..a64be00 100755 --- a/lib/bots/commands_server/commands/stop.rb +++ b/lib/bots/commands_server/commands/stop.rb @@ -66,7 +66,7 @@ def stop_app_bots( app_id ) def stop_framework_components - nutella_components_dir = "#{Nutella::NUTELLA_HOME}framework_components" + nutella_components_dir = "#{Nutella::NUTELLA_SRC}framework_components" ComponentsList.for_each_component_in_dir nutella_components_dir do |component| pid_file_path = "#{nutella_components_dir}/#{component}/.pid" kill_process_with_pid pid_file_path @@ -81,7 +81,7 @@ def stop_internal_broker def stop_mongo - pid_file_path = "#{Nutella.config['config_dir']}.mongo_pid" + pid_file_path = "#{Nutella.config['home_dir']}.mongo_pid" kill_process_with_pid pid_file_path end diff --git a/lib/bots/commands_server/commands/template.rb b/lib/bots/commands_server/commands/template.rb index db8ba07..ad35831 100755 --- a/lib/bots/commands_server/commands/template.rb +++ b/lib/bots/commands_server/commands/template.rb @@ -130,8 +130,8 @@ def create_template_files(json, template_dir) # Create nutella.json file File.open("#{template_dir}/nutella.json", 'w') { |f| f.write(JSON.pretty_generate json) } # Add bot/interface specific files - FileUtils.copy(File.join(Nutella::NUTELLA_HOME, 'data/startup'), template_dir) if json['type']=='bot' - FileUtils.copy(File.join(Nutella::NUTELLA_HOME, 'data/index.html'), template_dir) if json['type']=='interface' + FileUtils.copy(File.join(Nutella::NUTELLA_SRC, 'data/startup'), template_dir) if json['type']=='bot' + FileUtils.copy(File.join(Nutella::NUTELLA_SRC, 'data/index.html'), template_dir) if json['type']=='interface' console.success "Template #{json['name']} created successfully!" end diff --git a/lib/cli/cli.rb b/lib/cli/cli.rb index 39668c2..fe0419a 100755 --- a/lib/cli/cli.rb +++ b/lib/cli/cli.rb @@ -67,7 +67,7 @@ def self.command_exists?(command) # Print nutella logo def self.print_nutella_logo console.info(NUTELLA_LOGO) - nutella_version = File.open("#{Nutella::NUTELLA_HOME}VERSION", 'rb').read + nutella_version = File.open("#{Nutella::NUTELLA_SRC}VERSION", 'rb').read console.info("Welcome to nutella version #{nutella_version}! For a complete lists of available commands type 'nutella help'\n") # If nutella is not ready to be used (i.e. nobody has invoked the "nutella checkup" command yet), # append warning/reminder message diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index 6499d0b..d4a9a9e 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -1,6 +1,7 @@ require_relative 'meta/command' require 'util/config' require 'semantic' +require 'docker-api' module Nutella class Checkup < Command @@ -12,19 +13,16 @@ def run( args=nil ) return unless all_dependencies_installed? # Check if we have a local broker installed - # and install one if we don't - if broker_exists - console.info 'You have a local broker installed. Yay!' - else - console.warn 'You don\'t seem to have a local broker installed so we are going to go ahead and install one for you. This might take some time...' - unless install_local_broker - console.error 'Whoops...something went wrong while installing the broker' - return - end - end + # and installs one if we don't + return unless broker_docker_image_ready? + + # Check if we have a mongo installed locally + # and installs one if we don't + return unless mongo_docker_image_ready? - # Check that supervisor is properly configured - return unless supervisor_configured_correctly? + # Check that we have a nutella ruby image built + # if not, build one + return unless nutella_docker_image_ready? # Set ready flag in config.json Config.file['ready'] = true @@ -34,36 +32,13 @@ def run( args=nil ) end - private + private - - def broker_exists - # Check if Docker image for the broker was already pulled - if `docker images matteocollina/mosca:v2.3.0 --format "{{.ID}}"` != "" - # If so, check that a broker configuration exists and create one if it doesn't - Config.file['broker'] = '127.0.0.1' if Config.file['broker'].nil? - true - else - false - end - end - - def install_local_broker - # Docker pull to install - system "docker pull matteocollina/mosca:v2.3.0 > /dev/null 2>&1" - # Write broker setting inside config.json - Config.file['broker'] = '127.0.0.1' - end - - def all_dependencies_installed? # Docker version lambda docker_semver = lambda do - out = `docker --version` - token = out.split(' ') - token[2].slice(0..1) - Semantic::Version.new token[2].slice(0..1).concat('.0.0') + Semantic::Version.new(Docker.version['Version'].slice(0..1).concat('.0.0')) end # Git version lambda git_semver = lambda do @@ -76,21 +51,9 @@ def all_dependencies_installed? end semver end - # Immortal version lambda - supervisor_semver = lambda do - out = `supervisorctl version` - out.gsub("\n",'') - Semantic::Version.new out - end - # Mongo version lambda - mongo_semver = lambda do - out = `mongod --version` - out.slice!(0,12) - Semantic::Version.new out[0..4] - end # Check versions - return true if check_version?('docker', '17.0.0', docker_semver) && check_version?('git', '1.8.0', git_semver) && check_version?('supervisor', '4.1.0', supervisor_semver) && check_version?('mongodb', '2.6.9', mongo_semver) - # If even one of the checks fails, return false + return true if check_version?('docker', '17.0.0', docker_semver) && check_version?('git', '1.8.0', git_semver) + # If any of the checks fail, return false instead false end @@ -114,14 +77,101 @@ def check_version?(dep, req_version, lambda) end - def supervisor_configured_correctly? - # TODO Check that supervisor's MAC_CONFIG_DIR exists, if not create - # TODO Make sure the MAC_CONFIG_DIR is inluded in supervisor's MAC_CONFIG_FILE, if not include - # TODO Make sure that [inet_http_server] (rpc server) is enabled in MAC_CONFIG_FILE + # Checks that the broker image has been pulled and pulls it if not + def broker_docker_image_ready? + if broker_image_exists? + console.info 'You have a local broker installed. Yay!' + else + console.warn 'You don\'t seem to have a local broker installed so we are going to go ahead and install one for you. This might take some time...' + begin + install_local_broker + rescue => e + puts e + console.error 'Whoops...something went wrong while installing the broker, try running \'nutella checkup\' again' + false + end + console.info 'Broker installed successfully!' + end true end - end -end + # Checks that: 1. The Docker image for the broker has been pulled and + # 2. config.json has been correctly configured + def broker_image_exists? + Docker::Image.exist?('matteocollina/mosca:v2.3.0') && !Config.file['broker'].nil? + end + + def install_local_broker + # Docker pull to install + Docker::Image.create('fromImage': 'matteocollina/mosca:v2.3.0') + # Write broker setting inside config.json + Config.file['broker'] = '127.0.0.1' + end + + + # Checks that the mongo image has been pulled and pulls it if not + def mongo_docker_image_ready? + if mongo_image_exists? + console.info 'You have mongo installed locally. Yay!' + else + console.warn 'You don\'t seem to have a mongo installed locally so we are going to go ahead and install it for you. This might take some time...' + begin + install_local_mongo + rescue => e + puts e + console.error 'Whoops...something went wrong while installing mongo, try running \'nutella checkup\' again' + return false + end + console.info 'Mongo installed successfully!' + end + true + end + + + # Checks that: 1. The Docker image for mongo has been pulled and + # 2. config.json has been correctly configured + def mongo_image_exists? + Docker::Image.exist?('mongo:3.2.21') && !Config.file['mongo'].nil? + end + + + def install_local_mongo + # Docker pull to install + Docker::Image.create('fromImage': 'mongo:3.2.21') + # Write mongo setting inside config.json + Config.file['mongo'] = '127.0.0.1' + end + + + def nutella_docker_image_ready? + if nutella_image_exists? + console.info 'You have a nutella docker image ready. Yay!' + else + console.warn 'You don\'t seem to have a nutella docker image ready. We\'re gonna go ahead and build one for you. This might take some time...' + begin + build_nutella_docker_image + rescue => e + puts e + console.error 'Whoops...something went wrong while building the nutella docker image, try running \'nutella checkup\' again' + return false + end + console.info 'nutella docker image built successfully!' + end + true + end + + # Checks that the nutella image exists and if not tries to build it + def nutella_image_exists? + Docker::Image.exist?('nutella:1.0.0') + end + + + def build_nutella_docker_image + img = Docker::Image.build_from_dir(NUTELLA_SRC, { 'dockerfile': 'Dockerfile.rubyimage' }) + img.tag('repo': 'nutella', 'tag': '1.0.0', force: true) + end + + end +end diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index db105cc..2dc1348 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -4,8 +4,9 @@ module Nutella # Initialize nutella home to the folder where this source code is - NUTELLA_HOME = File.dirname(__FILE__)[0..-4] - NUTELLA_TMP = "#{NUTELLA_HOME}.tmp/" + NUTELLA_SRC = File.dirname(__FILE__)[0..-4] + NUTELLA_TMP = "#{NUTELLA_SRC}.tmp/" + NUTELLA_HOME = "#{ENV['HOME']}.nutella/" # If the nutella configuration file (config.json) is empty (or doesn't exist) we're going to initialize it # with nutella constants and defaults diff --git a/lib/supervisor/supervisor.proto b/lib/supervisor/supervisor.proto deleted file mode 100644 index 1f7a64b..0000000 --- a/lib/supervisor/supervisor.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -service Supervisor { - rpc StartProcess (StartProcessRequest) returns (StartProcessResponse); -} - -message StartProcessRequest { - string process_name = 1; - string process_command = 2; - string process_log = 3; -} - -message StartProcessResponse { - bool success = 1; - string process_pid = 2; -} \ No newline at end of file diff --git a/lib/supervisor/supervisor.rb b/lib/supervisor/supervisor.rb deleted file mode 100644 index e6c362c..0000000 --- a/lib/supervisor/supervisor.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Super-simple process supervisor - -require_relative 'supervisor_services_pb' - -# Defines the gRPC server -class SupervisorServer < Supervisor::Service - # start_process implements the StartProcess rpc method. - def start_process(start_process_req, _unused_call) - puts "Someone requested to start a process!!! #{start_process_req}" - res = StartProcessResponse.new(success: true) - puts "Responding with: #{res}" - raise "ALE IS AN ASS" - res - end -end - -# Runs the gRPC server -s = GRPC::RpcServer.new -s.add_http2_port('0.0.0.0:50051', :this_port_is_insecure) -s.handle(SupervisorServer) -s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT']) \ No newline at end of file diff --git a/lib/supervisor/supervisor_pb.rb b/lib/supervisor/supervisor_pb.rb deleted file mode 100644 index 5d03b36..0000000 --- a/lib/supervisor/supervisor_pb.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: supervisor.proto - -require 'google/protobuf' - -Google::Protobuf::DescriptorPool.generated_pool.build do - add_file("supervisor.proto", :syntax => :proto3) do - add_message "StartProcessRequest" do - optional :process_name, :string, 1 - optional :process_command, :string, 2 - optional :process_log, :string, 3 - end - add_message "StartProcessResponse" do - optional :success, :bool, 1 - optional :process_pid, :string, 2 - end - end -end - -StartProcessRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("StartProcessRequest").msgclass -StartProcessResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("StartProcessResponse").msgclass diff --git a/lib/supervisor/supervisor_services_pb.rb b/lib/supervisor/supervisor_services_pb.rb deleted file mode 100644 index 06c212f..0000000 --- a/lib/supervisor/supervisor_services_pb.rb +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# Source: supervisor.proto for package '' - -require 'grpc' -require_relative 'supervisor_pb' - -module Supervisor - class Service - - include GRPC::GenericService - - self.marshal_class_method = :encode - self.unmarshal_class_method = :decode - self.service_name = 'Supervisor' - - rpc :StartProcess, StartProcessRequest, StartProcessResponse - end - - Stub = Service.rpc_stub_class -end diff --git a/lib/supervisor/test_client.rb b/lib/supervisor/test_client.rb deleted file mode 100644 index 7985515..0000000 --- a/lib/supervisor/test_client.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'grpc' -require_relative 'supervisor_services_pb' - -stub = Supervisor::Stub.new('localhost:50051', :this_channel_is_insecure) -res = stub.start_process(StartProcessRequest.new({process_name: "p123", process_log: "abc"})) -puts "Start process? #{res}" \ No newline at end of file diff --git a/lib/util/config.rb b/lib/util/config.rb index 7cad5e8..60bc3b8 100755 --- a/lib/util/config.rb +++ b/lib/util/config.rb @@ -8,8 +8,9 @@ class Config # - immortal_dir: directory used to store immortal yaml files # - main_interface_port: the port used to serve interfaces def self.init - file['config_dir'] = "#{ENV['HOME']}/.nutella/" - file['broker_dir'] = "#{file['config_dir']}broker/" + file['src_dir'] = NUTELLA_SRC + file['tmp_dir'] = NUTELLA_TMP + file['home_dir'] = NUTELLA_HOME file['main_interface_port'] = 57880 end diff --git a/lib/util/framework_components_starter.rb b/lib/util/framework_components_starter.rb index 27d175a..965cd8f 100755 --- a/lib/util/framework_components_starter.rb +++ b/lib/util/framework_components_starter.rb @@ -1,4 +1,4 @@ -require 'util/supervisor' +require 'docker-api' module Nutella # Utility functions to deal with framework components @@ -11,7 +11,7 @@ def self.start # Starts all framework components. # @return [boolean] true if all components are started correctly, false otherwise def start - nutella_components_dir = "#{Nutella::NUTELLA_HOME}lib/bots" + nutella_components_dir = "#{Nutella::NUTELLA_SRC}lib/bots" # Todo, refactor so we don't reload 20 times framework_components.each do |c| Supervisor.instance.add("nutella_f_#{c}", "#{nutella_components_dir}/#{c}/startup") @@ -26,7 +26,7 @@ def start # Finds the framework level components def framework_components - d = "#{Nutella::NUTELLA_HOME}lib/bots" + d = "#{Nutella::NUTELLA_SRC}lib/bots" Dir.entries(d) .select {|entry| File.directory?(File.join(d, entry)) && !(entry =='.' || entry == '..') } .select { |c| File.exist? "#{d}/#{c}/startup" } diff --git a/lib/util/mongo.rb b/lib/util/mongo.rb index a02004c..1eaccb1 100644 --- a/lib/util/mongo.rb +++ b/lib/util/mongo.rb @@ -14,7 +14,7 @@ def self.start # installs mongo as a service and runs it. # @return [boolean] true if mongo has been correctly started, false otherwise def start_mongo_db - pid_file_path = "#{Config.file['config_dir']}.mongo_pid" + pid_file_path = "#{Config.file['home_dir']}.mongo_pid" # Check if the process with pid indicated in the pidfile is alive return true if PidFile.sanitize pid_file_path # Check that mongo is not running 'unsupervised' (i.e. check port 27017), if it is, return diff --git a/lib/util/mqtt_broker.rb b/lib/util/mqtt_broker.rb index 39d2a76..7c954f1 100644 --- a/lib/util/mqtt_broker.rb +++ b/lib/util/mqtt_broker.rb @@ -15,7 +15,7 @@ def start_internal_broker return true unless broker_port_free? # Broker is not running so we try to start it broker # TODO need to check that this is actually successfull... - `docker run -p 1883:1883 -p 1884:80 -d -v #{Config.file['broker_dir']}:/db matteocollina/mosca:v2.3.0` + `docker run -p 1883:1883 -p 1884:80 -d -v #{Config.file['home_dir']}broker/:/db matteocollina/mosca:v2.3.0` # Wait until the broker is up wait_for_broker # All went well so we return true diff --git a/lib/util/runlist.rb b/lib/util/runlist.rb index 2133bb0..35fd679 100755 --- a/lib/util/runlist.rb +++ b/lib/util/runlist.rb @@ -162,7 +162,8 @@ def app_has_no_bots( app_id ) # Calling this method (Nutella.runlist) simply returns and instance of # RunListHash linked to file runlist.json in the nutella home directory def Nutella.runlist - rl = RunListHash.new( "#{ENV['HOME']}/.nutella/runlist.json" ) + # TODO replace this with the config!!!! + # rl = RunListHash.new( "#{ENV['HOME']}/.nutella/runlist.json" ) rl.clean_list rl end diff --git a/lib/util/supervisor.rb b/lib/util/supervisor.rb deleted file mode 100644 index fe50f63..0000000 --- a/lib/util/supervisor.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'xmlrpc/client' -require 'singleton' - -module Nutella - MAC_CONFIG_FILE="/usr/local/etc/supervisord.ini" - MAC_CONFIG_DIR="/usr/local/etc/supervisor.d" - - # This class wraps supervisor and makes it easier to interact with - # Important note. The `reloadConfig` RPC is actually a `reread` which - # does NOT stop the supervisor - class Supervisor - include Singleton - - def initialize - # TODO need to wrap all @rpc calls in exceptions!!!! - @rpc = XMLRPC::Client.new2("http://localhost:9001/RPC2") - end - - # Adds a process to supervision - def add(name, command) - write_config_file(name, command) - begin - @rpc.call("supervisor.addProcessGroup", name) - rescue => exception - # Yup, we're swallowing this one because it returns true if it adds it and - # exceptions out if it can't... ^___^ - end - end - - # Adds a group of process to supervision - def add_group(processes) - processes.each { |name, command| write_config_file(name, command) } - @rpc.call("supervisor.reloadConfig") - processes.each { |name, _| @rpc.call("supervisor.addProcessGroup", name) } - end - - # Removes a process from supervision - def remove(name) - @rpc.call("supervisor.removeProcessGroup", name) - delete_config_file(name) - @rpc.call("supervisor.reloadConfig") - end - - # Removes a group of processes from supervision - def remove_group(processes) - @rpc.call("supervisor.removeProcessGroup", name) - processes.each do |name| - delete_config_file(name) - end - @rpc.call("supervisor.reloadConfig") - end - - # Starts a process, retuns false if error - def start(name) - @rpc.call("supervisor.startProcess", name) - end - - # Stops a process, retuns false if error - def stop(name) - @rpc.call("supervisor.stopProcess", name) - end - - # Gets all the info about a process - def getInfo(name) - @rpc.call("supervisor.getProcessInfo", name) - end - - - private - - def write_config_file(name, command) - file = "#{MAC_CONFIG_DIR}/#{name}.ini" - File.open(file, 'w') do |f| - f.puts "[program:#{name}]" - f.puts "command=#{command}" - f.puts "stdout_logfile=#{command[0..-8]+'stdout.log'}" - f.puts "autostart=false" - f.puts "redirect_stderr=true" - end - end - - def delete_config_file(name) - File.delete("#{MAC_CONFIG_DIR}/#{name}.ini") - end - - end -end \ No newline at end of file diff --git a/nutella_framework.gemspec b/nutella_framework.gemspec index 60094d6..39d0a75 100755 --- a/nutella_framework.gemspec +++ b/nutella_framework.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Alessandro Gnoli".freeze] - s.date = "2019-10-27" + s.date = "2019-11-10" s.description = "utella is a framework to create and run RoomApps".freeze s.email = "tebemis@gmail.com".freeze s.executables = ["nutella".freeze] @@ -30,6 +30,7 @@ Gem::Specification.new do |s| "VERSION", "bin/nutella", "lib/bots/binary-files-manager/bin_files_mngr.rb", + "lib/bots/commands_server/Gemfile", "lib/bots/commands_server/commands/broker.rb", "lib/bots/commands_server/commands/compile.rb", "lib/bots/commands_server/commands/dependencies.rb", @@ -80,6 +81,7 @@ Gem::Specification.new do |s| "lib/cli/commands/server.rb", "lib/cli/logger.rb", "lib/nutella_framework.rb", + "lib/templates/Dockerfile", "lib/templates/index.html", "lib/templates/startup", "lib/util/components_list.rb", @@ -91,7 +93,6 @@ Gem::Specification.new do |s| "lib/util/persisted_hash.rb", "lib/util/pid.rb", "lib/util/runlist.rb", - "lib/util/supervisor.rb", "nutella_framework.gemspec", "spec/cli/commands/checkup_spec.rb", "spec/cli/commands/help_spec.rb", @@ -112,8 +113,10 @@ Gem::Specification.new do |s| if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q.freeze, ["~> 1.5"]) s.add_runtime_dependency(%q.freeze, ["~> 3.0"]) + s.add_runtime_dependency(%q.freeze, ["~> 1.34"]) s.add_runtime_dependency(%q.freeze, ["~> 1.2"]) - s.add_runtime_dependency(%q.freeze, ["~> 0.4", ">= 0.4.24"]) + s.add_runtime_dependency(%q.freeze, ["~> 1.24"]) + s.add_runtime_dependency(%q.freeze, ["~> 0.5"]) s.add_runtime_dependency(%q.freeze, ["~> 1.6"]) s.add_runtime_dependency(%q.freeze, ["~> 4.0"]) s.add_runtime_dependency(%q.freeze, ["~> 1.4"]) @@ -125,11 +128,14 @@ Gem::Specification.new do |s| s.add_development_dependency(%q.freeze, ["~> 4.0"]) s.add_development_dependency(%q.freeze, ["~> 2.0"]) s.add_development_dependency(%q.freeze, ["~> 2.3"]) + s.add_development_dependency(%q.freeze, ["~> 1.24"]) else s.add_dependency(%q.freeze, ["~> 1.5"]) s.add_dependency(%q.freeze, ["~> 3.0"]) + s.add_dependency(%q.freeze, ["~> 1.34"]) s.add_dependency(%q.freeze, ["~> 1.2"]) - s.add_dependency(%q.freeze, ["~> 0.4", ">= 0.4.24"]) + s.add_dependency(%q.freeze, ["~> 1.24"]) + s.add_dependency(%q.freeze, ["~> 0.5"]) s.add_dependency(%q.freeze, ["~> 1.6"]) s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 1.4"]) @@ -141,12 +147,15 @@ Gem::Specification.new do |s| s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, ["~> 2.3"]) + s.add_dependency(%q.freeze, ["~> 1.24"]) end else s.add_dependency(%q.freeze, ["~> 1.5"]) s.add_dependency(%q.freeze, ["~> 3.0"]) + s.add_dependency(%q.freeze, ["~> 1.34"]) s.add_dependency(%q.freeze, ["~> 1.2"]) - s.add_dependency(%q.freeze, ["~> 0.4", ">= 0.4.24"]) + s.add_dependency(%q.freeze, ["~> 1.24"]) + s.add_dependency(%q.freeze, ["~> 0.5"]) s.add_dependency(%q.freeze, ["~> 1.6"]) s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 1.4"]) @@ -158,6 +167,7 @@ Gem::Specification.new do |s| s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, ["~> 2.3"]) + s.add_dependency(%q.freeze, ["~> 1.24"]) end end diff --git a/spec/cli/commands/checkup_spec.rb b/spec/cli/commands/checkup_spec.rb index 155a2a6..feb0a48 100644 --- a/spec/cli/commands/checkup_spec.rb +++ b/spec/cli/commands/checkup_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' -require 'cli/cli' +require 'nutella_framework' module Nutella describe Checkup do # Skipping because this command relies heavily on shelling out - skip 'executes correctly' do + it 'executes correctly' do NutellaCLI.execute_command('checkup') end end diff --git a/spec/config/config_spec.rb b/spec/config/config_spec.rb index 6fddde0..2cd63cd 100644 --- a/spec/config/config_spec.rb +++ b/spec/config/config_spec.rb @@ -20,7 +20,7 @@ module Nutella context 'invoked once' do it 'initializes config correctly' do Config.init - expect(@ph.length).to eq(3) + expect(@ph.length).to eq(4) end end @@ -28,7 +28,7 @@ module Nutella it 'initializes config file correctly' do Config.init Config.init - expect(@ph.length).to eq(3) + expect(@ph.length).to eq(4) end end end @@ -39,7 +39,7 @@ module Nutella it 'returns a valid persisted hash with configuration' do Config.init @ph = Config.file - expect(@ph.length).to eq(3) + expect(@ph.length).to eq(4) end end From bb70749d7998756b1e04dfa7de0f681d7d450eaa Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 9 Nov 2019 22:49:12 -0800 Subject: [PATCH 22/43] Got mqtt broker launched with docker --- Dockerfile.rubyimage | 2 +- Gemfile | 2 + .../commands/meta/run_command.rb | 2 +- lib/bots/commands_server/commands_server.rb | 8 +- .../commands_server}/util/components_list.rb | 0 lib/cli/commands/checkup.rb | 4 +- lib/cli/commands/server.rb | 20 ++--- lib/nutella_framework.rb | 2 +- lib/util/mqtt_broker.rb | 76 ++++++++++++++++--- spec/cli/commands/checkup_spec.rb | 3 +- spec/cli/commands/server_spec.rb | 2 +- 11 files changed, 92 insertions(+), 29 deletions(-) rename lib/{ => bots/commands_server}/util/components_list.rb (100%) diff --git a/Dockerfile.rubyimage b/Dockerfile.rubyimage index 9194ca7..f45c150 100644 --- a/Dockerfile.rubyimage +++ b/Dockerfile.rubyimage @@ -1,7 +1,7 @@ # Builds an image to run framework level bots FROM ruby:2.3.8 +RUN gem install bundler RUN bundle config --global frozen 1 WORKDIR /app COPY Gemfile Gemfile.lock ./ -RUN gem install bundler RUN bundle install \ No newline at end of file diff --git a/Gemfile b/Gemfile index 63e204c..8416909 100755 --- a/Gemfile +++ b/Gemfile @@ -22,10 +22,12 @@ group :development do gem 'bundler', '~> 2.0' gem 'jeweler', '~> 2.3' gem "grpc-tools", "~> 1.24" + gem "awesome_print", "~> 1.8" end group :test do gem 'rake' gem 'rspec', '~> 3.8' gem 'fuubar', '~> 2.4' + gem "awesome_print", "~> 1.8" end diff --git a/lib/bots/commands_server/commands/meta/run_command.rb b/lib/bots/commands_server/commands/meta/run_command.rb index 758f040..bc5bb78 100755 --- a/lib/bots/commands_server/commands/meta/run_command.rb +++ b/lib/bots/commands_server/commands/meta/run_command.rb @@ -1,5 +1,5 @@ require_relative 'command' -require 'util/components_list' +require_relative '../../util/components_list' require 'slop' module Nutella diff --git a/lib/bots/commands_server/commands_server.rb b/lib/bots/commands_server/commands_server.rb index fabb5ef..d08e7e0 100644 --- a/lib/bots/commands_server/commands_server.rb +++ b/lib/bots/commands_server/commands_server.rb @@ -14,13 +14,19 @@ begin i = 0 - while true + while i < 10 puts "#{i} A log line!" i = i + 1 sleep 1 end + raise StandardError, "Puttana merda!!!" rescue SignalException => e puts "HEY I WAS KILLED!!!" $stderr.puts "AND I COMPLAIN IN STDERR" puts e end + + +# docker run -d --restart=unless-stopped -v "$PWD":/app --name nutella:1.0.0 ruby +# docker kill +# docker rm 4e77e5964bd8 \ No newline at end of file diff --git a/lib/util/components_list.rb b/lib/bots/commands_server/util/components_list.rb similarity index 100% rename from lib/util/components_list.rb rename to lib/bots/commands_server/util/components_list.rb diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index d4a9a9e..ed27643 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -108,6 +108,8 @@ def install_local_broker Docker::Image.create('fromImage': 'matteocollina/mosca:v2.3.0') # Write broker setting inside config.json Config.file['broker'] = '127.0.0.1' + # Create data directory for the broker + Dir.mkdir("#{NUTELLA_HOME}broker") end @@ -167,7 +169,7 @@ def nutella_image_exists? Docker::Image.exist?('nutella:1.0.0') end - + # Builds a docker image that we can use to run framework level bots in a dockerized way def build_nutella_docker_image img = Docker::Image.build_from_dir(NUTELLA_SRC, { 'dockerfile': 'Dockerfile.rubyimage' }) img.tag('repo': 'nutella', 'tag': '1.0.0', force: true) diff --git a/lib/cli/commands/server.rb b/lib/cli/commands/server.rb index 40c1a6d..e4ca135 100644 --- a/lib/cli/commands/server.rb +++ b/lib/cli/commands/server.rb @@ -13,16 +13,16 @@ def run(args=nil) else console.error('Failed to start MQTT broker') end - if Mongo.start - console.success('Mongo started') - else - console.error('Failed to start Mongo') - end - if FrameworkComponents.start - console.success('Framework level components started') - else - console.error('Failed to start Framework level components') - end + # if Mongo.start + # console.success('Mongo started') + # else + # console.error('Failed to start Mongo') + # end + # if FrameworkComponents.start + # console.success('Framework level components started') + # else + # console.error('Failed to start Framework level components') + # end end end diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index 2dc1348..8382737 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -6,7 +6,7 @@ module Nutella # Initialize nutella home to the folder where this source code is NUTELLA_SRC = File.dirname(__FILE__)[0..-4] NUTELLA_TMP = "#{NUTELLA_SRC}.tmp/" - NUTELLA_HOME = "#{ENV['HOME']}.nutella/" + NUTELLA_HOME = "#{ENV['HOME']}/.nutella/" # If the nutella configuration file (config.json) is empty (or doesn't exist) we're going to initialize it # with nutella constants and defaults diff --git a/lib/util/mqtt_broker.rb b/lib/util/mqtt_broker.rb index 7c954f1..7bfcc8b 100644 --- a/lib/util/mqtt_broker.rb +++ b/lib/util/mqtt_broker.rb @@ -1,5 +1,6 @@ require 'socket' require 'util/config' +require 'docker-api' module Nutella class MQTTBroker @@ -7,18 +8,41 @@ class MQTTBroker def self.start MQTTBroker.new.start_internal_broker end + + def self.stop + MQTTBroker.new.stop_internal_broker + end def start_internal_broker - # Check if the broker has been started already, if it is, return - return true if broker_started? - # Check that broker is not running 'unsupervised' (i.e. check port 1883), if it is, return - return true unless broker_port_free? - # Broker is not running so we try to start it broker - # TODO need to check that this is actually successfull... - `docker run -p 1883:1883 -p 1884:80 -d -v #{Config.file['home_dir']}broker/:/db matteocollina/mosca:v2.3.0` + # Check if the broker has been started already + return true if broker_started? || broker_started_unsupervised? + # Broker is not running so we try to start it + begin + start_broker + rescue + return false + end # Wait until the broker is up wait_for_broker - # All went well so we return true + true + end + + def stop_internal_broker + # Find the broker'scontainer + begin + c = Docker::Container.get('nutella_broker') + rescue Docker::Error::NotFoundError + # There is no container so the broker + # is definitely not runnning, we're done + return true + end + # Try to stop the broker + begin + c.stop + c.delete(force: true) + rescue + return false + end true end @@ -27,13 +51,18 @@ def start_internal_broker # Checks if the broker is running already # @return [boolean] true if there is a container for the broker running already def broker_started? - `docker ps --filter ancestor=matteocollina/mosca:v2.3.0 --format "{{.ID}}"` != "" + begin + Docker::Container.get('nutella_broker') + rescue Docker::Error::NotFoundError + return false + end + true end # Checks if port 1883 (MQTT broker port) is free # or some other service is already listening on it # @return [boolean] true if there is no broker listening on port 1883, false otherwise - def broker_port_free? + def broker_started_unsupervised? begin s = TCPServer.new('0.0.0.0', 1883) s.close @@ -43,6 +72,31 @@ def broker_port_free? true end + # Starts the broker using docker + def start_broker + # remove any other 'nutella_broker' containers + begin + old_c = Docker::Container.get('nutella_broker') + old_c.delete(force: true) + rescue Docker::Error::NotFoundError + # If the container is not there we just proceed + end + # Try to create and start the container + Docker::Container.create( + 'Image': 'matteocollina/mosca:v2.3.0', + 'name': 'nutella_broker', + 'Detach': true, + 'HostConfig': { + 'PortBindings': { + '1883/tcp': [{ 'HostPort': '1883'}], + '80/tcp': [{ 'HostPort': '1884'}] + }, + 'Binds': ["#{Config.file['home_dir']}broker:/db"], + 'RestartPolicy': {'Name': 'unless-stopped'} + } + ).start + end + # Checks if there is connectivity to localhost:1883. If not, # it waits 1/4 second and then tries again def wait_for_broker @@ -56,4 +110,4 @@ def wait_for_broker end end -end +end \ No newline at end of file diff --git a/spec/cli/commands/checkup_spec.rb b/spec/cli/commands/checkup_spec.rb index feb0a48..aa36465 100644 --- a/spec/cli/commands/checkup_spec.rb +++ b/spec/cli/commands/checkup_spec.rb @@ -3,9 +3,8 @@ module Nutella describe Checkup do - # Skipping because this command relies heavily on shelling out it 'executes correctly' do - NutellaCLI.execute_command('checkup') + expect { NutellaCLI.execute_command('checkup') }.not_to raise_error end end end \ No newline at end of file diff --git a/spec/cli/commands/server_spec.rb b/spec/cli/commands/server_spec.rb index a42abd2..34d2334 100644 --- a/spec/cli/commands/server_spec.rb +++ b/spec/cli/commands/server_spec.rb @@ -4,7 +4,7 @@ module Nutella describe Server do # Skipped because it interacts heavily with supervisor - skip 'Starts the MQTT broker' do + it 'Starts the MQTT broker' do NutellaCLI.execute_command('server') end end From 826434d65d46a272436191fafd7c072841877611 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 9 Nov 2019 23:00:31 -0800 Subject: [PATCH 23/43] Fix silly bug --- Gemfile | 11 ++++------- lib/util/mqtt_broker.rb | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 8416909..e129ab2 100755 --- a/Gemfile +++ b/Gemfile @@ -2,11 +2,10 @@ source 'http://rubygems.org' ruby '2.3.8' -gem "ansi", "~> 1.5" +gem 'ansi', '~> 1.5' gem 'bson', '~> 3.0' -gem "docker-api", "~> 1.34" +gem 'docker-api', '~> 1.34' gem 'git', '~> 1.2' -gem "grpc", "~> 1.24" gem 'nutella_lib','~>0.5' gem 'nokogiri', '~>1.6' gem 'slop', '~>4.0' @@ -14,20 +13,18 @@ gem 'semantic', '~> 1.4' gem 'sinatra', '~>1.4' gem 'sinatra-cross_origin', '~> 0.3.2' gem 'thin', '~>1.6' -gem "xmlrpc", "~> 0.3.0" group :development do gem 'yard', '~> 0.9.11' gem 'rdoc', '~> 4.0' gem 'bundler', '~> 2.0' gem 'jeweler', '~> 2.3' - gem "grpc-tools", "~> 1.24" - gem "awesome_print", "~> 1.8" end group :test do gem 'rake' gem 'rspec', '~> 3.8' gem 'fuubar', '~> 2.4' - gem "awesome_print", "~> 1.8" + gem 'awesome_print', '~> 1.8' + gem 'pry', '~> 0.12.2' end diff --git a/lib/util/mqtt_broker.rb b/lib/util/mqtt_broker.rb index 7bfcc8b..1d740f2 100644 --- a/lib/util/mqtt_broker.rb +++ b/lib/util/mqtt_broker.rb @@ -67,14 +67,14 @@ def broker_started_unsupervised? s = TCPServer.new('0.0.0.0', 1883) s.close rescue - return false + return true end - true + false end # Starts the broker using docker def start_broker - # remove any other 'nutella_broker' containers + # Remove any other 'nutella_broker' containers begin old_c = Docker::Container.get('nutella_broker') old_c.delete(force: true) From 3030a82a541ecf61cdee5a2bf7a7dc28a1ab011e Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 9 Nov 2019 23:04:29 -0800 Subject: [PATCH 24/43] Version bump to 2.0.0.alpha1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 8adc70f..dde7cc6 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.0 \ No newline at end of file +2.0.0.alpha1 \ No newline at end of file From 0ef0a7fa8d0c5d1e99b960b11dc750d048b1bdfe Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 9 Nov 2019 23:06:52 -0800 Subject: [PATCH 25/43] regenerate gemspec --- nutella_framework.gemspec | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/nutella_framework.gemspec b/nutella_framework.gemspec index 39d0a75..a64d256 100755 --- a/nutella_framework.gemspec +++ b/nutella_framework.gemspec @@ -2,13 +2,13 @@ # DO NOT EDIT THIS FILE DIRECTLY # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- -# stub: nutella_framework 0.8.0 ruby lib +# stub: nutella_framework 2.0.0.alpha1 ruby lib Gem::Specification.new do |s| s.name = "nutella_framework".freeze - s.version = "0.8.0" + s.version = "2.0.0.alpha1" - s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= + s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Alessandro Gnoli".freeze] s.date = "2019-11-10" @@ -23,6 +23,7 @@ Gem::Specification.new do |s| ".document", ".rspec", ".travis.yml", + "Dockerfile.rubyimage", "Gemfile", "LICENSE", "README.md", @@ -46,6 +47,7 @@ Gem::Specification.new do |s| "lib/bots/commands_server/commands/template.rb", "lib/bots/commands_server/commands_server.rb", "lib/bots/commands_server/startup", + "lib/bots/commands_server/util/components_list.rb", "lib/bots/main_interface/main_interface_bot.rb", "lib/bots/main_interface/public/index.html", "lib/bots/main_interface/views/index.erb", @@ -81,10 +83,8 @@ Gem::Specification.new do |s| "lib/cli/commands/server.rb", "lib/cli/logger.rb", "lib/nutella_framework.rb", - "lib/templates/Dockerfile", "lib/templates/index.html", "lib/templates/startup", - "lib/util/components_list.rb", "lib/util/config.rb", "lib/util/current_app_utils.rb", "lib/util/framework_components_starter.rb", @@ -115,7 +115,6 @@ Gem::Specification.new do |s| s.add_runtime_dependency(%q.freeze, ["~> 3.0"]) s.add_runtime_dependency(%q.freeze, ["~> 1.34"]) s.add_runtime_dependency(%q.freeze, ["~> 1.2"]) - s.add_runtime_dependency(%q.freeze, ["~> 1.24"]) s.add_runtime_dependency(%q.freeze, ["~> 0.5"]) s.add_runtime_dependency(%q.freeze, ["~> 1.6"]) s.add_runtime_dependency(%q.freeze, ["~> 4.0"]) @@ -123,18 +122,15 @@ Gem::Specification.new do |s| s.add_runtime_dependency(%q.freeze, ["~> 1.4"]) s.add_runtime_dependency(%q.freeze, ["~> 0.3.2"]) s.add_runtime_dependency(%q.freeze, ["~> 1.6"]) - s.add_runtime_dependency(%q.freeze, ["~> 0.3.0"]) s.add_development_dependency(%q.freeze, ["~> 0.9.11"]) s.add_development_dependency(%q.freeze, ["~> 4.0"]) s.add_development_dependency(%q.freeze, ["~> 2.0"]) s.add_development_dependency(%q.freeze, ["~> 2.3"]) - s.add_development_dependency(%q.freeze, ["~> 1.24"]) else s.add_dependency(%q.freeze, ["~> 1.5"]) s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 1.34"]) s.add_dependency(%q.freeze, ["~> 1.2"]) - s.add_dependency(%q.freeze, ["~> 1.24"]) s.add_dependency(%q.freeze, ["~> 0.5"]) s.add_dependency(%q.freeze, ["~> 1.6"]) s.add_dependency(%q.freeze, ["~> 4.0"]) @@ -142,19 +138,16 @@ Gem::Specification.new do |s| s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 0.3.2"]) s.add_dependency(%q.freeze, ["~> 1.6"]) - s.add_dependency(%q.freeze, ["~> 0.3.0"]) s.add_dependency(%q.freeze, ["~> 0.9.11"]) s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, ["~> 2.3"]) - s.add_dependency(%q.freeze, ["~> 1.24"]) end else s.add_dependency(%q.freeze, ["~> 1.5"]) s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 1.34"]) s.add_dependency(%q.freeze, ["~> 1.2"]) - s.add_dependency(%q.freeze, ["~> 1.24"]) s.add_dependency(%q.freeze, ["~> 0.5"]) s.add_dependency(%q.freeze, ["~> 1.6"]) s.add_dependency(%q.freeze, ["~> 4.0"]) @@ -162,12 +155,10 @@ Gem::Specification.new do |s| s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 0.3.2"]) s.add_dependency(%q.freeze, ["~> 1.6"]) - s.add_dependency(%q.freeze, ["~> 0.3.0"]) s.add_dependency(%q.freeze, ["~> 0.9.11"]) s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, ["~> 2.3"]) - s.add_dependency(%q.freeze, ["~> 1.24"]) end end From 0979d1217284318d15066832e383f9898fae8e19 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 10 Nov 2019 00:22:02 -0800 Subject: [PATCH 26/43] Start mongo with docker --- .gitignore | 3 - lib/cli/commands/checkup.rb | 8 +- lib/cli/commands/server.rb | 16 +-- .../commands/server/framework_bots.rb} | 2 + lib/cli/commands/server/mongo.rb | 116 ++++++++++++++++++ .../commands/server}/mqtt_broker.rb | 20 +-- lib/util/mongo.rb | 60 --------- lib/util/pid.rb | 29 ----- 8 files changed, 145 insertions(+), 109 deletions(-) rename lib/{util/framework_components_starter.rb => cli/commands/server/framework_bots.rb} (98%) create mode 100644 lib/cli/commands/server/mongo.rb rename lib/{util => cli/commands/server}/mqtt_broker.rb (84%) delete mode 100644 lib/util/mongo.rb delete mode 100644 lib/util/pid.rb diff --git a/.gitignore b/.gitignore index 228f276..742ee38 100755 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,6 @@ config.json runlist.json broker/ .tmp/ -.pid -.mongo_pid -*.log # Test app test_app/ diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index ed27643..1d9342c 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -109,7 +109,9 @@ def install_local_broker # Write broker setting inside config.json Config.file['broker'] = '127.0.0.1' # Create data directory for the broker - Dir.mkdir("#{NUTELLA_HOME}broker") + unless File.directory?("#{NUTELLA_HOME}broker") + Dir.mkdir("#{NUTELLA_HOME}broker") + end end @@ -144,6 +146,10 @@ def install_local_mongo Docker::Image.create('fromImage': 'mongo:3.2.21') # Write mongo setting inside config.json Config.file['mongo'] = '127.0.0.1' + # Create data directory for mongo + unless File.directory?("#{NUTELLA_HOME}mongodb") + Dir.mkdir("#{NUTELLA_HOME}mongodb") + end end diff --git a/lib/cli/commands/server.rb b/lib/cli/commands/server.rb index e4ca135..3077355 100644 --- a/lib/cli/commands/server.rb +++ b/lib/cli/commands/server.rb @@ -1,7 +1,7 @@ require_relative 'meta/command' -require 'util/mqtt_broker' -require 'util/mongo' -require 'util/framework_components_starter' +require_relative 'server/mqtt_broker' +require_relative 'server/mongo' +require_relative 'server/framework_bots' module Nutella class Server < Command @@ -13,11 +13,11 @@ def run(args=nil) else console.error('Failed to start MQTT broker') end - # if Mongo.start - # console.success('Mongo started') - # else - # console.error('Failed to start Mongo') - # end + if Mongo.start + console.success('Mongo started') + else + console.error('Failed to start Mongo') + end # if FrameworkComponents.start # console.success('Framework level components started') # else diff --git a/lib/util/framework_components_starter.rb b/lib/cli/commands/server/framework_bots.rb similarity index 98% rename from lib/util/framework_components_starter.rb rename to lib/cli/commands/server/framework_bots.rb index 965cd8f..d81d14f 100755 --- a/lib/util/framework_components_starter.rb +++ b/lib/cli/commands/server/framework_bots.rb @@ -1,4 +1,6 @@ require 'docker-api' +require 'socket' +require 'util/config' module Nutella # Utility functions to deal with framework components diff --git a/lib/cli/commands/server/mongo.rb b/lib/cli/commands/server/mongo.rb new file mode 100644 index 0000000..585ec9a --- /dev/null +++ b/lib/cli/commands/server/mongo.rb @@ -0,0 +1,116 @@ +require 'docker-api' +require 'socket' +require 'util/config' + +module Nutella + class Mongo + + def self.start + Mongo.new.start_mongo_db + end + + def self.stop + Mongo.new.stop_mongo_db + end + + def start_mongo_db + # Check if mongo has been started already + return true if mongo_started? || mongo_started_unsupervised? + # Mongo is not running so we try to start it + begin + start_mongo + rescue + return false + end + # Wait until mongo is up + wait_for_mongo + true + end + + def stop_mongo_db + # Find mongo's container + begin + c = Docker::Container.get(mongo_container_name) + rescue Docker::Error::NotFoundError + # There is no container so mongo + # is definitely not runnning, we're done + return true + end + # Try to stop mongo + begin + c.stop + c.delete(force: true) + rescue + return false + end + true + end + + private + + def mongo_container_name + @mongo_container_name ||= 'mongodb' + end + + # Checks if mongo is running already + # @return [boolean] true if there is a container for mongo running already + def mongo_started? + begin + Docker::Container.get(mongo_container_name) + rescue Docker::Error::NotFoundError + return false + end + true + end + + # Checks if port 27017 (MongoDB standard port) is free + # or some other service is already listening on it + # @return [boolean] true if there is no mongo listening on port 27017, false otherwise + def mongo_started_unsupervised? + begin + s = TCPServer.new('0.0.0.0', 27017) + s.close + rescue + return true + end + false + end + + # Starts mongo using docker + def start_mongo + # Remove any other containers with the same name to avoid conflicts + begin + old_c = Docker::Container.get(mongo_container_name) + old_c.delete(force: true) + rescue Docker::Error::NotFoundError + # If the container is not there we just proceed + end + # Try to create and start the container for mongo + Docker::Container.create( + 'Image': 'mongo:3.2.21', + 'name': mongo_container_name, + 'Detach': true, + 'HostConfig': { + 'PortBindings': { + '27017/tcp': [{ 'HostPort': '27017'}] + }, + 'Binds': ["#{Config.file['home_dir']}mongo:/data/db"], + 'RestartPolicy': {'Name': 'unless-stopped'} + } + ).start + end + + # Checks if there is connectivity to localhost:27017. If not, + # it waits 1/4 second and then tries again + def wait_for_mongo + begin + s = TCPSocket.open('localhost', 27017) + s.close + rescue Errno::ECONNREFUSED + sleep 0.25 + wait_for_mongo + end + end + + end +end diff --git a/lib/util/mqtt_broker.rb b/lib/cli/commands/server/mqtt_broker.rb similarity index 84% rename from lib/util/mqtt_broker.rb rename to lib/cli/commands/server/mqtt_broker.rb index 1d740f2..e442178 100644 --- a/lib/util/mqtt_broker.rb +++ b/lib/cli/commands/server/mqtt_broker.rb @@ -1,6 +1,6 @@ +require 'docker-api' require 'socket' require 'util/config' -require 'docker-api' module Nutella class MQTTBroker @@ -28,9 +28,9 @@ def start_internal_broker end def stop_internal_broker - # Find the broker'scontainer + # Find the broker's container begin - c = Docker::Container.get('nutella_broker') + c = Docker::Container.get(broker_container_name) rescue Docker::Error::NotFoundError # There is no container so the broker # is definitely not runnning, we're done @@ -48,11 +48,15 @@ def stop_internal_broker private + def broker_container_name + @broker_container_name ||= 'mqtt_broker' + end + # Checks if the broker is running already # @return [boolean] true if there is a container for the broker running already def broker_started? begin - Docker::Container.get('nutella_broker') + Docker::Container.get(broker_container_name) rescue Docker::Error::NotFoundError return false end @@ -74,17 +78,17 @@ def broker_started_unsupervised? # Starts the broker using docker def start_broker - # Remove any other 'nutella_broker' containers + # Remove any other containers with the same name to avoid conflicts begin - old_c = Docker::Container.get('nutella_broker') + old_c = Docker::Container.get(broker_container_name) old_c.delete(force: true) rescue Docker::Error::NotFoundError # If the container is not there we just proceed end - # Try to create and start the container + # Try to create and start the container for the broker Docker::Container.create( 'Image': 'matteocollina/mosca:v2.3.0', - 'name': 'nutella_broker', + 'name': broker_container_name, 'Detach': true, 'HostConfig': { 'PortBindings': { diff --git a/lib/util/mongo.rb b/lib/util/mongo.rb deleted file mode 100644 index 1eaccb1..0000000 --- a/lib/util/mongo.rb +++ /dev/null @@ -1,60 +0,0 @@ -require 'socket' -require 'util/pid' - -module Nutella - class Mongo - include PidFile - - def self.start - Mongo.new.start_mongo_db - end - - # Starts mongodb if it's not started already. - # This operation is only necessary on mac because Ubuntu automatically - # installs mongo as a service and runs it. - # @return [boolean] true if mongo has been correctly started, false otherwise - def start_mongo_db - pid_file_path = "#{Config.file['home_dir']}.mongo_pid" - # Check if the process with pid indicated in the pidfile is alive - return true if PidFile.sanitize pid_file_path - # Check that mongo is not running 'unsupervised' (i.e. check port 27017), if it is, return - return true unless mongo_port_free? - # Mongo is not running and there is no pid file so we try to start it and create a new pid file. - # Note that the pid file is created by the `startup` script, not here. - pid = fork - exec("mongod --config /usr/local/etc/mongod.conf > /dev/null 2>&1 & \necho $! > #{pid_file_path}") if pid.nil? - # Wait until mongo is up - wait_for_mongo - # All went well so we return true - true - end - - private - - # Checks if port 27017 (MongoDB standard port) is free - # or some other service is already listening on it - # @return [boolean] true if there is no mongo listening on port 27017, false otherwise - def mongo_port_free? - begin - s = TCPServer.new('0.0.0.0', 27017) - s.close - rescue - return false - end - true - end - - # Checks if there is connectivity to localhost:27017. If not, - # it waits 1/4 second and then tries again - def wait_for_mongo - begin - s = TCPSocket.open('localhost', 27017) - s.close - rescue Errno::ECONNREFUSED - sleep 0.25 - wait_for_mongo - end - end - - end -end diff --git a/lib/util/pid.rb b/lib/util/pid.rb deleted file mode 100644 index 85f173a..0000000 --- a/lib/util/pid.rb +++ /dev/null @@ -1,29 +0,0 @@ -module PidFile - - # Cleans the pid file of a given process - # @param [String] pid_file_path the file storing the pid file of the process - # @return [Boolean] true if the pid file exists AND the process with that pid is still alive - def PidFile.sanitize( pid_file_path ) - # Does the pid file exist? - # If it does we try to see if the process with that pid is still alive - if File.exist? pid_file_path - pid_file = File.open(pid_file_path, 'rb') - pid = pid_file.read.to_i - pid_file.close - begin - # If this statement doesn't throw an exception then a process with - # this pid is still alive so we do nothing and just return true - Process.getpgid pid - return true - rescue - # If there is an exception, there is no process with this pid - # so we have a stale pid file that we need to remove - File.delete pid_file_path - return false - end - end - # If there is no pid file, there is no process running - false - end - -end \ No newline at end of file From 5f0e88ea7f65aa08a3f05ad26cbe04dc78e50928 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 10 Nov 2019 01:32:30 -0800 Subject: [PATCH 27/43] Start framework level bots using docker --- .../bin_files_mngr.rb | 0 lib/bots/commands_server/Gemfile | 5 -- lib/bots/commands_server/commands_server.rb | 32 ------- lib/bots/commands_server/startup | 4 - lib/bots/commands_server/startup.rb | 26 ++++++ .../README.md | 0 .../css/bootstrap-theme.css | 0 .../css/bootstrap-theme.css.map | 0 .../css/bootstrap-theme.min.css | 0 .../css/bootstrap.css | 0 .../css/bootstrap.css.map | 0 .../css/bootstrap.min.css | 0 .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin .../fonts/glyphicons-halflings-regular.woff2 | Bin .../index.html | 0 .../js/bootstrap.js | 0 .../js/bootstrap.min.js | 0 .../js/jquery.min.js | 0 .../js/npm.js | 0 .../js/nutella_lib.js | 0 .../{room-debugger => room_debugger}/main.css | 0 .../{room-debugger => room_debugger}/main.js | 0 .../nutella.json | 0 .../package.json | 0 .../room_places_simulator.js | 0 lib/cli/cli.rb | 3 + lib/cli/commands/server.rb | 10 +-- lib/cli/commands/server/framework_bots.rb | 83 ++++++++++++++---- lib/cli/commands/server/mongo.rb | 4 +- lib/cli/commands/server/mqtt_broker.rb | 3 +- 33 files changed, 105 insertions(+), 65 deletions(-) rename lib/bots/{binary-files-manager => binary_files_manager}/bin_files_mngr.rb (100%) delete mode 100644 lib/bots/commands_server/Gemfile delete mode 100644 lib/bots/commands_server/commands_server.rb delete mode 100755 lib/bots/commands_server/startup create mode 100644 lib/bots/commands_server/startup.rb rename lib/bots/{room-debugger => room_debugger}/README.md (100%) rename lib/bots/{room-debugger => room_debugger}/css/bootstrap-theme.css (100%) rename lib/bots/{room-debugger => room_debugger}/css/bootstrap-theme.css.map (100%) rename lib/bots/{room-debugger => room_debugger}/css/bootstrap-theme.min.css (100%) rename lib/bots/{room-debugger => room_debugger}/css/bootstrap.css (100%) rename lib/bots/{room-debugger => room_debugger}/css/bootstrap.css.map (100%) rename lib/bots/{room-debugger => room_debugger}/css/bootstrap.min.css (100%) rename lib/bots/{room-debugger => room_debugger}/fonts/glyphicons-halflings-regular.eot (100%) rename lib/bots/{room-debugger => room_debugger}/fonts/glyphicons-halflings-regular.svg (100%) rename lib/bots/{room-debugger => room_debugger}/fonts/glyphicons-halflings-regular.ttf (100%) rename lib/bots/{room-debugger => room_debugger}/fonts/glyphicons-halflings-regular.woff (100%) rename lib/bots/{room-debugger => room_debugger}/fonts/glyphicons-halflings-regular.woff2 (100%) rename lib/bots/{room-debugger => room_debugger}/index.html (100%) rename lib/bots/{room-debugger => room_debugger}/js/bootstrap.js (100%) rename lib/bots/{room-debugger => room_debugger}/js/bootstrap.min.js (100%) rename lib/bots/{room-debugger => room_debugger}/js/jquery.min.js (100%) rename lib/bots/{room-debugger => room_debugger}/js/npm.js (100%) rename lib/bots/{room-debugger => room_debugger}/js/nutella_lib.js (100%) rename lib/bots/{room-debugger => room_debugger}/main.css (100%) rename lib/bots/{room-debugger => room_debugger}/main.js (100%) rename lib/bots/{room-debugger => room_debugger}/nutella.json (100%) rename lib/bots/{room-debugger => room_debugger}/package.json (100%) rename lib/bots/{room-debugger => room_debugger}/room_places_simulator.js (100%) diff --git a/lib/bots/binary-files-manager/bin_files_mngr.rb b/lib/bots/binary_files_manager/bin_files_mngr.rb similarity index 100% rename from lib/bots/binary-files-manager/bin_files_mngr.rb rename to lib/bots/binary_files_manager/bin_files_mngr.rb diff --git a/lib/bots/commands_server/Gemfile b/lib/bots/commands_server/Gemfile deleted file mode 100644 index 12551af..0000000 --- a/lib/bots/commands_server/Gemfile +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gem "nutella_lib", "~> 0.5.0" diff --git a/lib/bots/commands_server/commands_server.rb b/lib/bots/commands_server/commands_server.rb deleted file mode 100644 index d08e7e0..0000000 --- a/lib/bots/commands_server/commands_server.rb +++ /dev/null @@ -1,32 +0,0 @@ -# Command server -# Connects to MQTT broker and listens for commands over (RPC) -# Executes the commands and returns the output -require 'nutella_lib' -Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| - require_relative "commands/#{File.basename(file, File.extname(file))}" -end - -$stdout.sync = true ## nutella woudl do this -# $stdout.sync = true -puts "Hi, I'm a basic ruby bot and all I do is idle and print stuff" -$stderr.puts "certainly first param is set #{ARGV[0]}" - - -begin - i = 0 - while i < 10 - puts "#{i} A log line!" - i = i + 1 - sleep 1 - end - raise StandardError, "Puttana merda!!!" -rescue SignalException => e - puts "HEY I WAS KILLED!!!" - $stderr.puts "AND I COMPLAIN IN STDERR" - puts e -end - - -# docker run -d --restart=unless-stopped -v "$PWD":/app --name nutella:1.0.0 ruby -# docker kill -# docker rm 4e77e5964bd8 \ No newline at end of file diff --git a/lib/bots/commands_server/startup b/lib/bots/commands_server/startup deleted file mode 100755 index 281e528..0000000 --- a/lib/bots/commands_server/startup +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -BASEDIR=$(dirname $0) -ruby $BASEDIR/commands_server.rb diff --git a/lib/bots/commands_server/startup.rb b/lib/bots/commands_server/startup.rb new file mode 100644 index 0000000..df57255 --- /dev/null +++ b/lib/bots/commands_server/startup.rb @@ -0,0 +1,26 @@ +# Commands server +# Connects to MQTT broker, listens for commands (basically RPC over MQTT), +# executes the commands, and returns the output to the client +require 'nutella_lib' +Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| + require_relative "commands/#{File.basename(file, File.extname(file))}" +end + +## nutella shuold do this... we need this! +$stdout.sync = true +puts "Hi, I'm a basic ruby bot and all I do is idle and print stuff" +puts "Certainly first param is set to: #{ARGV[0]}" + +begin + i = 0 + while i < 10 + puts "#{i} A log line!" + i = i + 1 + sleep 1 + end + raise StandardError, "Oh no! Standard error! Good thing someone will restart me..." +rescue SignalException => e + puts "This is printed when I get SIGINT" +end + +puts "This is the last line before I really die" \ No newline at end of file diff --git a/lib/bots/room-debugger/README.md b/lib/bots/room_debugger/README.md similarity index 100% rename from lib/bots/room-debugger/README.md rename to lib/bots/room_debugger/README.md diff --git a/lib/bots/room-debugger/css/bootstrap-theme.css b/lib/bots/room_debugger/css/bootstrap-theme.css similarity index 100% rename from lib/bots/room-debugger/css/bootstrap-theme.css rename to lib/bots/room_debugger/css/bootstrap-theme.css diff --git a/lib/bots/room-debugger/css/bootstrap-theme.css.map b/lib/bots/room_debugger/css/bootstrap-theme.css.map similarity index 100% rename from lib/bots/room-debugger/css/bootstrap-theme.css.map rename to lib/bots/room_debugger/css/bootstrap-theme.css.map diff --git a/lib/bots/room-debugger/css/bootstrap-theme.min.css b/lib/bots/room_debugger/css/bootstrap-theme.min.css similarity index 100% rename from lib/bots/room-debugger/css/bootstrap-theme.min.css rename to lib/bots/room_debugger/css/bootstrap-theme.min.css diff --git a/lib/bots/room-debugger/css/bootstrap.css b/lib/bots/room_debugger/css/bootstrap.css similarity index 100% rename from lib/bots/room-debugger/css/bootstrap.css rename to lib/bots/room_debugger/css/bootstrap.css diff --git a/lib/bots/room-debugger/css/bootstrap.css.map b/lib/bots/room_debugger/css/bootstrap.css.map similarity index 100% rename from lib/bots/room-debugger/css/bootstrap.css.map rename to lib/bots/room_debugger/css/bootstrap.css.map diff --git a/lib/bots/room-debugger/css/bootstrap.min.css b/lib/bots/room_debugger/css/bootstrap.min.css similarity index 100% rename from lib/bots/room-debugger/css/bootstrap.min.css rename to lib/bots/room_debugger/css/bootstrap.min.css diff --git a/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.eot b/lib/bots/room_debugger/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from lib/bots/room-debugger/fonts/glyphicons-halflings-regular.eot rename to lib/bots/room_debugger/fonts/glyphicons-halflings-regular.eot diff --git a/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.svg b/lib/bots/room_debugger/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from lib/bots/room-debugger/fonts/glyphicons-halflings-regular.svg rename to lib/bots/room_debugger/fonts/glyphicons-halflings-regular.svg diff --git a/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.ttf b/lib/bots/room_debugger/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from lib/bots/room-debugger/fonts/glyphicons-halflings-regular.ttf rename to lib/bots/room_debugger/fonts/glyphicons-halflings-regular.ttf diff --git a/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff b/lib/bots/room_debugger/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff rename to lib/bots/room_debugger/fonts/glyphicons-halflings-regular.woff diff --git a/lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff2 b/lib/bots/room_debugger/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff2 rename to lib/bots/room_debugger/fonts/glyphicons-halflings-regular.woff2 diff --git a/lib/bots/room-debugger/index.html b/lib/bots/room_debugger/index.html similarity index 100% rename from lib/bots/room-debugger/index.html rename to lib/bots/room_debugger/index.html diff --git a/lib/bots/room-debugger/js/bootstrap.js b/lib/bots/room_debugger/js/bootstrap.js similarity index 100% rename from lib/bots/room-debugger/js/bootstrap.js rename to lib/bots/room_debugger/js/bootstrap.js diff --git a/lib/bots/room-debugger/js/bootstrap.min.js b/lib/bots/room_debugger/js/bootstrap.min.js similarity index 100% rename from lib/bots/room-debugger/js/bootstrap.min.js rename to lib/bots/room_debugger/js/bootstrap.min.js diff --git a/lib/bots/room-debugger/js/jquery.min.js b/lib/bots/room_debugger/js/jquery.min.js similarity index 100% rename from lib/bots/room-debugger/js/jquery.min.js rename to lib/bots/room_debugger/js/jquery.min.js diff --git a/lib/bots/room-debugger/js/npm.js b/lib/bots/room_debugger/js/npm.js similarity index 100% rename from lib/bots/room-debugger/js/npm.js rename to lib/bots/room_debugger/js/npm.js diff --git a/lib/bots/room-debugger/js/nutella_lib.js b/lib/bots/room_debugger/js/nutella_lib.js similarity index 100% rename from lib/bots/room-debugger/js/nutella_lib.js rename to lib/bots/room_debugger/js/nutella_lib.js diff --git a/lib/bots/room-debugger/main.css b/lib/bots/room_debugger/main.css similarity index 100% rename from lib/bots/room-debugger/main.css rename to lib/bots/room_debugger/main.css diff --git a/lib/bots/room-debugger/main.js b/lib/bots/room_debugger/main.js similarity index 100% rename from lib/bots/room-debugger/main.js rename to lib/bots/room_debugger/main.js diff --git a/lib/bots/room-debugger/nutella.json b/lib/bots/room_debugger/nutella.json similarity index 100% rename from lib/bots/room-debugger/nutella.json rename to lib/bots/room_debugger/nutella.json diff --git a/lib/bots/room-debugger/package.json b/lib/bots/room_debugger/package.json similarity index 100% rename from lib/bots/room-debugger/package.json rename to lib/bots/room_debugger/package.json diff --git a/lib/bots/room-debugger/room_places_simulator.js b/lib/bots/room_debugger/room_places_simulator.js similarity index 100% rename from lib/bots/room-debugger/room_places_simulator.js rename to lib/bots/room_debugger/room_places_simulator.js diff --git a/lib/cli/cli.rb b/lib/cli/cli.rb index fe0419a..9404b72 100755 --- a/lib/cli/cli.rb +++ b/lib/cli/cli.rb @@ -21,6 +21,9 @@ class NutellaCLI # the method that gets called. # It reads the command line parameters and it invokes the right sub-command def self.run + # Flush output immediately + $stdout.sync = true + # Read parameters args = ARGV.dup args.shift diff --git a/lib/cli/commands/server.rb b/lib/cli/commands/server.rb index 3077355..2c284d8 100644 --- a/lib/cli/commands/server.rb +++ b/lib/cli/commands/server.rb @@ -18,11 +18,11 @@ def run(args=nil) else console.error('Failed to start Mongo') end - # if FrameworkComponents.start - # console.success('Framework level components started') - # else - # console.error('Failed to start Framework level components') - # end + if FrameworkBots.start + console.success('Framework-level bots started') + else + console.error('Failed to start Framework-level bots') + end end end diff --git a/lib/cli/commands/server/framework_bots.rb b/lib/cli/commands/server/framework_bots.rb index d81d14f..8f20c97 100755 --- a/lib/cli/commands/server/framework_bots.rb +++ b/lib/cli/commands/server/framework_bots.rb @@ -3,39 +3,90 @@ require 'util/config' module Nutella - # Utility functions to deal with framework components - class FrameworkComponents + class FrameworkBots def self.start - FrameworkComponents.new.start + FrameworkBots.new.start end - # Starts all framework components. - # @return [boolean] true if all components are started correctly, false otherwise + def self.stop + FrameworkBots.new.stop + end + + # Starts all framework bots. + # @return [boolean] true if all bots are started correctly, false otherwise def start - nutella_components_dir = "#{Nutella::NUTELLA_SRC}lib/bots" - # Todo, refactor so we don't reload 20 times - framework_components.each do |c| - Supervisor.instance.add("nutella_f_#{c}", "#{nutella_components_dir}/#{c}/startup") + result = true + framework_bots.each do |bot| + unless bot_started?(bot) + result && start_bot(bot) + end end - framework_components.each do |c| - puts Supervisor.instance.start("nutella_f_#{c}") + result + end + + def stop + result = true + # Find docker images of the bots and terminate them + # Compound results and return + result + end + + private + + def bot_started?(bot_name) + begin + c = Docker::Container.get("nutella_f_#{bot_name}") + return c.info['State']['Running'] + rescue Docker::Error::NotFoundError + return false end true end - private + def start_bot(bot_name) + bot_container_name = "nutella_f_#{bot_name}" + bot_dir = "#{Nutella::NUTELLA_SRC}lib/bots/#{bot_name}" + # Remove any other containers with the same name to avoid conflicts + begin + old_c = Docker::Container.get(bot_container_name) + old_c.delete(force: true) + rescue Docker::Error::NotFoundError + # If the container is not there we just proceed + end + # Try to create and start the container for the bot + begin + Docker::Container.create( + 'Cmd': [ + 'ruby', + 'startup.rb', + Config.file['broker'] + ], + 'Image': 'nutella:1.0.0', + 'name': bot_container_name, + 'Detach': true, + 'HostConfig': { + 'Binds': ["#{bot_dir}:/app"], + 'RestartPolicy': {'Name': 'unless-stopped'} + } + ).start + rescue => e + console.error "Failed to start #{bot_name}!" + console.error e + return false + end + true + end - # Finds the framework level components - def framework_components + # Finds the framework level bots + def framework_bots d = "#{Nutella::NUTELLA_SRC}lib/bots" Dir.entries(d) .select {|entry| File.directory?(File.join(d, entry)) && !(entry =='.' || entry == '..') } - .select { |c| File.exist? "#{d}/#{c}/startup" } + .select { |c| File.exist? "#{d}/#{c}/startup.rb" } end end - end # # Starts the application level bots diff --git a/lib/cli/commands/server/mongo.rb b/lib/cli/commands/server/mongo.rb index 585ec9a..7221306 100644 --- a/lib/cli/commands/server/mongo.rb +++ b/lib/cli/commands/server/mongo.rb @@ -56,11 +56,11 @@ def mongo_container_name # @return [boolean] true if there is a container for mongo running already def mongo_started? begin - Docker::Container.get(mongo_container_name) + c = Docker::Container.get(mongo_container_name) + return c.info['State']['Running'] rescue Docker::Error::NotFoundError return false end - true end # Checks if port 27017 (MongoDB standard port) is free diff --git a/lib/cli/commands/server/mqtt_broker.rb b/lib/cli/commands/server/mqtt_broker.rb index e442178..f95d82c 100644 --- a/lib/cli/commands/server/mqtt_broker.rb +++ b/lib/cli/commands/server/mqtt_broker.rb @@ -56,7 +56,8 @@ def broker_container_name # @return [boolean] true if there is a container for the broker running already def broker_started? begin - Docker::Container.get(broker_container_name) + c = Docker::Container.get(broker_container_name) + return c.info['State']['Running'] rescue Docker::Error::NotFoundError return false end From 0169cac74d19d44082a629d91be3d15154a65aff Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 10 Nov 2019 01:47:44 -0800 Subject: [PATCH 28/43] Fix readme and silly bug --- README.md | 23 +++++++++++++++++------ lib/cli/commands/server/framework_bots.rb | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5eb733b..57095e0 100755 --- a/README.md +++ b/README.md @@ -7,16 +7,27 @@ nutella is a framework to build and run Macroworlds. The original prototype was # Installing nutella works on OSX and Linux (tested on Ubuntu) and it depends on a couple other things to work correctly. You will need: -1. _ruby_ (version >= 2.3.0). Do yourself a favor and use [RVM](https://rvm.io/rvm/install) to install Ruby. -1. _git_ (version >= 1.8.0). Should come with the OS, yay! -1. _supervisor_ (version >= 4.1.0). You can use [Homebrew](http://brew.sh/) to install supervisor on OSX. -1. _docker_ (version >= 17.03.0). We use Docker to run the broker that handles all communications between all the pieces of the framework. Do yourself a favor and use [Docker for mac](https://store.docker.com/editions/community/docker-ce-desktop-mac), if you are on OSX. +1. _ruby_ (version >= 2.3.8). Do yourself a favor and use [RVM](https://rvm.io/rvm/install) to install Ruby. +1. _docker_ (version >= 17.03.0). If you are on OSX, do yourself a favor and use [Docker for mac](https://store.docker.com/editions/community/docker-ce-desktop-mac). +1. _git_ (version >= 1.8.0). Should come with the OS, nothing to do, yay! -Once you have all of thi stuff installed you can do: +Once you have all the nutella dependencies installed you can do: ``` gem install nutella_framework ``` -Once the installation is complete you should be able to type `nutella` in your shell and get a welcome message. +You should then be able to type `nutella` in your shell and get a welcome message that looks like this + +``` + _ _ _ + | | | | | + _ __ _ _| |_ ___| | | __ _ + | _ \| | | | __/ _ \ | |/ _ | + | | | | |_| | || __/ | | (_| | + |_| |_|\__,_|\__\___|_|_|\__,_| + +Welcome to nutella version 1.0.0! For a complete lists of available commands type 'nutella help' +Looks like this is a fresh installation of nutella. Please run 'nutella checkup' to check all dependencies are installed. +``` ## nutella checkup If you are reading this you probably already saw the warning: "Looks like this is a fresh installation of nutella. Please run `nutella checkup` to check all dependencies are installed correctly". **Please follow the prompt!** diff --git a/lib/cli/commands/server/framework_bots.rb b/lib/cli/commands/server/framework_bots.rb index 8f20c97..c2783e6 100755 --- a/lib/cli/commands/server/framework_bots.rb +++ b/lib/cli/commands/server/framework_bots.rb @@ -72,7 +72,7 @@ def start_bot(bot_name) ).start rescue => e console.error "Failed to start #{bot_name}!" - console.error e + puts e return false end true From 3c3621e3587ffde2740ba3702738ba16f6c2533b Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 10 Nov 2019 15:29:53 -0800 Subject: [PATCH 29/43] Basic control loop for commands --- Gemfile | 2 +- lib/bots/commands_server/commands/start.rb | 45 ++++++++------- lib/bots/commands_server/startup.rb | 54 ++++++++++++----- lib/cli/cli.rb | 3 +- lib/cli/commands/start.rb | 24 ++++++++ lib/cli/commands/stop.rb | 24 ++++++++ nutella_framework.gemspec | 67 +++++++++++----------- 7 files changed, 148 insertions(+), 71 deletions(-) create mode 100644 lib/cli/commands/start.rb create mode 100644 lib/cli/commands/stop.rb diff --git a/Gemfile b/Gemfile index e129ab2..aafbf63 100755 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ gem 'ansi', '~> 1.5' gem 'bson', '~> 3.0' gem 'docker-api', '~> 1.34' gem 'git', '~> 1.2' -gem 'nutella_lib','~>0.5' +gem 'nutella_lib','~>0.6' gem 'nokogiri', '~>1.6' gem 'slop', '~>4.0' gem 'semantic', '~> 1.4' diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index 60b421c..15ae20a 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -6,34 +6,39 @@ class Start < RunCommand @description = 'Starts all or some of the bots in the current application' def run(args=nil) - - # If the current directory is not a nutella application, return - unless Nutella.current_app.exist? - console.warn 'The current directory is not a nutella application' - return + if rand < 0.5 + { success: true, message: "This is a success for command Start with args #{args}"} + else + { success: false, message: "This is a failure for command Start with args #{args}"} end - begin - run_id, params = parse_cli_arguments args - rescue StandardError => e - console.error e.message - return - end + # # If the current directory is not a nutella application, return + # unless Nutella.current_app.exist? + # console.warn 'The current directory is not a nutella application' + # return + # end - app_id, app_path = fetch_app_details + # begin + # run_id, params = parse_cli_arguments args + # rescue StandardError => e + # console.error e.message + # return + # end - if no_app_bot_to_start app_id, app_path, params - console.warn "Run #{run} not created: your application bots are already started and you specified no regular bots exclusively for this run" - return - end + # app_id, app_path = fetch_app_details + + # if no_app_bot_to_start app_id, app_path, params + # console.warn "Run #{run} not created: your application bots are already started and you specified no regular bots exclusively for this run" + # return + # end - return if run_exist?( app_id, run_id) + # return if run_exist?( app_id, run_id) - return unless start_all_components(app_id, app_path, run_id, params) + # return unless start_all_components(app_id, app_path, run_id, params) - return unless Nutella.runlist.add?(app_id, run_id, app_path) + # return unless Nutella.runlist.add?(app_id, run_id, app_path) - print_confirmation(run_id, params, app_id, app_path) + # print_confirmation(run_id, params, app_id, app_path) end diff --git a/lib/bots/commands_server/startup.rb b/lib/bots/commands_server/startup.rb index df57255..35e9321 100644 --- a/lib/bots/commands_server/startup.rb +++ b/lib/bots/commands_server/startup.rb @@ -1,26 +1,50 @@ # Commands server -# Connects to MQTT broker, listens for commands (basically RPC over MQTT), +# Connects to the MQTT broker, listens for commands (basically RPC over MQTT), # executes the commands, and returns the output to the client +# This is the heart of nutella and implements all the nutella logic really + require 'nutella_lib' +# Load all available commands Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| require_relative "commands/#{File.basename(file, File.extname(file))}" end -## nutella shuold do this... we need this! -$stdout.sync = true -puts "Hi, I'm a basic ruby bot and all I do is idle and print stuff" -puts "Certainly first param is set to: #{ARGV[0]}" +# Parse command line arguments and initialize nutella +broker = nutella.f.parse_args ARGV +component_id = nutella.f.extract_component_id +nutella.f.init(broker, component_id) + +# Commands handler +nutella.f.net.handle_requests('commands', lambda do |message, component_id| + nutella.log.info("[#{Time.now}] Command received: #{message}") + execute_command(message['command'], message['params']) +end) -begin - i = 0 - while i < 10 - puts "#{i} A log line!" - i = i + 1 - sleep 1 +# This function executes a particular command +# @param command [String] the name of the command +# @param args [Array] command line parameters passed to the command +def execute_command(command, args=nil) + # Check that the command exists and if it does, + # execute its run method passing the args parameters + if command_exists?(command) + begin + return Object::const_get("Nutella::#{command.capitalize}").new.run(args) + rescue => e + return { success: false, message: "Unexpected failure of command #{command}", exception: e } + end + else + return { success: false, message: "Unknown command #{command}" } end - raise StandardError, "Oh no! Standard error! Good thing someone will restart me..." -rescue SignalException => e - puts "This is printed when I get SIGINT" end -puts "This is the last line before I really die" \ No newline at end of file +# This function checks that a particular command exists +# @return [Boolean] true if the command exists, false otherwise +def self.command_exists?(command) + return Nutella.const_get("Nutella::#{command.capitalize}").is_a?(Class) && + Nutella.const_get("Nutella::#{command.capitalize}").method_defined?(:run) +rescue NameError + return false +end + +nutella.log.success "Starting commands server..." +nutella.f.net.listen \ No newline at end of file diff --git a/lib/cli/cli.rb b/lib/cli/cli.rb index 9404b72..a40a236 100755 --- a/lib/cli/cli.rb +++ b/lib/cli/cli.rb @@ -62,7 +62,8 @@ def self.execute_command(command, args=nil) # This method checks that a particular command exists # @return [Boolean] true if the command exists, false otherwise def self.command_exists?(command) - return Nutella.const_get("Nutella::#{command.capitalize}").is_a?(Class) + return Nutella.const_get("Nutella::#{command.capitalize}").is_a?(Class) && + Nutella.const_get("Nutella::#{command.capitalize}").method_defined?(:run) rescue NameError return false end diff --git a/lib/cli/commands/start.rb b/lib/cli/commands/start.rb new file mode 100644 index 0000000..f4d2dae --- /dev/null +++ b/lib/cli/commands/start.rb @@ -0,0 +1,24 @@ +require_relative 'meta/command' +require 'nutella_lib' + +module Nutella + class Start < Command + @description = 'Starts runs and nutella applications' + + def run(args=nil) + nutella.f.init(Config.file['broker'], 'nutella_cli') + response = nutella.f.net.sync_request( 'commands', { "command": "start", "params": args} ) + if response['success'] + console.success response['message'] + else + console.error response['message'] + if response['exception'] != nil + console.error response['exception'] + end + end + end + + end +end + + diff --git a/lib/cli/commands/stop.rb b/lib/cli/commands/stop.rb new file mode 100644 index 0000000..7110a7f --- /dev/null +++ b/lib/cli/commands/stop.rb @@ -0,0 +1,24 @@ +require_relative 'meta/command' +require 'nutella_lib' + +module Nutella + class Stop < Command + @description = 'Stops runs and nutella applications' + + def run(args=nil) + nutella.f.init(Config.file['broker'], 'nutella_cli') + response = nutella.f.net.sync_request( 'commands', { "command": "stop", "params": args} ) + if response['success'] + console.success response['message'] + else + console.error response['message'] + if response['exception'] != nil + console.error response['exception'] + end + end + end + + end +end + + diff --git a/nutella_framework.gemspec b/nutella_framework.gemspec index a64d256..0a79bff 100755 --- a/nutella_framework.gemspec +++ b/nutella_framework.gemspec @@ -30,8 +30,7 @@ Gem::Specification.new do |s| "Rakefile", "VERSION", "bin/nutella", - "lib/bots/binary-files-manager/bin_files_mngr.rb", - "lib/bots/commands_server/Gemfile", + "lib/bots/binary_files_manager/bin_files_mngr.rb", "lib/bots/commands_server/commands/broker.rb", "lib/bots/commands_server/commands/compile.rb", "lib/bots/commands_server/commands/dependencies.rb", @@ -45,53 +44,53 @@ Gem::Specification.new do |s| "lib/bots/commands_server/commands/start.rb", "lib/bots/commands_server/commands/stop.rb", "lib/bots/commands_server/commands/template.rb", - "lib/bots/commands_server/commands_server.rb", - "lib/bots/commands_server/startup", + "lib/bots/commands_server/startup.rb", "lib/bots/commands_server/util/components_list.rb", "lib/bots/main_interface/main_interface_bot.rb", "lib/bots/main_interface/public/index.html", "lib/bots/main_interface/views/index.erb", "lib/bots/main_interface/views/not_found_404.erb", - "lib/bots/room-debugger/README.md", - "lib/bots/room-debugger/css/bootstrap-theme.css", - "lib/bots/room-debugger/css/bootstrap-theme.css.map", - "lib/bots/room-debugger/css/bootstrap-theme.min.css", - "lib/bots/room-debugger/css/bootstrap.css", - "lib/bots/room-debugger/css/bootstrap.css.map", - "lib/bots/room-debugger/css/bootstrap.min.css", - "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.eot", - "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.svg", - "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.ttf", - "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff", - "lib/bots/room-debugger/fonts/glyphicons-halflings-regular.woff2", - "lib/bots/room-debugger/index.html", - "lib/bots/room-debugger/js/bootstrap.js", - "lib/bots/room-debugger/js/bootstrap.min.js", - "lib/bots/room-debugger/js/jquery.min.js", - "lib/bots/room-debugger/js/npm.js", - "lib/bots/room-debugger/js/nutella_lib.js", - "lib/bots/room-debugger/main.css", - "lib/bots/room-debugger/main.js", - "lib/bots/room-debugger/nutella.json", - "lib/bots/room-debugger/package.json", - "lib/bots/room-debugger/room_places_simulator.js", + "lib/bots/room_debugger/README.md", + "lib/bots/room_debugger/css/bootstrap-theme.css", + "lib/bots/room_debugger/css/bootstrap-theme.css.map", + "lib/bots/room_debugger/css/bootstrap-theme.min.css", + "lib/bots/room_debugger/css/bootstrap.css", + "lib/bots/room_debugger/css/bootstrap.css.map", + "lib/bots/room_debugger/css/bootstrap.min.css", + "lib/bots/room_debugger/fonts/glyphicons-halflings-regular.eot", + "lib/bots/room_debugger/fonts/glyphicons-halflings-regular.svg", + "lib/bots/room_debugger/fonts/glyphicons-halflings-regular.ttf", + "lib/bots/room_debugger/fonts/glyphicons-halflings-regular.woff", + "lib/bots/room_debugger/fonts/glyphicons-halflings-regular.woff2", + "lib/bots/room_debugger/index.html", + "lib/bots/room_debugger/js/bootstrap.js", + "lib/bots/room_debugger/js/bootstrap.min.js", + "lib/bots/room_debugger/js/jquery.min.js", + "lib/bots/room_debugger/js/npm.js", + "lib/bots/room_debugger/js/nutella_lib.js", + "lib/bots/room_debugger/main.css", + "lib/bots/room_debugger/main.js", + "lib/bots/room_debugger/nutella.json", + "lib/bots/room_debugger/package.json", + "lib/bots/room_debugger/room_places_simulator.js", "lib/bots/runs_list_bot/runs_list_bot.rb", "lib/cli/cli.rb", "lib/cli/commands/checkup.rb", "lib/cli/commands/help.rb", "lib/cli/commands/meta/command.rb", "lib/cli/commands/server.rb", + "lib/cli/commands/server/framework_bots.rb", + "lib/cli/commands/server/mongo.rb", + "lib/cli/commands/server/mqtt_broker.rb", + "lib/cli/commands/start.rb", + "lib/cli/commands/stop.rb", "lib/cli/logger.rb", "lib/nutella_framework.rb", "lib/templates/index.html", "lib/templates/startup", "lib/util/config.rb", "lib/util/current_app_utils.rb", - "lib/util/framework_components_starter.rb", - "lib/util/mongo.rb", - "lib/util/mqtt_broker.rb", "lib/util/persisted_hash.rb", - "lib/util/pid.rb", "lib/util/runlist.rb", "nutella_framework.gemspec", "spec/cli/commands/checkup_spec.rb", @@ -115,7 +114,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency(%q.freeze, ["~> 3.0"]) s.add_runtime_dependency(%q.freeze, ["~> 1.34"]) s.add_runtime_dependency(%q.freeze, ["~> 1.2"]) - s.add_runtime_dependency(%q.freeze, ["~> 0.5"]) + s.add_runtime_dependency(%q.freeze, ["~> 0.6"]) s.add_runtime_dependency(%q.freeze, ["~> 1.6"]) s.add_runtime_dependency(%q.freeze, ["~> 4.0"]) s.add_runtime_dependency(%q.freeze, ["~> 1.4"]) @@ -131,7 +130,7 @@ Gem::Specification.new do |s| s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 1.34"]) s.add_dependency(%q.freeze, ["~> 1.2"]) - s.add_dependency(%q.freeze, ["~> 0.5"]) + s.add_dependency(%q.freeze, ["~> 0.6"]) s.add_dependency(%q.freeze, ["~> 1.6"]) s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 1.4"]) @@ -148,7 +147,7 @@ Gem::Specification.new do |s| s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 1.34"]) s.add_dependency(%q.freeze, ["~> 1.2"]) - s.add_dependency(%q.freeze, ["~> 0.5"]) + s.add_dependency(%q.freeze, ["~> 0.6"]) s.add_dependency(%q.freeze, ["~> 1.6"]) s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 1.4"]) From 0bb1888a954ed6db8510c6e2c18a7b94e0fc8358 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 10 Nov 2019 15:57:04 -0800 Subject: [PATCH 30/43] Move things around --- lib/bots/commands_server/commands/start.rb | 100 +++++++++++++----- lib/bots/commands_server/startup.rb | 8 +- .../util/current_app_utils.rb | 0 .../commands_server}/util/runlist.rb | 0 lib/cli/commands/checkup.rb | 2 +- lib/cli/commands/server/framework_bots.rb | 54 +--------- lib/cli/commands/server/mongo.rb | 2 +- lib/cli/commands/server/mqtt_broker.rb | 2 +- lib/cli/commands/start.rb | 2 +- lib/cli/commands/stop.rb | 2 +- lib/{util => config}/config.rb | 0 lib/{util => config}/persisted_hash.rb | 0 lib/nutella_framework.rb | 2 +- spec/config/config_spec.rb | 2 +- spec/config/persisted_hash_spec.rb | 2 +- 15 files changed, 84 insertions(+), 94 deletions(-) rename lib/{ => bots/commands_server}/util/current_app_utils.rb (100%) rename lib/{ => bots/commands_server}/util/runlist.rb (100%) rename lib/{util => config}/config.rb (100%) rename lib/{util => config}/persisted_hash.rb (100%) diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index 15ae20a..4bd045c 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -1,44 +1,37 @@ require_relative 'meta/run_command' -# require 'util/components_starter' module Nutella class Start < RunCommand @description = 'Starts all or some of the bots in the current application' def run(args=nil) - if rand < 0.5 - { success: true, message: "This is a success for command Start with args #{args}"} - else - { success: false, message: "This is a failure for command Start with args #{args}"} + # If the current directory is not a nutella application, return + unless Nutella.current_app.exist? + console.warn 'The current directory is not a nutella application' + return end - # # If the current directory is not a nutella application, return - # unless Nutella.current_app.exist? - # console.warn 'The current directory is not a nutella application' - # return - # end - - # begin - # run_id, params = parse_cli_arguments args - # rescue StandardError => e - # console.error e.message - # return - # end + begin + run_id, params = parse_cli_arguments args + rescue StandardError => e + console.error e.message + return + end - # app_id, app_path = fetch_app_details + app_id, app_path = fetch_app_details - # if no_app_bot_to_start app_id, app_path, params - # console.warn "Run #{run} not created: your application bots are already started and you specified no regular bots exclusively for this run" - # return - # end + if no_app_bot_to_start app_id, app_path, params + console.warn "Run #{run} not created: your application bots are already started and you specified no regular bots exclusively for this run" + return + end - # return if run_exist?( app_id, run_id) + return if run_exist?( app_id, run_id) - # return unless start_all_components(app_id, app_path, run_id, params) + return unless start_all_components(app_id, app_path, run_id, params) - # return unless Nutella.runlist.add?(app_id, run_id, app_path) + return unless Nutella.runlist.add?(app_id, run_id, app_path) - # print_confirmation(run_id, params, app_id, app_path) + print_confirmation(run_id, params, app_id, app_path) end @@ -141,5 +134,56 @@ def print_monitoring_details( app_id, run_id ) end - -end \ No newline at end of file +end + +# # Starts the application level bots + # # @return [boolean] true if all bots are started correctly, false otherwise + # def self.start_app_bots( app_id, app_path ) + # app_bots_list = Nutella.current_app.config['app_bots'] + # bots_dir = "#{app_path}/bots/" + # # If app bots have been started already, then do nothing + # unless Nutella::Tmux.session_exist? Nutella::Tmux.app_bot_session_name app_id + # # Start all app bots in the list into a new tmux session + # tmux = Nutella::Tmux.new app_id, nil + # ComponentsList.for_each_component_in_dir bots_dir do |bot| + # unless app_bots_list.nil? || !app_bots_list.include?( bot ) + # # If there is no 'startup' script output a warning (because + # # startup is mandatory) and skip the bot + # unless File.exist?("#{bots_dir}#{bot}/startup") + # console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." + # next + # end + # # Create a new window in the session for this run + # tmux.new_app_bot_window bot + # end + # end + # end + # true + # end + + + # def self.start_run_bots( bots_list, app_path, app_id, run_id ) + # # Create a new tmux instance for this run + # tmux = Nutella::Tmux.new app_id, run_id + # # Fetch bots dir + # bots_dir = "#{app_path}/bots/" + # # Start the appropriate bots + # bots_list.each { |bot| start_run_level_bot(bots_dir, bot, tmux) } + # true + # end + + + # #--- Private class methods -------------- + + # # Starts a run level bot + # def self.start_run_level_bot( bots_dir, bot, tmux ) + # # If there is no 'startup' script output a warning (because + # # startup is mandatory) and skip the bot + # unless File.exist?("#{bots_dir}#{bot}/startup") + # console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." + # return + # end + # # Create a new window in the session for this run + # tmux.new_bot_window bot + # end + # private_class_method :start_run_level_bot \ No newline at end of file diff --git a/lib/bots/commands_server/startup.rb b/lib/bots/commands_server/startup.rb index 35e9321..b25ef9f 100644 --- a/lib/bots/commands_server/startup.rb +++ b/lib/bots/commands_server/startup.rb @@ -22,13 +22,11 @@ # This function executes a particular command # @param command [String] the name of the command -# @param args [Array] command line parameters passed to the command -def execute_command(command, args=nil) - # Check that the command exists and if it does, - # execute its run method passing the args parameters +# @param opts [Hash] parameters passed to the command +def execute_command(command, opts=nil) if command_exists?(command) begin - return Object::const_get("Nutella::#{command.capitalize}").new.run(args) + return Object::const_get("Nutella::#{command.capitalize}").new.run(opts) rescue => e return { success: false, message: "Unexpected failure of command #{command}", exception: e } end diff --git a/lib/util/current_app_utils.rb b/lib/bots/commands_server/util/current_app_utils.rb similarity index 100% rename from lib/util/current_app_utils.rb rename to lib/bots/commands_server/util/current_app_utils.rb diff --git a/lib/util/runlist.rb b/lib/bots/commands_server/util/runlist.rb similarity index 100% rename from lib/util/runlist.rb rename to lib/bots/commands_server/util/runlist.rb diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index 1d9342c..b35e52f 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -1,5 +1,5 @@ require_relative 'meta/command' -require 'util/config' +require 'config/config' require 'semantic' require 'docker-api' diff --git a/lib/cli/commands/server/framework_bots.rb b/lib/cli/commands/server/framework_bots.rb index c2783e6..83a3931 100755 --- a/lib/cli/commands/server/framework_bots.rb +++ b/lib/cli/commands/server/framework_bots.rb @@ -1,6 +1,6 @@ require 'docker-api' require 'socket' -require 'util/config' +require 'config/config' module Nutella class FrameworkBots @@ -89,56 +89,4 @@ def framework_bots end end - # # Starts the application level bots - # # @return [boolean] true if all bots are started correctly, false otherwise - # def self.start_app_bots( app_id, app_path ) - # app_bots_list = Nutella.current_app.config['app_bots'] - # bots_dir = "#{app_path}/bots/" - # # If app bots have been started already, then do nothing - # unless Nutella::Tmux.session_exist? Nutella::Tmux.app_bot_session_name app_id - # # Start all app bots in the list into a new tmux session - # tmux = Nutella::Tmux.new app_id, nil - # ComponentsList.for_each_component_in_dir bots_dir do |bot| - # unless app_bots_list.nil? || !app_bots_list.include?( bot ) - # # If there is no 'startup' script output a warning (because - # # startup is mandatory) and skip the bot - # unless File.exist?("#{bots_dir}#{bot}/startup") - # console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." - # next - # end - # # Create a new window in the session for this run - # tmux.new_app_bot_window bot - # end - # end - # end - # true - # end - - - # def self.start_run_bots( bots_list, app_path, app_id, run_id ) - # # Create a new tmux instance for this run - # tmux = Nutella::Tmux.new app_id, run_id - # # Fetch bots dir - # bots_dir = "#{app_path}/bots/" - # # Start the appropriate bots - # bots_list.each { |bot| start_run_level_bot(bots_dir, bot, tmux) } - # true - # end - - - # #--- Private class methods -------------- - - # # Starts a run level bot - # def self.start_run_level_bot( bots_dir, bot, tmux ) - # # If there is no 'startup' script output a warning (because - # # startup is mandatory) and skip the bot - # unless File.exist?("#{bots_dir}#{bot}/startup") - # console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." - # return - # end - # # Create a new window in the session for this run - # tmux.new_bot_window bot - # end - # private_class_method :start_run_level_bot - diff --git a/lib/cli/commands/server/mongo.rb b/lib/cli/commands/server/mongo.rb index 7221306..47595d0 100644 --- a/lib/cli/commands/server/mongo.rb +++ b/lib/cli/commands/server/mongo.rb @@ -1,6 +1,6 @@ require 'docker-api' require 'socket' -require 'util/config' +require 'config/config' module Nutella class Mongo diff --git a/lib/cli/commands/server/mqtt_broker.rb b/lib/cli/commands/server/mqtt_broker.rb index f95d82c..0c22315 100644 --- a/lib/cli/commands/server/mqtt_broker.rb +++ b/lib/cli/commands/server/mqtt_broker.rb @@ -1,6 +1,6 @@ require 'docker-api' require 'socket' -require 'util/config' +require 'config/config' module Nutella class MQTTBroker diff --git a/lib/cli/commands/start.rb b/lib/cli/commands/start.rb index f4d2dae..5f1a610 100644 --- a/lib/cli/commands/start.rb +++ b/lib/cli/commands/start.rb @@ -7,7 +7,7 @@ class Start < Command def run(args=nil) nutella.f.init(Config.file['broker'], 'nutella_cli') - response = nutella.f.net.sync_request( 'commands', { "command": "start", "params": args} ) + response = nutella.f.net.sync_request( 'commands', { 'command': 'start', 'opts': {'current_dir': Dir.pwd, 'args': args}} ) if response['success'] console.success response['message'] else diff --git a/lib/cli/commands/stop.rb b/lib/cli/commands/stop.rb index 7110a7f..57100cf 100644 --- a/lib/cli/commands/stop.rb +++ b/lib/cli/commands/stop.rb @@ -7,7 +7,7 @@ class Stop < Command def run(args=nil) nutella.f.init(Config.file['broker'], 'nutella_cli') - response = nutella.f.net.sync_request( 'commands', { "command": "stop", "params": args} ) + response = nutella.f.net.sync_request( 'commands', { 'command': 'start', 'opts': {'current_dir': Dir.pwd, 'args': args}} ) if response['success'] console.success response['message'] else diff --git a/lib/util/config.rb b/lib/config/config.rb similarity index 100% rename from lib/util/config.rb rename to lib/config/config.rb diff --git a/lib/util/persisted_hash.rb b/lib/config/persisted_hash.rb similarity index 100% rename from lib/util/persisted_hash.rb rename to lib/config/persisted_hash.rb diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index 8382737..443f046 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -1,4 +1,4 @@ -require 'util/config' +require 'config/config' require 'cli/cli' module Nutella diff --git a/spec/config/config_spec.rb b/spec/config/config_spec.rb index 2cd63cd..772d231 100644 --- a/spec/config/config_spec.rb +++ b/spec/config/config_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -require 'util/config' +require 'config/config' require 'securerandom' module Nutella diff --git a/spec/config/persisted_hash_spec.rb b/spec/config/persisted_hash_spec.rb index 711838e..942699e 100644 --- a/spec/config/persisted_hash_spec.rb +++ b/spec/config/persisted_hash_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -require 'util/persisted_hash' +require 'config/persisted_hash' require 'securerandom' module Nutella From 4295e0a80e10c345c8292ff13846f61601775c1f Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 10 Nov 2019 16:46:10 -0800 Subject: [PATCH 31/43] Implemented command new --- Gemfile | 1 - .../commands_server/commands/meta/command.rb | 12 +++++ .../commands/meta/run_command.rb | 34 ++----------- lib/bots/commands_server/commands/new.rb | 50 ++++++++----------- lib/bots/commands_server/commands/start.rb | 42 ++++++++++++---- lib/bots/commands_server/commands/stop.rb | 2 +- lib/bots/commands_server/startup.rb | 3 +- lib/cli/commands/new.rb | 24 +++++++++ nutella_framework.gemspec | 14 +++--- 9 files changed, 102 insertions(+), 80 deletions(-) create mode 100644 lib/cli/commands/new.rb diff --git a/Gemfile b/Gemfile index aafbf63..37e0cbe 100755 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,6 @@ gem 'docker-api', '~> 1.34' gem 'git', '~> 1.2' gem 'nutella_lib','~>0.6' gem 'nokogiri', '~>1.6' -gem 'slop', '~>4.0' gem 'semantic', '~> 1.4' gem 'sinatra', '~>1.4' gem 'sinatra-cross_origin', '~> 0.3.2' diff --git a/lib/bots/commands_server/commands/meta/command.rb b/lib/bots/commands_server/commands/meta/command.rb index 4d3f047..0c0126d 100755 --- a/lib/bots/commands_server/commands/meta/command.rb +++ b/lib/bots/commands_server/commands/meta/command.rb @@ -12,6 +12,18 @@ def run( args=nil ) console.error 'Running the generic command!!! WAT? https://www.destroyallsoftware.com/talks/wat' end + def success(message) + { success: true, message: message } + end + + def failure(message, exception=nil) + if exception.nil? + { success: false, message: message } + else + { success: false, message: message, exception: exception } + end + end + end end diff --git a/lib/bots/commands_server/commands/meta/run_command.rb b/lib/bots/commands_server/commands/meta/run_command.rb index bc5bb78..412241d 100755 --- a/lib/bots/commands_server/commands/meta/run_command.rb +++ b/lib/bots/commands_server/commands/meta/run_command.rb @@ -1,6 +1,5 @@ require_relative 'command' require_relative '../../util/components_list' -require 'slop' module Nutella # This class describes a run command which can be either start or stop. @@ -11,46 +10,21 @@ def run(args=nil) console.error 'Running the generic RunCommand!!! WAT? https://www.destroyallsoftware.com/talks/wat' end - # Extracts the run_id from the parameters passed to the command line # @param [Array] args command line arguments passed to the command # @return [String, String ] the run_id - def parse_run_id_from( args ) + def parse_run_id_from_args( args ) # Simple `nutella start/stop` (no args) if args.nil? || args.empty? return 'default' end - # If the first argument is a parameter, set the run_id to default - if args[0].start_with? '-' - run_id = 'default' - else - # If the first argument is a run_id, check that it's not 'default' and return it - # and shift the arguments so we are left with only the parameters in args - run_id = args[0] - raise StandardError.new 'Unfortunately you can\'t use `default` as a run_id because it is reserved :(' if run_id=='default' - args.shift - end + # Check that the run_id is not 'default' and return it + run_id = args[0] + raise StandardError.new 'Unfortunately you can\'t use `default` as a run_id because it is reserved :(' if run_id=='default' run_id end - # Parse the command line parameters - # @param [Array] args command line arguments passed to the command - # @return [Hash] an hash containing the parameters - def parse_cli_parameters( args ) - begin - opts = Slop::Options.new - opts.array '-wo', '--without', 'A list of components NOT to start' - opts.array '-w', '--with', 'A list of components that needs to be started' - parser = Slop::Parser.new(opts) - result = parser.parse(args) - result.to_hash - rescue - raise StandardError.new 'The only supported parameters are --with (-w) and --without (-wo)' - end - end - - # Prints a success message if the command completes correctly def print_success_message(app_id, run_id, action) if run_id == 'default' diff --git a/lib/bots/commands_server/commands/new.rb b/lib/bots/commands_server/commands/new.rb index 5663b9f..97ea931 100755 --- a/lib/bots/commands_server/commands/new.rb +++ b/lib/bots/commands_server/commands/new.rb @@ -5,53 +5,47 @@ module Nutella class New < Command @description = 'Creates a new nutella application' - def run(args=nil) - app_id = args[0] - - # If no other arguments, show help and quit here - if args.empty? - console.warn 'You need to specify a name for your new application' - return + def run(opts=nil) + # If no app name is provided, return an error + if opts['args'].empty? + return failure('You need to specify a name for your new application') end - - # Check that a directory (i.e. an app) with the same name doesn't already exist - # If it does it looks into it to see if there is a nutella.json file and displays + # Parse the name of the app from the CLI parameters + app_id = opts['args'][0] + # Checks that a directory (i.e. an app) with the same name doesn't already exist + # If it does, we looks into it to see if there is a nutella.json file and display # the proper error message - if File.directory? app_id - if File.exist? "#{app_id}/nutella.json" - console.warn "An application named #{app_id} already exists" - return + app_dir = "#{opts['current_dir']}/#{app_id}" + puts app_dir + if File.directory? app_dir + if File.exist? "#{app_dir}/nutella.json" + return failure("An application named #{app_id} already exists") else - console.warn "A directory named #{app_id} already exists" - return + return failure("A directory named #{app_id} already exists") end end - # If all seems good, generate the application skeleton - create_dir_structure app_id - - # Display a nice success message and return - console.success "Your new nutella application #{app_id} is ready!" + create_app(app_id, app_dir) + # Display a success message and return + return success("Your new nutella application #{app_id} is ready!") end - private - - def create_dir_structure( app_id ) + def create_app( app_id, app_dir ) # Create directories - FileUtils.mkdir_p("#{app_id}/bots") - FileUtils.mkdir_p("#{app_id}/interfaces") + FileUtils.mkdir_p("#{app_dir}/bots") + FileUtils.mkdir_p("#{app_dir}/interfaces") # Create nutella.json hash config_file_hash = { :name => app_id, :version => '0.1.0', - :nutella_version => File.open("#{Nutella::NUTELLA_SRC}VERSION", 'rb').read, + # TODO figure out how to do this :nutella_version => File.open("#{Nutella::NUTELLA_SRC}VERSION", 'rb').read, :type => 'application', :description => 'A quick description of your application' } # Write nutella.json hash - File.open("#{app_id}/nutella.json", 'w') do |f| + File.open("#{app_dir}/nutella.json", 'w') do |f| f.write(JSON.pretty_generate(config_file_hash)) end end diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index 4bd045c..4ae4a45 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -4,18 +4,16 @@ module Nutella class Start < RunCommand @description = 'Starts all or some of the bots in the current application' - def run(args=nil) - # If the current directory is not a nutella application, return - unless Nutella.current_app.exist? - console.warn 'The current directory is not a nutella application' - return + def run(opts=nil) + # If opts['current_dir'] is not a nutella application, return and error + unless is_nutella_app?(opts['current_dir']) + return failure('The current directory is not a nutella application') end - + # If there is an error parsing the run_id, return an error begin - run_id, params = parse_cli_arguments args + run_id = parse_run_id_from_args(opts['args']) rescue StandardError => e - console.error e.message - return + return failure(e.message) end app_id, app_path = fetch_app_details @@ -35,14 +33,36 @@ def run(args=nil) end - private + # Checks that the provided directory is actually a nutella application + # @return [Boolean] true if the directory is a nutella application, false otherwise + def is_nutella_app?(dir) + nutella_json_file_path = "#{dir}/nutella.json" + # Check that there is a nutella.json file in the main directory of the application + if !File.exist? nutella_json_file_path + return false + end + # If there is a file, try to parse it + begin + conf = JSON.parse( IO.read(nutella_json_file_path) ) + rescue + # Not valid JSON, returning false + return false + end + # No nutella version in the file, return false + if conf['nutella_version'].nil? + return false + end + true + end + + # Parses command line arguments def parse_cli_arguments( args ) # Parse run_id - run_id = parse_run_id_from args + run_id = parse_run_id_from_args(args) # Extract parameters params = parse_cli_parameters args # Check that we are not using 'with' and 'without' options at the same time diff --git a/lib/bots/commands_server/commands/stop.rb b/lib/bots/commands_server/commands/stop.rb index a64be00..82d1e83 100755 --- a/lib/bots/commands_server/commands/stop.rb +++ b/lib/bots/commands_server/commands/stop.rb @@ -4,7 +4,7 @@ module Nutella class Stop < RunCommand @description = 'Stops all the bots in the current application' - def run(args=nil) + def run(opts=nil) # If the current directory is not a nutella application, return unless Nutella.current_app.exist? diff --git a/lib/bots/commands_server/startup.rb b/lib/bots/commands_server/startup.rb index b25ef9f..8f155bd 100644 --- a/lib/bots/commands_server/startup.rb +++ b/lib/bots/commands_server/startup.rb @@ -17,7 +17,7 @@ # Commands handler nutella.f.net.handle_requests('commands', lambda do |message, component_id| nutella.log.info("[#{Time.now}] Command received: #{message}") - execute_command(message['command'], message['params']) + execute_command(message['command'], message['opts']) end) # This function executes a particular command @@ -28,6 +28,7 @@ def execute_command(command, opts=nil) begin return Object::const_get("Nutella::#{command.capitalize}").new.run(opts) rescue => e + puts e.backtrace return { success: false, message: "Unexpected failure of command #{command}", exception: e } end else diff --git a/lib/cli/commands/new.rb b/lib/cli/commands/new.rb new file mode 100644 index 0000000..6e2b7ed --- /dev/null +++ b/lib/cli/commands/new.rb @@ -0,0 +1,24 @@ +require_relative 'meta/command' +require 'nutella_lib' + +module Nutella + class New < Command + @description = 'Creates a new nutella application' + + def run(args=nil) + nutella.f.init(Config.file['broker'], 'nutella_cli') + response = nutella.f.net.sync_request( 'commands', { 'command': 'new', 'opts': {'current_dir': Dir.pwd, 'args': args}} ) + if response['success'] + console.success response['message'] + else + console.error response['message'] + if response['exception'] != nil + console.error response['exception'] + end + end + end + + end +end + + diff --git a/nutella_framework.gemspec b/nutella_framework.gemspec index 0a79bff..9fc43b6 100755 --- a/nutella_framework.gemspec +++ b/nutella_framework.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Alessandro Gnoli".freeze] - s.date = "2019-11-10" + s.date = "2019-11-11" s.description = "utella is a framework to create and run RoomApps".freeze s.email = "tebemis@gmail.com".freeze s.executables = ["nutella".freeze] @@ -46,6 +46,8 @@ Gem::Specification.new do |s| "lib/bots/commands_server/commands/template.rb", "lib/bots/commands_server/startup.rb", "lib/bots/commands_server/util/components_list.rb", + "lib/bots/commands_server/util/current_app_utils.rb", + "lib/bots/commands_server/util/runlist.rb", "lib/bots/main_interface/main_interface_bot.rb", "lib/bots/main_interface/public/index.html", "lib/bots/main_interface/views/index.erb", @@ -78,6 +80,7 @@ Gem::Specification.new do |s| "lib/cli/commands/checkup.rb", "lib/cli/commands/help.rb", "lib/cli/commands/meta/command.rb", + "lib/cli/commands/new.rb", "lib/cli/commands/server.rb", "lib/cli/commands/server/framework_bots.rb", "lib/cli/commands/server/mongo.rb", @@ -85,13 +88,11 @@ Gem::Specification.new do |s| "lib/cli/commands/start.rb", "lib/cli/commands/stop.rb", "lib/cli/logger.rb", + "lib/config/config.rb", + "lib/config/persisted_hash.rb", "lib/nutella_framework.rb", "lib/templates/index.html", "lib/templates/startup", - "lib/util/config.rb", - "lib/util/current_app_utils.rb", - "lib/util/persisted_hash.rb", - "lib/util/runlist.rb", "nutella_framework.gemspec", "spec/cli/commands/checkup_spec.rb", "spec/cli/commands/help_spec.rb", @@ -116,7 +117,6 @@ Gem::Specification.new do |s| s.add_runtime_dependency(%q.freeze, ["~> 1.2"]) s.add_runtime_dependency(%q.freeze, ["~> 0.6"]) s.add_runtime_dependency(%q.freeze, ["~> 1.6"]) - s.add_runtime_dependency(%q.freeze, ["~> 4.0"]) s.add_runtime_dependency(%q.freeze, ["~> 1.4"]) s.add_runtime_dependency(%q.freeze, ["~> 1.4"]) s.add_runtime_dependency(%q.freeze, ["~> 0.3.2"]) @@ -132,7 +132,6 @@ Gem::Specification.new do |s| s.add_dependency(%q.freeze, ["~> 1.2"]) s.add_dependency(%q.freeze, ["~> 0.6"]) s.add_dependency(%q.freeze, ["~> 1.6"]) - s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 0.3.2"]) @@ -149,7 +148,6 @@ Gem::Specification.new do |s| s.add_dependency(%q.freeze, ["~> 1.2"]) s.add_dependency(%q.freeze, ["~> 0.6"]) s.add_dependency(%q.freeze, ["~> 1.6"]) - s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 1.4"]) s.add_dependency(%q.freeze, ["~> 0.3.2"]) From c8482cd10c48766bbf55f8b23deaad9e5a6f3784 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 10 Nov 2019 17:15:03 -0800 Subject: [PATCH 32/43] Stopping for the day --- lib/bots/commands_server/commands/new.rb | 12 +++--- lib/bots/commands_server/commands/start.rb | 48 ++++++++-------------- pippa/nutella.json | 7 ++++ 3 files changed, 30 insertions(+), 37 deletions(-) create mode 100644 pippa/nutella.json diff --git a/lib/bots/commands_server/commands/new.rb b/lib/bots/commands_server/commands/new.rb index 97ea931..9b90b4e 100755 --- a/lib/bots/commands_server/commands/new.rb +++ b/lib/bots/commands_server/commands/new.rb @@ -38,11 +38,13 @@ def create_app( app_id, app_dir ) FileUtils.mkdir_p("#{app_dir}/interfaces") # Create nutella.json hash config_file_hash = { - :name => app_id, - :version => '0.1.0', - # TODO figure out how to do this :nutella_version => File.open("#{Nutella::NUTELLA_SRC}VERSION", 'rb').read, - :type => 'application', - :description => 'A quick description of your application' + name: app_id, + version: '0.1.0', + # TODO figure out how to do make this dynamic + # :nutella_version => File.open("#{Nutella::NUTELLA_SRC}VERSION", 'rb').read, + nutella_version: '2.0.0', + type: 'application', + description: 'A quick description of your application' } # Write nutella.json hash File.open("#{app_dir}/nutella.json", 'w') do |f| diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index 4ae4a45..d3fe2d2 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -9,26 +9,26 @@ def run(opts=nil) unless is_nutella_app?(opts['current_dir']) return failure('The current directory is not a nutella application') end + # Extract app path and id (i.e. name) + app_path = opts['current_dir'] + app_id = app_config(app_path)['name'] # If there is an error parsing the run_id, return an error begin run_id = parse_run_id_from_args(opts['args']) rescue StandardError => e return failure(e.message) end - - app_id, app_path = fetch_app_details - - if no_app_bot_to_start app_id, app_path, params - console.warn "Run #{run} not created: your application bots are already started and you specified no regular bots exclusively for this run" - return + # TODO resume from here + # Check if there are actully bots that need to be started... + if app_bots_running_already(app_id) + return success("Run #{run} not created: your app bots are running already and your application has no run bots)" end - - return if run_exist?( app_id, run_id) - + if run_exist?( app_id, run_id) + return failure("Impossible to start nutella app: an instance of this app with the same run_id is already running!\nYou might want to kill it with 'nutella stop #{run_id}'") + end + # Start bots return unless start_all_components(app_id, app_path, run_id, params) - return unless Nutella.runlist.add?(app_id, run_id, app_path) - print_confirmation(run_id, params, app_id, app_path) end @@ -58,29 +58,13 @@ def is_nutella_app?(dir) true end - - # Parses command line arguments - def parse_cli_arguments( args ) - # Parse run_id - run_id = parse_run_id_from_args(args) - # Extract parameters - params = parse_cli_parameters args - # Check that we are not using 'with' and 'without' options at the same time - unless params[:with].empty? || params[:without].empty? - raise StandardError.new 'You can\'t use both --with and --without at the same time' - end - return run_id, params + # Builds a PersistedHash of the application nutella.json file and returns it. + # This method is used to ease access to the app nutella.json file. + # @return [PersistedHash] the PersistedHash of the app nutella.json file + def app_config(dir) + PersistedHash.new("#{dir}/nutella.json") end - - # Fetches the app_id and app_path - def fetch_app_details - # Extract app_id - app_id = Nutella.current_app.config['name'] - return app_id, Dir.pwd - end - - # Returns true if both the list of run level bots is empty and the app bots # have been started already def no_app_bot_to_start(app_id, app_path, params) diff --git a/pippa/nutella.json b/pippa/nutella.json new file mode 100644 index 0000000..533eaa6 --- /dev/null +++ b/pippa/nutella.json @@ -0,0 +1,7 @@ +{ + "name": "pippa", + "version": "0.1.0", + "nutella_version": "2.0.0", + "type": "application", + "description": "A quick description of your application" +} \ No newline at end of file From c9e488b4231292073957049502ab283829d8f9bd Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 10 Nov 2019 17:39:25 -0800 Subject: [PATCH 33/43] Remove test nutella app --- pippa/nutella.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 pippa/nutella.json diff --git a/pippa/nutella.json b/pippa/nutella.json deleted file mode 100644 index 533eaa6..0000000 --- a/pippa/nutella.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "pippa", - "version": "0.1.0", - "nutella_version": "2.0.0", - "type": "application", - "description": "A quick description of your application" -} \ No newline at end of file From 2cb88df5cf0f9e44c279bd467f118e83d98d9806 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 24 Nov 2019 18:50:17 -0800 Subject: [PATCH 34/43] Runlist spec and Start command spec --- lib/bots/commands_server/commands/broker.rb | 23 ++- lib/bots/commands_server/commands/compile.rb | 11 +- .../commands_server/commands/dependencies.rb | 11 +- lib/bots/commands_server/commands/install.rb | 126 ++++++------- .../commands_server/commands/meta/command.rb | 22 +-- .../commands/meta/run_command.rb | 41 +++-- .../commands/meta/template_command.rb | 27 ++- lib/bots/commands_server/commands/new.rb | 22 ++- lib/bots/commands_server/commands/reset.rb | 15 +- lib/bots/commands_server/commands/runs.rb | 23 +-- lib/bots/commands_server/commands/start.rb | 171 +++++++++--------- lib/bots/commands_server/commands/stop.rb | 31 ++-- lib/bots/commands_server/commands/template.rb | 62 +++---- .../commands_server/util/components_list.rb | 52 +++--- .../commands_server/util/current_app_utils.rb | 27 ++- .../util => config}/runlist.rb | 72 ++------ spec/bots/commands_server/start_spec.rb | 35 ++++ spec/config/runlist_spec.rb | 48 +++++ 18 files changed, 409 insertions(+), 410 deletions(-) rename lib/{bots/commands_server/util => config}/runlist.rb (72%) create mode 100644 spec/bots/commands_server/start_spec.rb create mode 100644 spec/config/runlist_spec.rb diff --git a/lib/bots/commands_server/commands/broker.rb b/lib/bots/commands_server/commands/broker.rb index 16ffec9..8b71882 100755 --- a/lib/bots/commands_server/commands/broker.rb +++ b/lib/bots/commands_server/commands/broker.rb @@ -1,29 +1,30 @@ +# frozen_string_literal: true + require_relative 'meta/command' require 'socket' -module Nutella +module CommandsServer class Broker < Command @description = 'Displays information about the broker and allows to change it' - - def run(args=nil) + def run(args = nil) # If no argument then we just display info about the broker - if args==nil || args.empty? + if args.nil? || args.empty? print_broker_info return end # If there are arguments we are doing manipulations sub_command = args[0] sum_command_param = args[1] - if sub_command=='set' + if sub_command == 'set' change_broker sum_command_param else console.warn "Unknown 'nutella broker' option #{sub_command}. Try 'nutella broker' or 'nutella broker set ' instead" end end - + private - + def print_broker_info if Nutella.config['broker'].nil? console.warn 'No broker has been specified yet. Please, run `nutella broker set ` to specify a broker' @@ -31,9 +32,8 @@ def print_broker_info console.info "Currently using broker: #{Nutella.config['broker']}" end end - - - def change_broker( new_broker ) + + def change_broker(new_broker) # Check that there are no runs hinging on this broker unless Nutella.runlist.empty? console.warn 'You are currently running some nutella applications on this broker. You can\'t change the broker while running.' @@ -42,7 +42,7 @@ def change_broker( new_broker ) # Try to parse the hostname and switch to the new broker begin IPSocket.getaddress new_broker - rescue + rescue StandardError console.warn "#{new_broker} is not a valid hostname for a broker" return end @@ -50,6 +50,5 @@ def change_broker( new_broker ) # Print a confirmation message console.success "Now using broker: #{Nutella.config['broker']}" end - end end diff --git a/lib/bots/commands_server/commands/compile.rb b/lib/bots/commands_server/commands/compile.rb index 9cc1070..1928524 100755 --- a/lib/bots/commands_server/commands/compile.rb +++ b/lib/bots/commands_server/commands/compile.rb @@ -1,12 +1,13 @@ +# frozen_string_literal: true + require_relative 'meta/run_command' -module Nutella +module CommandsServer class Compile < RunCommand @description = 'Compiles all the components that need compilation in the application' - - def run(args=nil) + + def run(_args = nil) compile_and_dependencies 'compile', 'Compiling', 'components compiled' end - end -end \ No newline at end of file +end diff --git a/lib/bots/commands_server/commands/dependencies.rb b/lib/bots/commands_server/commands/dependencies.rb index fd7b5dd..b30fb4c 100755 --- a/lib/bots/commands_server/commands/dependencies.rb +++ b/lib/bots/commands_server/commands/dependencies.rb @@ -1,12 +1,13 @@ +# frozen_string_literal: true + require_relative 'meta/run_command' -module Nutella +module CommandsServer class Dependencies < RunCommand @description = 'Installs the dependencies for all components in the application' - - def run(args=nil) + + def run(_args = nil) compile_and_dependencies 'dependencies', 'Installing dependencies for', 'dependencies installed' end - end -end \ No newline at end of file +end diff --git a/lib/bots/commands_server/commands/install.rb b/lib/bots/commands_server/commands/install.rb index 88e86a9..90bfd44 100755 --- a/lib/bots/commands_server/commands/install.rb +++ b/lib/bots/commands_server/commands/install.rb @@ -1,96 +1,90 @@ +# frozen_string_literal: true + require_relative 'meta/template_command' require 'git' require 'net/http' -module Nutella - +module CommandsServer class Install < TemplateCommand @description = 'Copies an arbitrary template (from central DB, directory or URL) into the current application' - - def run(args=nil) + def run(args = nil) # If the current directory is not a nutella application, return unless Nutella.current_app.exist? console.warn 'The current directory is not a nutella application' return end - + # Check args if args.empty? console.warn 'You need to specify a template name, directory or URL' return end template = args[0] - destination_dir = args.length==2 ? args[1] : nil - + destination_dir = args.length == 2 ? args[1] : nil + # Extract application directory - app_path =Dir.pwd + app_path = Dir.pwd # What kind of template are we handling? if is_template_a_local_dir? template - add_local_template( template, template, app_path, destination_dir ) + add_local_template(template, template, app_path, destination_dir) elsif is_template_a_git_repo? template - add_remote_template( template, app_path, destination_dir) + add_remote_template(template, app_path, destination_dir) elsif is_template_in_db? template - add_central_template( template, app_path, destination_dir) + add_central_template(template, app_path, destination_dir) else console.warn 'The specified template is not a valid nutella template' end - end - - + private - - - def is_template_a_local_dir?( template_path ) + + def is_template_a_local_dir?(template_path) # Does the specified directory exist? return false unless File.directory? template_path + validate_template template_path end - - - def is_template_a_git_repo?( template_git_url ) - return false unless template_git_url =~ /\A#{URI::regexp(['http', 'https'])}\z/ + + def is_template_a_git_repo?(template_git_url) + unless template_git_url =~ /\A#{URI.regexp(%w[http https])}\z/ + return false + end + begin if template_git_url.end_with? '.git' - tmp_dest_dir = template_git_url[template_git_url.rindex('/')+1 .. template_git_url.length-5] + tmp_dest_dir = template_git_url[template_git_url.rindex('/') + 1..template_git_url.length - 5] else - tmp_dest_dir = template_git_url[template_git_url.rindex('/')+1 .. template_git_url.length] + tmp_dest_dir = template_git_url[template_git_url.rindex('/') + 1..template_git_url.length] end - clone_template_from_repo_to( template_git_url, tmp_dest_dir ) + clone_template_from_repo_to(template_git_url, tmp_dest_dir) return validate_template "#{Nutella::NUTELLA_TMP}/#{tmp_dest_dir}" - rescue - return false - end - end - - - def is_template_in_db?( template_name ) - begin - repo = extract_remote_repo_url template_name - return is_template_a_git_repo? repo - rescue + rescue StandardError return false end end + def is_template_in_db?(template_name) + repo = extract_remote_repo_url template_name + is_template_a_git_repo? repo + rescue StandardError + false + end - def add_local_template( template, template_dir, prj_dir, dest_dir_name) + def add_local_template(template, template_dir, prj_dir, dest_dir_name) template_nutella_file_json = JSON.parse(IO.read("#{template_dir}/nutella.json")) - + # If destination is not specified, set it to the template name - if dest_dir_name.nil? - dest_dir_name = template_nutella_file_json['name'] - end + dest_dir_name = template_nutella_file_json['name'] if dest_dir_name.nil? # Am I trying to copy onto a template that already exists? - if template_nutella_file_json['type']=='bot' - # Look into bots folder - dest_dir = "#{prj_dir}/bots/#{dest_dir_name}" - else - # Look into interfaces folder - dest_dir = "#{prj_dir}/interfaces/#{dest_dir_name}" - end + dest_dir = if template_nutella_file_json['type'] == 'bot' + # Look into bots folder + "#{prj_dir}/bots/#{dest_dir_name}" + else + # Look into interfaces folder + "#{prj_dir}/interfaces/#{dest_dir_name}" + end if File.directory?(dest_dir) console.error("Destination folder #{dest_dir} already exists! Can't add template #{template}") return @@ -100,46 +94,40 @@ def add_local_template( template, template_dir, prj_dir, dest_dir_name) FileUtils.copy_entry template_dir, dest_dir # ... and remove nutella.json and .git folder if they exist - File.delete "#{dest_dir}/nutella.json" if File.exist? "#{dest_dir}/nutella.json" + if File.exist? "#{dest_dir}/nutella.json" + File.delete "#{dest_dir}/nutella.json" + end FileUtils.rm_rf "#{dest_dir}/.git" # Make the user feel happy and accomplished! :) console.success("Installed template: #{template} as #{dest_dir_name}") end - - - def add_remote_template( template, prj_dir, dest_dir) - template_name = template[template.rindex('/')+1 .. template.length-5] + + def add_remote_template(template, prj_dir, dest_dir) + template_name = template[template.rindex('/') + 1..template.length - 5] template_dir = "#{Nutella::NUTELLA_TMP}/#{template_name}" - add_local_template( template, template_dir, prj_dir, dest_dir ) + add_local_template(template, template_dir, prj_dir, dest_dir) end - - - def add_central_template( template_name, prj_dir, dest_dir) + + def add_central_template(template_name, prj_dir, dest_dir) template_dir = "#{Nutella::NUTELLA_TMP}/#{template_name}" - add_local_template( template_name, template_dir, prj_dir, dest_dir ) + add_local_template(template_name, template_dir, prj_dir, dest_dir) end - - + def clone_template_from_repo_to(template, dest_dir) clean_tmp_dir - Dir.mkdir Nutella::NUTELLA_TMP unless Dir.exists? Nutella::NUTELLA_TMP - Git.clone(template, dest_dir, :path => Nutella::NUTELLA_TMP) + Dir.mkdir Nutella::NUTELLA_TMP unless Dir.exist? Nutella::NUTELLA_TMP + Git.clone(template, dest_dir, path: Nutella::NUTELLA_TMP) end - - def extract_remote_repo_url( template_name ) + def extract_remote_repo_url(template_name) uri = URI.parse "https://raw.githubusercontent.com/nutella-framework/nutella_framework/templates-database/#{template_name}.json" nutella_json = JSON.parse Net::HTTP.get uri nutella_json['repo'] end - - + def clean_tmp_dir - FileUtils.rm_rf "#{Nutella::NUTELLA_TMP}" + FileUtils.rm_rf Nutella::NUTELLA_TMP.to_s end - end - end - diff --git a/lib/bots/commands_server/commands/meta/command.rb b/lib/bots/commands_server/commands/meta/command.rb index 0c0126d..e4a03c3 100755 --- a/lib/bots/commands_server/commands/meta/command.rb +++ b/lib/bots/commands_server/commands/meta/command.rb @@ -1,29 +1,27 @@ -module Nutella +# frozen_string_literal: true +module CommandsServer # Nutella command - class Command - - class << self; + class Command + class << self attr_accessor :description end - + # Commands overload this method to execute - def run( args=nil ) + def run(_args = nil) console.error 'Running the generic command!!! WAT? https://www.destroyallsoftware.com/talks/wat' end def success(message) - { success: true, message: message } + { success: true, message: message, message_level: 'success' } end - def failure(message, exception=nil) + def failure(message, level, exception = nil) if exception.nil? - { success: false, message: message } + { success: false, message: message, message_level: level } else - { success: false, message: message, exception: exception } + { success: false, message: message, message_level: level, exception: exception } end end - end - end diff --git a/lib/bots/commands_server/commands/meta/run_command.rb b/lib/bots/commands_server/commands/meta/run_command.rb index 412241d..9c12a79 100755 --- a/lib/bots/commands_server/commands/meta/run_command.rb +++ b/lib/bots/commands_server/commands/meta/run_command.rb @@ -1,30 +1,32 @@ +# frozen_string_literal: true + require_relative 'command' require_relative '../../util/components_list' -module Nutella +module CommandsServer # This class describes a run command which can be either start or stop. # It is mostly a commodity class for code reuse. class RunCommand < Command - - def run(args=nil) + def run(_args = nil) console.error 'Running the generic RunCommand!!! WAT? https://www.destroyallsoftware.com/talks/wat' end # Extracts the run_id from the parameters passed to the command line # @param [Array] args command line arguments passed to the command # @return [String, String ] the run_id - def parse_run_id_from_args( args ) + def parse_run_id_from_args(args) # Simple `nutella start/stop` (no args) - if args.nil? || args.empty? - return 'default' - end + return 'default' if args.nil? || args.empty? + # Check that the run_id is not 'default' and return it run_id = args[0] - raise StandardError.new 'Unfortunately you can\'t use `default` as a run_id because it is reserved :(' if run_id=='default' + if run_id == 'default' + raise StandardError, 'Unfortunately you can\'t use `default` as a run_id because it is reserved :(' + end + run_id end - # Prints a success message if the command completes correctly def print_success_message(app_id, run_id, action) if run_id == 'default' @@ -34,31 +36,34 @@ def print_success_message(app_id, run_id, action) end end - - def compile_and_dependencies( script , in_progress_message, complete_message) + def compile_and_dependencies(script, in_progress_message, complete_message) # If the current directory is not a nutella application, return unless Nutella.current_app.exist? console.warn 'The current directory is not a nutella application' return end # Run script for all bots - return unless run_script_for_components_in( "#{Dir.pwd}/bots", script, in_progress_message ) + unless run_script_for_components_in("#{Dir.pwd}/bots", script, in_progress_message) + return + end # Run script for all interfaces - return unless run_script_for_components_in( "#{Dir.pwd}/interfaces", script, in_progress_message ) + unless run_script_for_components_in("#{Dir.pwd}/interfaces", script, in_progress_message) + return + end + # Output success message console.success "All #{complete_message} for #{Nutella.current_app.config['name']}" end - private - # Runs a script for each component in a certain directory. # Message is displayed in case something goes wrong - def run_script_for_components_in( dir, script, message ) + def run_script_for_components_in(dir, script, message) ComponentsList.for_each_component_in_dir dir do |component| # Skip component if there is no script next unless File.exist? "#{dir}/#{component}/#{script}" + # Output message console.info "#{message} #{component}." # Execute 'script' script @@ -69,7 +74,5 @@ def run_script_for_components_in( dir, script, message ) end true end - end - -end \ No newline at end of file +end diff --git a/lib/bots/commands_server/commands/meta/template_command.rb b/lib/bots/commands_server/commands/meta/template_command.rb index cd580fb..14d03c2 100755 --- a/lib/bots/commands_server/commands/meta/template_command.rb +++ b/lib/bots/commands_server/commands/meta/template_command.rb @@ -1,46 +1,43 @@ +# frozen_string_literal: true + require_relative 'command' require 'json' -module Nutella +module CommandsServer # This class describes a template command which can be either install or template # It is mostly a commodity class for code reuse. class TemplateCommand < Command - - def run(args=nil) + def run(_args = nil) console.error 'Running generic TemplateCommand!!! WAT?' end - # Validates a template in a certain folder # @param [String] dir the directory where the template is stored - def validate_template( dir ) + def validate_template(dir) # Parse and validate the template's nutella.json file begin template_nutella_file_json = JSON.parse(IO.read("#{dir}/nutella.json")) - rescue + rescue StandardError return false end return false unless validate_nutella_file_json template_nutella_file_json + # If template is a bot, perform the appropriate checks - if template_nutella_file_json['type']=='bot' + if template_nutella_file_json['type'] == 'bot' # Is there a mandatory 'startup' script and is it executable return false unless File.executable? "#{dir}/startup" end # If template is an interface, perform the appropriate checks - if template_nutella_file_json['type']=='interface' + if template_nutella_file_json['type'] == 'interface' # Is there the mandatory index.html file return false unless File.exist? "#{dir}/index.html" end true end - - def validate_nutella_file_json( json ) - !json['name'].nil? && !json['version'].nil? && !json['type'].nil? && (json['type']=='bot' || json['type']=='interface') + def validate_nutella_file_json(json) + !json['name'].nil? && !json['version'].nil? && !json['type'].nil? && (json['type'] == 'bot' || json['type'] == 'interface') end - - end - -end \ No newline at end of file +end diff --git a/lib/bots/commands_server/commands/new.rb b/lib/bots/commands_server/commands/new.rb index 9b90b4e..b80d71a 100755 --- a/lib/bots/commands_server/commands/new.rb +++ b/lib/bots/commands_server/commands/new.rb @@ -1,15 +1,18 @@ +# frozen_string_literal: true + require_relative 'meta/command' require 'fileutils' -module Nutella +module CommandsServer class New < Command @description = 'Creates a new nutella application' - - def run(opts=nil) + + def run(opts = nil) # If no app name is provided, return an error if opts['args'].empty? return failure('You need to specify a name for your new application') end + # Parse the name of the app from the CLI parameters app_id = opts['args'][0] # Checks that a directory (i.e. an app) with the same name doesn't already exist @@ -27,12 +30,12 @@ def run(opts=nil) # If all seems good, generate the application skeleton create_app(app_id, app_dir) # Display a success message and return - return success("Your new nutella application #{app_id} is ready!") + success("Your new nutella application #{app_id} is ready!") end - - private - - def create_app( app_id, app_dir ) + + private + + def create_app(app_id, app_dir) # Create directories FileUtils.mkdir_p("#{app_dir}/bots") FileUtils.mkdir_p("#{app_dir}/interfaces") @@ -40,7 +43,7 @@ def create_app( app_id, app_dir ) config_file_hash = { name: app_id, version: '0.1.0', - # TODO figure out how to do make this dynamic + # TODO: figure out how to do make this dynamic # :nutella_version => File.open("#{Nutella::NUTELLA_SRC}VERSION", 'rb').read, nutella_version: '2.0.0', type: 'application', @@ -51,6 +54,5 @@ def create_app( app_id, app_dir ) f.write(JSON.pretty_generate(config_file_hash)) end end - end end diff --git a/lib/bots/commands_server/commands/reset.rb b/lib/bots/commands_server/commands/reset.rb index 1dca0bc..21f8451 100644 --- a/lib/bots/commands_server/commands/reset.rb +++ b/lib/bots/commands_server/commands/reset.rb @@ -1,18 +1,17 @@ +# frozen_string_literal: true + require_relative 'meta/command' -module Nutella +module CommandsServer class Reset < Command @description = 'Resets nutella to factory settings' - - def run(args=nil) - if system "rm -rf $HOME/.nutella" - console.success "Successfully reset nutella to factory settings" + + def run(_args = nil) + if system 'rm -rf $HOME/.nutella' + console.success 'Successfully reset nutella to factory settings' else console.error 'Whoops...something went wrong while resetting nutella to factory settings' end end - end end - - diff --git a/lib/bots/commands_server/commands/runs.rb b/lib/bots/commands_server/commands/runs.rb index baa84eb..50470d9 100755 --- a/lib/bots/commands_server/commands/runs.rb +++ b/lib/bots/commands_server/commands/runs.rb @@ -1,14 +1,14 @@ -require_relative 'meta/command' +# frozen_string_literal: true +require_relative 'meta/command' -module Nutella +module CommandsServer class Runs < Command @description = 'Displays a list of runs for the current application or all applications' - - def run(args=nil) + def run(args = nil) # If invoked with "all" it will show all the runs under this instance of nutella - if args[0]=='--all' || args[0]=='-a' + if args[0] == '--all' || args[0] == '-a' display_all_runs else # If the current directory is not a nutella application, return @@ -20,11 +20,9 @@ def run(args=nil) display_app_runs end end - - - private - - + + private + def display_all_runs if Nutella.runlist.empty? console.info 'You are not running any nutella apps' @@ -38,7 +36,7 @@ def display_all_runs end end end - + def display_app_runs app_id = Nutella.current_app.config['name'] app_runs = Nutella.runlist.runs_for_app app_id @@ -47,8 +45,5 @@ def display_app_runs console.info " #{run_id}" end end - - end end - diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index d3fe2d2..87ea8c0 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -1,14 +1,20 @@ +# frozen_string_literal: true + +require 'docker-api' +require 'config/config' +require 'config/runlist' require_relative 'meta/run_command' -module Nutella +module CommandsServer class Start < RunCommand @description = 'Starts all or some of the bots in the current application' - def run(opts=nil) + def run(opts = nil) # If opts['current_dir'] is not a nutella application, return and error unless is_nutella_app?(opts['current_dir']) - return failure('The current directory is not a nutella application') + return failure('The current directory is not a nutella application', 'error') end + # Extract app path and id (i.e. name) app_path = opts['current_dir'] app_id = app_config(app_path)['name'] @@ -16,50 +22,54 @@ def run(opts=nil) begin run_id = parse_run_id_from_args(opts['args']) rescue StandardError => e - return failure(e.message) + return failure(e.message, 'error') end - # TODO resume from here # Check if there are actully bots that need to be started... - if app_bots_running_already(app_id) - return success("Run #{run} not created: your app bots are running already and your application has no run bots)" + if app_bots_running_already?(app_id) + return failure("Run #{run} not created!\n + Your app bots are running already and your application has no run bots.\n + Are you sure that's what you wanted to do?", 'warn') end - if run_exist?( app_id, run_id) - return failure("Impossible to start nutella app: an instance of this app with the same run_id is already running!\nYou might want to kill it with 'nutella stop #{run_id}'") + if run_exist?(app_id, run_id) + return failure("Impossible to start nutella app!\n + An instance of this app with the same run_id is already running!\n + You might want to kill it with 'nutella stop #{run_id}'", 'warn') end # Start bots return unless start_all_components(app_id, app_path, run_id, params) return unless Nutella.runlist.add?(app_id, run_id, app_path) + print_confirmation(run_id, params, app_id, app_path) end - private + def runlist + @runlist ||= RunList.new("#{Config.file['home_dir']}/runlist.json") + end # Checks that the provided directory is actually a nutella application # @return [Boolean] true if the directory is a nutella application, false otherwise def is_nutella_app?(dir) nutella_json_file_path = "#{dir}/nutella.json" # Check that there is a nutella.json file in the main directory of the application - if !File.exist? nutella_json_file_path - return false - end + return false unless File.exist? nutella_json_file_path + # If there is a file, try to parse it begin - conf = JSON.parse( IO.read(nutella_json_file_path) ) - rescue + conf = JSON.parse(IO.read(nutella_json_file_path)) + rescue StandardError # Not valid JSON, returning false return false end # No nutella version in the file, return false - if conf['nutella_version'].nil? - return false - end + return false if conf['nutella_version'].nil? + true end # Builds a PersistedHash of the application nutella.json file and returns it. - # This method is used to ease access to the app nutella.json file. + # This method is used to ease access to the app nutella.json file inside the app. # @return [PersistedHash] the PersistedHash of the app nutella.json file def app_config(dir) PersistedHash.new("#{dir}/nutella.json") @@ -71,15 +81,13 @@ def no_app_bot_to_start(app_id, app_path, params) ComponentsList.run_level_bots_list(app_path, params).empty? && app_bots_started?(app_id) end - # Returns true if the app bots have been started already - def app_bots_started?( app_id ) + def app_bots_running_already?(app_id) Tmux.session_exist? Tmux.app_bot_session_name app_id end - # Check that the run_id we are trying to start has not been started already - def run_exist?( app_id, run_id) + def run_exist?(app_id, run_id) if Nutella.runlist.include?(app_id, run_id) # If the run_id is already in the list, check that it is actually live if Tmux.session_exist? Tmux.session_name(app_id, run_id) @@ -91,9 +99,8 @@ def run_exist?( app_id, run_id) false end - # Starts all the components at all levels for this run - def start_all_components( app_id, app_path, run_id, params ) + def start_all_components(app_id, app_path, run_id, params) # Start the internal broker return false unless ComponentsStarter.start_internal_broker # Start mongo db @@ -102,13 +109,15 @@ def start_all_components( app_id, app_path, run_id, params ) return false unless ComponentsStarter.start_framework_components # Start all app-level bots (if any, if needed) return false unless ComponentsStarter.start_app_bots(app_id, app_path) + # Start all run-level bots - false unless ComponentsStarter.start_run_bots(ComponentsList.run_level_bots_list(app_path, params), app_path, app_id, run_id) + unless ComponentsStarter.start_run_bots(ComponentsList.run_level_bots_list(app_path, params), app_path, app_id, run_id) + false + end true end - - def print_confirmation( run_id, params, app_id, app_path ) + def print_confirmation(run_id, params, app_id, app_path) # If there are no run-level bots to start, do not create the run and error out if ComponentsList.run_level_bots_list(app_path, params).empty? && !Nutella.runlist.app_has_no_bots(app_id) console.warn 'This run doesn\'t seem to have any components. No run was created.' @@ -118,8 +127,7 @@ def print_confirmation( run_id, params, app_id, app_path ) print_monitoring_details(app_id, run_id) end - - def print_monitoring_details( app_id, run_id ) + def print_monitoring_details(app_id, run_id) # Output broker info console.success "Application is running on broker: #{Nutella.config['broker']}" # If some application bots were started, say it @@ -129,65 +137,60 @@ def print_monitoring_details( app_id, run_id ) end # Only print bots monitoring info if there bots in the run unless Nutella.runlist.app_has_no_bots app_id - console.success "Do `tmux attach-session -t #{Tmux.session_name(app_id,run_id)}` to monitor your bots." + console.success "Do `tmux attach-session -t #{Tmux.session_name(app_id, run_id)}` to monitor your bots." end # Main interface is always available console.success "Go to http://localhost:#{Nutella.config['main_interface_port']}/#{app_id}/#{run_id} to access your interfaces" end - - - end end # # Starts the application level bots - # # @return [boolean] true if all bots are started correctly, false otherwise - # def self.start_app_bots( app_id, app_path ) - # app_bots_list = Nutella.current_app.config['app_bots'] - # bots_dir = "#{app_path}/bots/" - # # If app bots have been started already, then do nothing - # unless Nutella::Tmux.session_exist? Nutella::Tmux.app_bot_session_name app_id - # # Start all app bots in the list into a new tmux session - # tmux = Nutella::Tmux.new app_id, nil - # ComponentsList.for_each_component_in_dir bots_dir do |bot| - # unless app_bots_list.nil? || !app_bots_list.include?( bot ) - # # If there is no 'startup' script output a warning (because - # # startup is mandatory) and skip the bot - # unless File.exist?("#{bots_dir}#{bot}/startup") - # console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." - # next - # end - # # Create a new window in the session for this run - # tmux.new_app_bot_window bot - # end - # end - # end - # true - # end - - - # def self.start_run_bots( bots_list, app_path, app_id, run_id ) - # # Create a new tmux instance for this run - # tmux = Nutella::Tmux.new app_id, run_id - # # Fetch bots dir - # bots_dir = "#{app_path}/bots/" - # # Start the appropriate bots - # bots_list.each { |bot| start_run_level_bot(bots_dir, bot, tmux) } - # true - # end - - - # #--- Private class methods -------------- - - # # Starts a run level bot - # def self.start_run_level_bot( bots_dir, bot, tmux ) - # # If there is no 'startup' script output a warning (because - # # startup is mandatory) and skip the bot - # unless File.exist?("#{bots_dir}#{bot}/startup") - # console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." - # return - # end - # # Create a new window in the session for this run - # tmux.new_bot_window bot - # end - # private_class_method :start_run_level_bot \ No newline at end of file +# # @return [boolean] true if all bots are started correctly, false otherwise +# def self.start_app_bots( app_id, app_path ) +# app_bots_list = Nutella.current_app.config['app_bots'] +# bots_dir = "#{app_path}/bots/" +# # If app bots have been started already, then do nothing +# unless Nutella::Tmux.session_exist? Nutella::Tmux.app_bot_session_name app_id +# # Start all app bots in the list into a new tmux session +# tmux = Nutella::Tmux.new app_id, nil +# ComponentsList.for_each_component_in_dir bots_dir do |bot| +# unless app_bots_list.nil? || !app_bots_list.include?( bot ) +# # If there is no 'startup' script output a warning (because +# # startup is mandatory) and skip the bot +# unless File.exist?("#{bots_dir}#{bot}/startup") +# console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." +# next +# end +# # Create a new window in the session for this run +# tmux.new_app_bot_window bot +# end +# end +# end +# true +# end + +# def self.start_run_bots( bots_list, app_path, app_id, run_id ) +# # Create a new tmux instance for this run +# tmux = Nutella::Tmux.new app_id, run_id +# # Fetch bots dir +# bots_dir = "#{app_path}/bots/" +# # Start the appropriate bots +# bots_list.each { |bot| start_run_level_bot(bots_dir, bot, tmux) } +# true +# end + +# #--- Private class methods -------------- + +# # Starts a run level bot +# def self.start_run_level_bot( bots_dir, bot, tmux ) +# # If there is no 'startup' script output a warning (because +# # startup is mandatory) and skip the bot +# unless File.exist?("#{bots_dir}#{bot}/startup") +# console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script." +# return +# end +# # Create a new window in the session for this run +# tmux.new_bot_window bot +# end +# private_class_method :start_run_level_bot diff --git a/lib/bots/commands_server/commands/stop.rb b/lib/bots/commands_server/commands/stop.rb index 82d1e83..9cd8565 100755 --- a/lib/bots/commands_server/commands/stop.rb +++ b/lib/bots/commands_server/commands/stop.rb @@ -1,11 +1,12 @@ +# frozen_string_literal: true + require_relative 'meta/run_command' -module Nutella +module CommandsServer class Stop < RunCommand @description = 'Stops all the bots in the current application' - def run(opts=nil) - + def run(_opts = nil) # If the current directory is not a nutella application, return unless Nutella.current_app.exist? console.warn 'The current directory is not a nutella application' @@ -26,9 +27,7 @@ def run(opts=nil) stop_app_bots app_id # Stop all framework-level components (if needed) - if Nutella.runlist.empty? - stop_framework_components - end + stop_framework_components if Nutella.runlist.empty? # If running on the internal broker, stop it if needed if Nutella.runlist.empty? @@ -40,11 +39,9 @@ def run(opts=nil) print_success_message(app_id, run_id, 'stopped') end - private - - def remove_from_run_list( app_id, run_id ) + def remove_from_run_list(app_id, run_id) unless Nutella.runlist.delete? app_id, run_id console.warn "Run #{run_id} doesn't exist. Impossible to stop it." return false @@ -52,8 +49,7 @@ def remove_from_run_list( app_id, run_id ) true end - - def stop_app_bots( app_id ) + def stop_app_bots(app_id) tmux_session_name = Tmux.app_bot_session_name app_id if Tmux.session_exist? tmux_session_name # Are there any run of this app hinging on the app bots? @@ -64,7 +60,6 @@ def stop_app_bots( app_id ) true end - def stop_framework_components nutella_components_dir = "#{Nutella::NUTELLA_SRC}framework_components" ComponentsList.for_each_component_in_dir nutella_components_dir do |component| @@ -73,35 +68,31 @@ def stop_framework_components end end - def stop_internal_broker cid = `docker ps --filter ancestor=matteocollina/mosca:v2.3.0 --format "{{.ID}}"` `docker kill #{cid}` end - def stop_mongo pid_file_path = "#{Nutella.config['home_dir']}.mongo_pid" kill_process_with_pid pid_file_path end - # Does the process pid file exist? # If it does we send a SIGKILL to the process with that pid # to stop the process and delete the pid file - def kill_process_with_pid( pid_file_path ) + def kill_process_with_pid(pid_file_path) if File.exist? pid_file_path - pid_file = File.open( pid_file_path, 'rb' ) + pid_file = File.open(pid_file_path, 'rb') pid = pid_file.read.to_i pid_file.close begin - Process.kill( 'SIGKILL', pid ) - rescue + Process.kill('SIGKILL', pid) + rescue StandardError # Pid file exists but process is dead. Do nothing end File.delete pid_file_path end end - end end diff --git a/lib/bots/commands_server/commands/template.rb b/lib/bots/commands_server/commands/template.rb index ad35831..177c2d0 100755 --- a/lib/bots/commands_server/commands/template.rb +++ b/lib/bots/commands_server/commands/template.rb @@ -1,14 +1,14 @@ -require_relative 'meta/template_command' +# frozen_string_literal: true -module Nutella +require_relative 'meta/template_command' +module CommandsServer class Template < TemplateCommand @description = 'Creates and validates nutella components templates' - def run(args=nil) - + def run(args = nil) # If no argument then we just display info about the command - if args==nil || args.length < 2 + if args.nil? || args.length < 2 display_help return end @@ -17,14 +17,9 @@ def run(args=nil) sub_command = args[0] param = args[1] - if sub_command == 'validate' - validate_template_sub_command param - end - - if sub_command == 'create' - create_template_sub_command param - end + validate_template_sub_command param if sub_command == 'validate' + create_template_sub_command param if sub_command == 'create' end private @@ -35,8 +30,7 @@ def display_help console.warn 'validate validates a template that already exists' end - - def validate_template_sub_command( dir ) + def validate_template_sub_command(dir) if validate_template dir console.success 'This directory appears to be a valid nutella template' else @@ -44,8 +38,7 @@ def validate_template_sub_command( dir ) end end - - def create_template_sub_command( template_name ) + def create_template_sub_command(template_name) @default_version = '0.1.0' @default_type = 'bot' # Get template parameters from command line @@ -55,13 +48,11 @@ def create_template_sub_command( template_name ) description = prompt_and_read_description type repo = prompt_and_read_repo # Build JSON - json = build_nutella_json( name, version, type, description, repo ) + json = build_nutella_json(name, version, type, description, repo) # Show confirmation and read prompt_and_read_confirm json - end - def prompt_and_read_name(template_name) puts 'What is the name of your template?' print "(#{template_name}) " @@ -77,7 +68,7 @@ def prompt_and_read_type prompt_and_read 'Are you creating a template for a "bot" or "interface"?', @default_type end - def prompt_and_read( prompt, default ) + def prompt_and_read(prompt, default) puts prompt print "(#{default}) " c = $stdin.gets.chomp! @@ -101,21 +92,21 @@ def prompt_and_read_confirm(json) puts JSON.pretty_generate json print '(yes/no) ' confirm = $stdin.gets.chomp! - if confirm=='yes' + if confirm == 'yes' create_template json else console.warn 'Template creation aborted' end end - def create_template( json ) + def create_template(json) # First validate the JSON unless validate_nutella_file_json json console.error 'Something was wrong with your nutella.json file. Template creation aborted' return end # Check that the template directory doesn't already exist - template_dir = File.join( Dir.pwd, json['name'] ) + template_dir = File.join(Dir.pwd, json['name']) if File.directory? template_dir console.error("The directory #{template_dir} already exists! Can't create template #{json[:name]}") return @@ -128,25 +119,22 @@ def create_template_files(json, template_dir) # Create directory Dir.mkdir template_dir # Create nutella.json file - File.open("#{template_dir}/nutella.json", 'w') { |f| f.write(JSON.pretty_generate json) } + File.open("#{template_dir}/nutella.json", 'w') { |f| f.write(JSON.pretty_generate(json)) } # Add bot/interface specific files - FileUtils.copy(File.join(Nutella::NUTELLA_SRC, 'data/startup'), template_dir) if json['type']=='bot' - FileUtils.copy(File.join(Nutella::NUTELLA_SRC, 'data/index.html'), template_dir) if json['type']=='interface' + if json['type'] == 'bot' + FileUtils.copy(File.join(Nutella::NUTELLA_SRC, 'data/startup'), template_dir) + end + if json['type'] == 'interface' + FileUtils.copy(File.join(Nutella::NUTELLA_SRC, 'data/index.html'), template_dir) + end console.success "Template #{json['name']} created successfully!" end - - def build_nutella_json( name, version, type, description, repo ) - json = { 'name' => name, 'version' => version, 'type' => type} - unless description.empty? - json['description'] = description - end - unless repo.empty? - json['repo'] = repo - end + def build_nutella_json(name, version, type, description, repo) + json = { 'name' => name, 'version' => version, 'type' => type } + json['description'] = description unless description.empty? + json['repo'] = repo unless repo.empty? json end - end - end diff --git a/lib/bots/commands_server/util/components_list.rb b/lib/bots/commands_server/util/components_list.rb index 4fdc4cb..1b13e71 100755 --- a/lib/bots/commands_server/util/components_list.rb +++ b/lib/bots/commands_server/util/components_list.rb @@ -1,71 +1,65 @@ -module Nutella - -end +# frozen_string_literal: true + # Utility methods to list components class ComponentsList - # Returns all the components in a certain directory - def self.components_in_dir( dir ) - Dir.entries(dir).select {|entry| File.directory?(File.join(dir, entry)) && !(entry =='.' || entry == '..') } + def self.components_in_dir(dir) + Dir.entries(dir).select { |entry| File.directory?(File.join(dir, entry)) && !(entry == '.' || entry == '..') } end - # Executes a code block for each component in a certain directory # @param [String] dir directory where we are iterating # @yield [component] Passes the component name to the block - def self.for_each_component_in_dir( dir, &block ) + def self.for_each_component_in_dir(dir, &block) components_in_dir(dir).each { |component| block.call component } end - # Returns the list of run-level bots for this run # Depending on the mode we are in, we want to start only some bots, exclude only some bots or start all bots - def self.run_level_bots_list( app_path, params ) + def self.run_level_bots_list(app_path, params) # Fetch the list of all components in the bots dir all_bots = components_in_dir "#{app_path}/bots/" # Fetch the list of app bots app_bots = Nutella.current_app.config['app_bots'] # Return correct list based on the mode we are in case start_mode(params) - when :WITH - return get_with_bots_list params[:with], app_bots - when :WO - return get_wo_bots_list all_bots, app_bots, params[:without] - when :ALL - return get_all_bots_list all_bots, app_bots - else - # If we get here it means we are both in with and without mode and something went very wrong... - raise 'You are using simultaneously with and without modes. This should not happen. Please contact developers.' + when :WITH + return get_with_bots_list params[:with], app_bots + when :WO + return get_wo_bots_list all_bots, app_bots, params[:without] + when :ALL + return get_all_bots_list all_bots, app_bots + else + # If we get here it means we are both in with and without mode and something went very wrong... + raise 'You are using simultaneously with and without modes. This should not happen. Please contact developers.' end end - #--- Private class methods -------------- - def self.start_mode(params) return :WITH unless params[:with].empty? return :WO unless params[:without].empty? + :ALL if params[:with].empty? && params[:without].empty? end private_class_method :start_mode # If we are in "with mode", we want to run only the bots in the "with list" (minus the ones in app_bots_list) - def self.get_with_bots_list( incl_bots, app_bots) - return app_bots.nil? ? incl_bots : incl_bots - app_bots + def self.get_with_bots_list(incl_bots, app_bots) + app_bots.nil? ? incl_bots : incl_bots - app_bots end private_class_method :get_with_bots_list # If we are in "without mode", we want to run all the bots in the bots_list minus the ones in the "without list" and in the "app bots list" - def self.get_wo_bots_list( all_bots, app_bots, excl_bots ) - return app_bots.nil? ? all_bots - excl_bots : all_bots - excl_bots - app_bots + def self.get_wo_bots_list(all_bots, app_bots, excl_bots) + app_bots.nil? ? all_bots - excl_bots : all_bots - excl_bots - app_bots end private_class_method :get_wo_bots_list # If we are in "all mode", we want to run all the bots minus the ones in the "app bots list" - def self.get_all_bots_list( all_bots, app_bots ) - return app_bots.nil? ? all_bots : all_bots - app_bots + def self.get_all_bots_list(all_bots, app_bots) + app_bots.nil? ? all_bots : all_bots - app_bots end private_class_method :get_all_bots_list - -end \ No newline at end of file +end diff --git a/lib/bots/commands_server/util/current_app_utils.rb b/lib/bots/commands_server/util/current_app_utils.rb index 22ee04c..4b8dd4e 100755 --- a/lib/bots/commands_server/util/current_app_utils.rb +++ b/lib/bots/commands_server/util/current_app_utils.rb @@ -1,28 +1,26 @@ -require 'json' +# frozen_string_literal: true -module Nutella +require 'json' +module CommandsServer # This module contains a series of utilities methods to handle the nutella # application contained in the directory we are at this moment module CurrentAppUtils - # Checks that the current directory is actually a nutella application # @return [Boolean] true if the current directory is a nutella application, false otherwise - def CurrentAppUtils.exist? + def self.exist? cur_app_dir = Dir.pwd nutella_json_file = "#{cur_app_dir}/nutella.json" # Check that there is a nutella.json file in the main directory of the application if File.exist? nutella_json_file begin - conf = JSON.parse( IO.read(nutella_json_file) ) - rescue + conf = JSON.parse(IO.read(nutella_json_file)) + rescue StandardError console.warn 'The nutella.json file for this application does not contain properly formatted JSON' return false end - if conf['nutella_version'].nil? - return false - end + return false if conf['nutella_version'].nil? else return false end @@ -32,7 +30,7 @@ def CurrentAppUtils.exist? # Builds a PersistedHash of the application nutella.json file and returns it. # This method is used to ease access to the app nutella.json file. # @return [PersistedHash] the PersistedHash of the app nutella.json file - def CurrentAppUtils.config + def self.config cur_app_dir = Dir.pwd nutella_json_file = "#{cur_app_dir}/nutella.json" if File.exist? nutella_json_file @@ -41,17 +39,16 @@ def CurrentAppUtils.config raise 'The current directory is not a nutella app: impossible to read nutella.json file' end end - end - # Calling this method (Nutella.current_app) simply returns # a reference to the CurrentAppUtils module def Nutella.current_app CurrentAppUtils end - end - - +# Returns true if the app has no bots +# def app_has_no_bots(app_id) +# Dir.entries("#{app_path(app_id)}/bots").select { |entry| File.directory?(File.join("#{app_path(app_id)}/bots", entry)) && !(entry == '.' || entry == '..') }.empty? +# end diff --git a/lib/bots/commands_server/util/runlist.rb b/lib/config/runlist.rb similarity index 72% rename from lib/bots/commands_server/util/runlist.rb rename to lib/config/runlist.rb index 35fd679..c704059 100755 --- a/lib/bots/commands_server/util/runlist.rb +++ b/lib/config/runlist.rb @@ -1,8 +1,8 @@ +# frozen_string_literal: true + require 'config/persisted_hash' -require 'tmux/tmux' module Nutella - # Manages the list of nutella applications and runs handled by the framework. # The list has a structure similar this one: # { @@ -15,13 +15,11 @@ module Nutella # "path": "/path/to/app/b/files/" # } # } - class RunListHash - - def initialize( file ) + class RunList + def initialize(file) @ph = PersistedHash.new file end - # Returns a list of all the apps in the runlist # # @return [Array] an array containing the app_ids of all the apps in the runlist @@ -29,7 +27,6 @@ def all_apps @ph.to_h.keys end - # Returns all the +run_id+s for ALL applications (i.e. the runslist) # # @return [Hash] the run list with all app_ids and run_ids @@ -37,29 +34,28 @@ def all_runs @ph.to_h end - # Returns all the +run_id+s for a certain application # # @param [String] app_id of the application we want to find run_ids for # @return [Array] list of +run_id+s associated to the specified app_id - def runs_for_app( app_id ) + def runs_for_app(app_id) # If there is no app, then return false and do nothing return [] if @ph[app_id].nil? + runs = @ph[app_id]['runs'] runs.nil? ? [] : runs end - # Returns the path for a certain application # # @param [String] app_id of the application we want to find the path of # @return [String] the path of the app or nil if the app doesn't exist - def app_path( app_id ) + def app_path(app_id) return nil if @ph[app_id].nil? + @ph[app_id]['path'] end - # Adds a run_id to the runlist # # @param [String] app_id the app_id the run_id belongs to @@ -67,11 +63,9 @@ def app_path( app_id ) # @param [String] path_to_app_files the path to the application files # @return [Boolean] true if the run_id is added to the list (i.e. there is no other # run_id with for the same app_id) - def add?( app_id, run_id, path_to_app_files ) - # If no run_id is specified, we are adding the "default" run - run_id = 'default' if run_id.nil? + def add?(app_id, run_id, path_to_app_files) # Check if we are adding the first run for a certain application - if @ph.add_key_value?(app_id, Hash.new) + if @ph.add_key_value?(app_id, {}) t = @ph[app_id] # Add path and initialize runs t['path'] = path_to_app_files @@ -80,6 +74,7 @@ def add?( app_id, run_id, path_to_app_files ) t = @ph[app_id] # Check a run with this name doesn't already exist return false if t['runs'].include? run_id + # Add the run_id to list of runs t['runs'].push(run_id) end @@ -87,16 +82,16 @@ def add?( app_id, run_id, path_to_app_files ) true end - # Remove a run_id from the list # # @param [String] app_id the app_id the run_id belongs to # @param [String] run_id the run_if we are trying to remove from the runs list # @return [Boolean] true if the run_id is removed from the list (i.e. a run_id with that name exists # and is successfully removed) - def delete?( app_id, run_id ) + def delete?(app_id, run_id) # If there is no app, then return false and do nothing return false if @ph[app_id].nil? + t = @ph[app_id] result = t['runs'].delete run_id if t['runs'].empty? @@ -109,63 +104,28 @@ def delete?( app_id, run_id ) result.nil? ? false : true end - # Checks if a certain run is contained in the list # # @param [String] app_id the app_id the run_id belongs to # @param [String] run_id the run_if we are checking # @return [Boolean] true if the run_id is in the list, false otherwise - def include? (app_id, run_id) + def include?(app_id, run_id) # If there is no app, then return false and do nothing return false if @ph[app_id].nil? + # Otherwise check the runs array @ph[app_id]['runs'].include? run_id end - # Returns true if the runs list is empty # @return [Boolean] true if the list is empty, false otherwise def empty? @ph.empty? end - # Removes the runs list file def remove_file @ph.remove_file end - - - # This method checks that the list reflects the actual - # state of the system. It does so by checking that there is - # still a tmux session with the run name. If that's not the case, - # it removes the missing runs from the list. - def clean_list - all_runs.each do |app, _| - runs_for_app(app).each do |run| - unless Tmux.session_exist?(Tmux.session_name(app, run)) || app_has_no_bots(app) - delete? app, run - end - end - end - end - - - # Returns true if the app has no bots - def app_has_no_bots( app_id ) - Dir.entries("#{app_path(app_id)}/bots").select{|entry| File.directory?(File.join("#{app_path(app_id)}/bots",entry)) && !(entry =='.' || entry == '..') }.empty? - end - end - - - # Calling this method (Nutella.runlist) simply returns and instance of - # RunListHash linked to file runlist.json in the nutella home directory - def Nutella.runlist - # TODO replace this with the config!!!! - # rl = RunListHash.new( "#{ENV['HOME']}/.nutella/runlist.json" ) - rl.clean_list - rl - end - -end \ No newline at end of file +end diff --git a/spec/bots/commands_server/start_spec.rb b/spec/bots/commands_server/start_spec.rb new file mode 100644 index 0000000..8b7795f --- /dev/null +++ b/spec/bots/commands_server/start_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'bots/commands_server/commands/start' + +module CommandsServer + describe Start do + before(:each) do + @start = Start.new + end + + # after(:each) do + # @rl.remove_file + # end + + describe '#run' do + context 'outside nutella app' do + let(:input) { { 'current_directory': 'whatever' } } + it 'errors out' do + expect(@start.run(:input)).to eq(failure('The current directory is not a nutella application', 'error')) + end + end + end + + def failure(message, level, _exception = nil) + c = Command.new + c.failure(message, level, exception = nil) + end + + def success(message) + c = Command.new + c.success(message) + end + end +end diff --git a/spec/config/runlist_spec.rb b/spec/config/runlist_spec.rb new file mode 100644 index 0000000..4fd6424 --- /dev/null +++ b/spec/config/runlist_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require 'config/runlist' +require 'securerandom' + +module Nutella + describe RunList do + before(:each) do + @rl = RunList.new(SecureRandom.uuid) + end + + after(:each) do + @rl.remove_file + end + + describe '#add?' do + subject { @rl.add?('app_id', 'run_id', 'path_(to_files') } + context 'when list is empty' do + it { is_expected.to be true } + end + context 'when list is not empty' do + context 'and run exists' do + before { @rl.add?('app_id', 'run_id', 'path/to/files') } + it { is_expected.to be false } + end + context 'and run does not exist' do + before { @rl.add?('app_id', 'different', 'path/to/files') } + it { is_expected.to be true } + end + end + end + + describe '#all_apps' do + context 'when list is empty' do + it { expect(@rl.all_apps).to be_empty } + end + context 'when list is not empty' do + let(:want) { { 'app_id': { runs: ['run_id'], path: 'path/to/files' } } } + before { @rl.add?('app_id', 'run_id', 'path/to/files') } + it { expect(@rl.all_apps).to eq(['app_id']) } + end + end + + # TODO: finish writing tests for all the other methods... Yes, I am being lazy + end +end From bd46b721fde2315aff3f903c89214b0e4684d455 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sun, 24 Nov 2019 18:53:38 -0800 Subject: [PATCH 35/43] Revert to old nutella_lib --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 37e0cbe..2b8301a 100755 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ gem 'ansi', '~> 1.5' gem 'bson', '~> 3.0' gem 'docker-api', '~> 1.34' gem 'git', '~> 1.2' -gem 'nutella_lib','~>0.6' +gem 'nutella_lib','~>0.5' gem 'nokogiri', '~>1.6' gem 'semantic', '~> 1.4' gem 'sinatra', '~>1.4' From ece0d267b695504b463fccb1ca6d0cb7c1c73f4d Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Fri, 13 Dec 2019 02:51:58 -0800 Subject: [PATCH 36/43] Cleanup start --- .../commands/meta/run_command.rb | 1 - lib/bots/commands_server/commands/start.rb | 135 +++++++----------- .../commands_server/util/components_list.rb | 65 --------- .../commands_server/util/current_app_utils.rb | 54 ------- lib/config/app.rb | 57 ++++++++ lib/config/runlist.rb | 4 +- spec/bots/commands_server/start_spec.rb | 25 +++- 7 files changed, 131 insertions(+), 210 deletions(-) delete mode 100755 lib/bots/commands_server/util/components_list.rb delete mode 100755 lib/bots/commands_server/util/current_app_utils.rb create mode 100644 lib/config/app.rb diff --git a/lib/bots/commands_server/commands/meta/run_command.rb b/lib/bots/commands_server/commands/meta/run_command.rb index 9c12a79..e956a4e 100755 --- a/lib/bots/commands_server/commands/meta/run_command.rb +++ b/lib/bots/commands_server/commands/meta/run_command.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative 'command' -require_relative '../../util/components_list' module CommandsServer # This class describes a run command which can be either start or stop. diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index 87ea8c0..4452f33 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -3,6 +3,7 @@ require 'docker-api' require 'config/config' require 'config/runlist' +require 'config/app' require_relative 'meta/run_command' module CommandsServer @@ -11,13 +12,12 @@ class Start < RunCommand def run(opts = nil) # If opts['current_dir'] is not a nutella application, return and error - unless is_nutella_app?(opts['current_dir']) + unless Nutella::App.exist?(opts['current_dir']) return failure('The current directory is not a nutella application', 'error') end # Extract app path and id (i.e. name) - app_path = opts['current_dir'] - app_id = app_config(app_path)['name'] + app = Nutella::App.new(opts['current_dir']) # If there is an error parsing the run_id, return an error begin run_id = parse_run_id_from_args(opts['args']) @@ -25,65 +25,35 @@ def run(opts = nil) return failure(e.message, 'error') end # Check if there are actully bots that need to be started... - if app_bots_running_already?(app_id) - return failure("Run #{run} not created!\n + if app_bots_running_already?(app) && app.run_level_bots.empty? + return failure("Run #{run_id} not created!\n Your app bots are running already and your application has no run bots.\n - Are you sure that's what you wanted to do?", 'warn') + No run was created. Are you sure that's what you wanted to do?", 'warn') end - if run_exist?(app_id, run_id) + if run_exist?(app.id, run_id) return failure("Impossible to start nutella app!\n An instance of this app with the same run_id is already running!\n You might want to kill it with 'nutella stop #{run_id}'", 'warn') end - # Start bots - return unless start_all_components(app_id, app_path, run_id, params) - return unless Nutella.runlist.add?(app_id, run_id, app_path) - - print_confirmation(run_id, params, app_id, app_path) - end - - private - - def runlist - @runlist ||= RunList.new("#{Config.file['home_dir']}/runlist.json") - end - - # Checks that the provided directory is actually a nutella application - # @return [Boolean] true if the directory is a nutella application, false otherwise - def is_nutella_app?(dir) - nutella_json_file_path = "#{dir}/nutella.json" - # Check that there is a nutella.json file in the main directory of the application - return false unless File.exist? nutella_json_file_path - - # If there is a file, try to parse it + # Start bots and create run begin - conf = JSON.parse(IO.read(nutella_json_file_path)) - rescue StandardError - # Not valid JSON, returning false - return false + start_app_level_bots(app) + start_run_level_bots(app, run_id) + runlist.add?(app.path, app.id, run_id) + rescue StandardError => e + return failure(e.message, 'error') end - # No nutella version in the file, return false - return false if conf['nutella_version'].nil? - - true - end - - # Builds a PersistedHash of the application nutella.json file and returns it. - # This method is used to ease access to the app nutella.json file inside the app. - # @return [PersistedHash] the PersistedHash of the app nutella.json file - def app_config(dir) - PersistedHash.new("#{dir}/nutella.json") + success(success_message(app, run_id)) end - # Returns true if both the list of run level bots is empty and the app bots - # have been started already - def no_app_bot_to_start(app_id, app_path, params) - ComponentsList.run_level_bots_list(app_path, params).empty? && app_bots_started?(app_id) - end + private # Returns true if the app bots have been started already - def app_bots_running_already?(app_id) - Tmux.session_exist? Tmux.app_bot_session_name app_id + def app_bots_running_already?(_app) + # TODO: look a what the app level bots are (via app.app_level_bots) + # Fetch the list of running app bots for app.id from docker + # If eq, all app bots are running, return true + false end # Check that the run_id we are trying to start has not been started already @@ -99,52 +69,45 @@ def run_exist?(app_id, run_id) false end - # Starts all the components at all levels for this run - def start_all_components(app_id, app_path, run_id, params) - # Start the internal broker - return false unless ComponentsStarter.start_internal_broker - # Start mongo db - return false unless ComponentsStarter.start_mongo_db - # Start all framework-level components (if needed) - return false unless ComponentsStarter.start_framework_components - # Start all app-level bots (if any, if needed) - return false unless ComponentsStarter.start_app_bots(app_id, app_path) - - # Start all run-level bots - unless ComponentsStarter.start_run_bots(ComponentsList.run_level_bots_list(app_path, params), app_path, app_id, run_id) - false - end - true + # Starts app level bots + def start_run_level_bots(app) + # TODO: implement end - def print_confirmation(run_id, params, app_id, app_path) - # If there are no run-level bots to start, do not create the run and error out - if ComponentsList.run_level_bots_list(app_path, params).empty? && !Nutella.runlist.app_has_no_bots(app_id) - console.warn 'This run doesn\'t seem to have any components. No run was created.' - return - end - print_success_message(app_id, run_id, 'started') - print_monitoring_details(app_id, run_id) + # Starts run level bots + def start_run_level_bots(app, run_id) + # TODO: implement end - def print_monitoring_details(app_id, run_id) - # Output broker info - console.success "Application is running on broker: #{Nutella.config['broker']}" - # If some application bots were started, say it - app_bots_list = Nutella.current_app.config['app_bots'] - unless app_bots_list.nil? || app_bots_list.empty? - console.success "Do `tmux attach-session -t #{Tmux.app_bot_session_name(app_id)}` to monitor your app bots." - end + def success_message(app, run_id) + # Initialized the message with the broker + message = "Application is running on broker: #{Nutella::Config.file['broker']}\n" + # If some app-level bots were started, say it + # app_bots_list = Nutella.current_app.config['app_bots'] + # unless app_bots_list.nil? || app_bots_list.empty? + # console.success "Do `tmux attach-session -t #{Tmux.app_bot_session_name(app_id)}` to monitor your app bots." + # end # Only print bots monitoring info if there bots in the run - unless Nutella.runlist.app_has_no_bots app_id - console.success "Do `tmux attach-session -t #{Tmux.session_name(app_id, run_id)}` to monitor your bots." - end + # unless Nutella.runlist.app_has_no_bots app_id + # console.success "Do `tmux attach-session -t #{Tmux.session_name(app_id, run_id)}` to monitor your bots." + # end # Main interface is always available - console.success "Go to http://localhost:#{Nutella.config['main_interface_port']}/#{app_id}/#{run_id} to access your interfaces" + message += "Go to http://localhost:#{Nutella::Config.file['main_interface_port']}/#{app.id}/#{run_id} to access your interfaces\n" + end + + def runlist + @runlist ||= Nutella::RunList.new("#{Nutella::Config.file['home_dir']}/runlist.json") end end end +# Executes a code block for each component in a certain directory +# @param [String] dir directory where we are iterating +# @yield [component] Passes the component name to the block +# def self.for_each_component_in_dir(dir, &block) +# components_in_dir(dir).each { |component| block.call component } +# end + # # Starts the application level bots # # @return [boolean] true if all bots are started correctly, false otherwise # def self.start_app_bots( app_id, app_path ) diff --git a/lib/bots/commands_server/util/components_list.rb b/lib/bots/commands_server/util/components_list.rb deleted file mode 100755 index 1b13e71..0000000 --- a/lib/bots/commands_server/util/components_list.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -# Utility methods to list components -class ComponentsList - # Returns all the components in a certain directory - def self.components_in_dir(dir) - Dir.entries(dir).select { |entry| File.directory?(File.join(dir, entry)) && !(entry == '.' || entry == '..') } - end - - # Executes a code block for each component in a certain directory - # @param [String] dir directory where we are iterating - # @yield [component] Passes the component name to the block - def self.for_each_component_in_dir(dir, &block) - components_in_dir(dir).each { |component| block.call component } - end - - # Returns the list of run-level bots for this run - # Depending on the mode we are in, we want to start only some bots, exclude only some bots or start all bots - def self.run_level_bots_list(app_path, params) - # Fetch the list of all components in the bots dir - all_bots = components_in_dir "#{app_path}/bots/" - # Fetch the list of app bots - app_bots = Nutella.current_app.config['app_bots'] - # Return correct list based on the mode we are in - case start_mode(params) - when :WITH - return get_with_bots_list params[:with], app_bots - when :WO - return get_wo_bots_list all_bots, app_bots, params[:without] - when :ALL - return get_all_bots_list all_bots, app_bots - else - # If we get here it means we are both in with and without mode and something went very wrong... - raise 'You are using simultaneously with and without modes. This should not happen. Please contact developers.' - end - end - - #--- Private class methods -------------- - - def self.start_mode(params) - return :WITH unless params[:with].empty? - return :WO unless params[:without].empty? - - :ALL if params[:with].empty? && params[:without].empty? - end - private_class_method :start_mode - - # If we are in "with mode", we want to run only the bots in the "with list" (minus the ones in app_bots_list) - def self.get_with_bots_list(incl_bots, app_bots) - app_bots.nil? ? incl_bots : incl_bots - app_bots - end - private_class_method :get_with_bots_list - - # If we are in "without mode", we want to run all the bots in the bots_list minus the ones in the "without list" and in the "app bots list" - def self.get_wo_bots_list(all_bots, app_bots, excl_bots) - app_bots.nil? ? all_bots - excl_bots : all_bots - excl_bots - app_bots - end - private_class_method :get_wo_bots_list - - # If we are in "all mode", we want to run all the bots minus the ones in the "app bots list" - def self.get_all_bots_list(all_bots, app_bots) - app_bots.nil? ? all_bots : all_bots - app_bots - end - private_class_method :get_all_bots_list -end diff --git a/lib/bots/commands_server/util/current_app_utils.rb b/lib/bots/commands_server/util/current_app_utils.rb deleted file mode 100755 index 4b8dd4e..0000000 --- a/lib/bots/commands_server/util/current_app_utils.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require 'json' - -module CommandsServer - # This module contains a series of utilities methods to handle the nutella - # application contained in the directory we are at this moment - module CurrentAppUtils - # Checks that the current directory is actually a nutella application - # @return [Boolean] true if the current directory is a nutella application, false otherwise - def self.exist? - cur_app_dir = Dir.pwd - nutella_json_file = "#{cur_app_dir}/nutella.json" - # Check that there is a nutella.json file in the main directory of the application - if File.exist? nutella_json_file - begin - conf = JSON.parse(IO.read(nutella_json_file)) - rescue StandardError - console.warn 'The nutella.json file for this application does not contain properly formatted JSON' - return false - end - - return false if conf['nutella_version'].nil? - else - return false - end - true - end - - # Builds a PersistedHash of the application nutella.json file and returns it. - # This method is used to ease access to the app nutella.json file. - # @return [PersistedHash] the PersistedHash of the app nutella.json file - def self.config - cur_app_dir = Dir.pwd - nutella_json_file = "#{cur_app_dir}/nutella.json" - if File.exist? nutella_json_file - return PersistedHash.new(nutella_json_file) - else - raise 'The current directory is not a nutella app: impossible to read nutella.json file' - end - end - end - - # Calling this method (Nutella.current_app) simply returns - # a reference to the CurrentAppUtils module - def Nutella.current_app - CurrentAppUtils - end -end - -# Returns true if the app has no bots -# def app_has_no_bots(app_id) -# Dir.entries("#{app_path(app_id)}/bots").select { |entry| File.directory?(File.join("#{app_path(app_id)}/bots", entry)) && !(entry == '.' || entry == '..') }.empty? -# end diff --git a/lib/config/app.rb b/lib/config/app.rb new file mode 100644 index 0000000..072e275 --- /dev/null +++ b/lib/config/app.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Models a nutella application +module Nutella + class App + attr_reader :path + attr_reader :id + attr_reader :config + + def initialize(app_path) + @path = app_path + # Build a PersistedHash of the application nutella.json file + # to ease access to the nutella.json file inside the app + @config = Nutella::PersistedHash.new("#{@path}/nutella.json") + @id = @config['name'] + end + + # Returns an array of run-level bots + def run_level_bots + all_bots = components_in_dir("#{@path}/bots/") + # Run-level bots are all the bots minus app-level ones + all_bots - app_level_bots + end + + # Returns an array of app-level bots + def app_level_bots + config['app_bots'].nil? ? [] : config['app_bots'] + end + + # Checks that the provided directory is actually a nutella application + # @return [Boolean] true if the directory is a nutella application, false otherwise + def self.exist?(dir) + nutella_json_file_path = "#{dir}/nutella.json" + # Check that there is a nutella.json file in the main directory of the application + return false unless File.exist? nutella_json_file_path + + # If there is a file, try to parse it + begin + conf = JSON.parse(IO.read(nutella_json_file_path)) + rescue StandardError + # Not valid JSON, returning false + return false + end + # No nutella version in the file, return false + return false if conf['nutella_version'].nil? + + true + end + + private + + # Returns all the components in a certain directory + def components_in_dir(dir) + Dir.entries(dir).select { |entry| File.directory?(File.join(dir, entry)) && !(entry == '.' || entry == '..') } + end + end +end diff --git a/lib/config/runlist.rb b/lib/config/runlist.rb index c704059..e40caac 100755 --- a/lib/config/runlist.rb +++ b/lib/config/runlist.rb @@ -58,12 +58,12 @@ def app_path(app_id) # Adds a run_id to the runlist # + # @param [String] path_to_app_files the path to the application files # @param [String] app_id the app_id the run_id belongs to # @param [String] run_id the run_id we are trying to add to the runs list - # @param [String] path_to_app_files the path to the application files # @return [Boolean] true if the run_id is added to the list (i.e. there is no other # run_id with for the same app_id) - def add?(app_id, run_id, path_to_app_files) + def add?(path_to_app_files, app_id, run_id) # Check if we are adding the first run for a certain application if @ph.add_key_value?(app_id, {}) t = @ph[app_id] diff --git a/spec/bots/commands_server/start_spec.rb b/spec/bots/commands_server/start_spec.rb index 8b7795f..98d03f8 100644 --- a/spec/bots/commands_server/start_spec.rb +++ b/spec/bots/commands_server/start_spec.rb @@ -15,11 +15,32 @@ module CommandsServer describe '#run' do context 'outside nutella app' do - let(:input) { { 'current_directory': 'whatever' } } + let(:opts) { { 'current_directory': 'whatever' } } it 'errors out' do - expect(@start.run(:input)).to eq(failure('The current directory is not a nutella application', 'error')) + expect(@start.run(opts)).to eq(failure('The current directory is not a nutella application', 'error')) end end + + context 'error parsing run_id' do + before { allow(Nutella::App).to receive(:exist?).and_return(true) } + let(:opts) { { 'args' => ['default'] } } + subject { @start.run(opts) } + it { is_expected.to eq(failure('Unfortunately you can\'t use `default` as a run_id because it is reserved :(', 'error')) } + end + + context 'app bots already running' do + before do + allow(Nutella::App).to receive(:exist?).and_return(true) + allow_any_instance_of(Nutella::App).to receive(:components_in_dir).and_return([]) + allow(@start).to receive(:app_bots_running_already?).and_return(true) + end + let(:opts) { { 'current_directory': 'whatever' } } + it 'should return a failure' do + expect(@start.run(opts).to_s).to include('not created') + end + end + + # TODO: last part of start method end def failure(message, level, _exception = nil) From 495ffaad5299bc1c1c4423f40ab1f327dc28682d Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Fri, 13 Dec 2019 21:45:04 -0800 Subject: [PATCH 37/43] Fixed tests --- lib/bots/commands_server/commands/start.rb | 22 +++++++++++----------- lib/config/{app.rb => nutella_app.rb} | 2 +- spec/bots/commands_server/start_spec.rb | 6 +++--- spec/config/runlist_spec.rb | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) rename lib/config/{app.rb => nutella_app.rb} (98%) diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index 4452f33..17e3488 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -3,7 +3,7 @@ require 'docker-api' require 'config/config' require 'config/runlist' -require 'config/app' +require 'config/nutella_app' require_relative 'meta/run_command' module CommandsServer @@ -12,12 +12,12 @@ class Start < RunCommand def run(opts = nil) # If opts['current_dir'] is not a nutella application, return and error - unless Nutella::App.exist?(opts['current_dir']) + unless Nutella::NutellaApp.exist?(opts['current_dir']) return failure('The current directory is not a nutella application', 'error') end # Extract app path and id (i.e. name) - app = Nutella::App.new(opts['current_dir']) + app = Nutella::NutellaApp.new(opts['current_dir']) # If there is an error parsing the run_id, return an error begin run_id = parse_run_id_from_args(opts['args']) @@ -48,14 +48,6 @@ def run(opts = nil) private - # Returns true if the app bots have been started already - def app_bots_running_already?(_app) - # TODO: look a what the app level bots are (via app.app_level_bots) - # Fetch the list of running app bots for app.id from docker - # If eq, all app bots are running, return true - false - end - # Check that the run_id we are trying to start has not been started already def run_exist?(app_id, run_id) if Nutella.runlist.include?(app_id, run_id) @@ -69,6 +61,14 @@ def run_exist?(app_id, run_id) false end + # Returns true if the app bots have been started already + def app_bots_running_already?(_app) + # TODO: look a what the app level bots are (via app.app_level_bots) + # Fetch the list of running app bots for app.id from docker + # If eq, all app bots are running, return true + false + end + # Starts app level bots def start_run_level_bots(app) # TODO: implement diff --git a/lib/config/app.rb b/lib/config/nutella_app.rb similarity index 98% rename from lib/config/app.rb rename to lib/config/nutella_app.rb index 072e275..637059f 100644 --- a/lib/config/app.rb +++ b/lib/config/nutella_app.rb @@ -2,7 +2,7 @@ # Models a nutella application module Nutella - class App + class NutellaApp attr_reader :path attr_reader :id attr_reader :config diff --git a/spec/bots/commands_server/start_spec.rb b/spec/bots/commands_server/start_spec.rb index 98d03f8..1bcd1de 100644 --- a/spec/bots/commands_server/start_spec.rb +++ b/spec/bots/commands_server/start_spec.rb @@ -22,7 +22,7 @@ module CommandsServer end context 'error parsing run_id' do - before { allow(Nutella::App).to receive(:exist?).and_return(true) } + before { allow(Nutella::NutellaApp).to receive(:exist?).and_return(true) } let(:opts) { { 'args' => ['default'] } } subject { @start.run(opts) } it { is_expected.to eq(failure('Unfortunately you can\'t use `default` as a run_id because it is reserved :(', 'error')) } @@ -30,8 +30,8 @@ module CommandsServer context 'app bots already running' do before do - allow(Nutella::App).to receive(:exist?).and_return(true) - allow_any_instance_of(Nutella::App).to receive(:components_in_dir).and_return([]) + allow(Nutella::NutellaApp).to receive(:exist?).and_return(true) + allow_any_instance_of(Nutella::NutellaApp).to receive(:components_in_dir).and_return([]) allow(@start).to receive(:app_bots_running_already?).and_return(true) end let(:opts) { { 'current_directory': 'whatever' } } diff --git a/spec/config/runlist_spec.rb b/spec/config/runlist_spec.rb index 4fd6424..e4a9e0e 100644 --- a/spec/config/runlist_spec.rb +++ b/spec/config/runlist_spec.rb @@ -16,17 +16,17 @@ module Nutella end describe '#add?' do - subject { @rl.add?('app_id', 'run_id', 'path_(to_files') } + subject { @rl.add?('path/to/app', 'app_id', 'run_id') } context 'when list is empty' do it { is_expected.to be true } end context 'when list is not empty' do context 'and run exists' do - before { @rl.add?('app_id', 'run_id', 'path/to/files') } + before { @rl.add?('path/to/app', 'app_id', 'run_id') } it { is_expected.to be false } end context 'and run does not exist' do - before { @rl.add?('app_id', 'different', 'path/to/files') } + before { @rl.add?('path/to/app', 'app_id', 'different') } it { is_expected.to be true } end end @@ -37,8 +37,8 @@ module Nutella it { expect(@rl.all_apps).to be_empty } end context 'when list is not empty' do - let(:want) { { 'app_id': { runs: ['run_id'], path: 'path/to/files' } } } - before { @rl.add?('app_id', 'run_id', 'path/to/files') } + let(:want) { { 'app_id': { runs: ['run_id'], path: 'path/to/app' } } } + before { @rl.add?('path/to/app', 'app_id', 'run_id') } it { expect(@rl.all_apps).to eq(['app_id']) } end end From 9f0d106d411cba0a7b1717d9101e0773d3377401 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 14 Dec 2019 12:15:56 -0800 Subject: [PATCH 38/43] Complete start command --- lib/bots/commands_server/commands/start.rb | 47 +++++------ lib/cli/commands/server/framework_bots.rb | 34 ++++---- lib/util/docker_bot_starter.rb | 91 ++++++++++++++++++++++ lib/{config => util}/nutella_app.rb | 0 spec/bots/commands_server/start_spec.rb | 2 +- 5 files changed, 135 insertions(+), 39 deletions(-) create mode 100644 lib/util/docker_bot_starter.rb rename lib/{config => util}/nutella_app.rb (100%) diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index 17e3488..6ce35c9 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -3,7 +3,8 @@ require 'docker-api' require 'config/config' require 'config/runlist' -require 'config/nutella_app' +require 'util/nutella_app' +require 'util/docker_bot_starter' require_relative 'meta/run_command' module CommandsServer @@ -25,12 +26,12 @@ def run(opts = nil) return failure(e.message, 'error') end # Check if there are actully bots that need to be started... - if app_bots_running_already?(app) && app.run_level_bots.empty? + if app_level_bots_started?(app.id) && app.run_level_bots.empty? return failure("Run #{run_id} not created!\n Your app bots are running already and your application has no run bots.\n No run was created. Are you sure that's what you wanted to do?", 'warn') end - if run_exist?(app.id, run_id) + if run_exists?(app.id, run_id) return failure("Impossible to start nutella app!\n An instance of this app with the same run_id is already running!\n You might want to kill it with 'nutella stop #{run_id}'", 'warn') @@ -41,7 +42,7 @@ def run(opts = nil) start_run_level_bots(app, run_id) runlist.add?(app.path, app.id, run_id) rescue StandardError => e - return failure(e.message, 'error') + return failure(e.message, 'error', e) end success(success_message(app, run_id)) end @@ -49,34 +50,32 @@ def run(opts = nil) private # Check that the run_id we are trying to start has not been started already - def run_exist?(app_id, run_id) - if Nutella.runlist.include?(app_id, run_id) - # If the run_id is already in the list, check that it is actually live - if Tmux.session_exist? Tmux.session_name(app_id, run_id) - console.error 'Impossible to start nutella app: an instance of this app with the same run_id is already running!' - console.error "You might want to kill it with 'nutella stop #{run_id}'" - return true - end - end - false + def run_exists?(app_id, run_id) + runlist.include?(app_id, run_id) && run_level_bots_started?(app_id, run_id) + end + + # Checks if run level bots are running + def run_level_bots_started?(app_id, run_id) + !Docker::Container.all.select { |c| c.info['Names'][0].include?("nutella_r_#{app_id}_#{run_id}_") }.empty? end # Returns true if the app bots have been started already - def app_bots_running_already?(_app) - # TODO: look a what the app level bots are (via app.app_level_bots) - # Fetch the list of running app bots for app.id from docker - # If eq, all app bots are running, return true - false + def app_level_bots_started?(app_id) + !Docker::Container.all.select { |c| c.info['Names'][0].include?("nutella_a_#{app_id}_") }.empty? end # Starts app level bots - def start_run_level_bots(app) - # TODO: implement + def start_app_level_bots(app) + app.app_level_bots.each do |bot| + DockerBotStarter.new.start_app_level_bot(app, bot, true) + end end # Starts run level bots def start_run_level_bots(app, run_id) - # TODO: implement + app.run_level_bots.each do |bot| + DockerBotStarter.new.start_run_level_bot(app, run_id, bot, true) + end end def success_message(app, run_id) @@ -101,6 +100,10 @@ def runlist end end +# nutella_f_botname Framework-level bots (1 per server) +# nutella_a_appname_botname App-level bots (1 per app, need to namespace with app) +# nutella_r_appname_runid_botname Run-level bots (1 per run) + # Executes a code block for each component in a certain directory # @param [String] dir directory where we are iterating # @yield [component] Passes the component name to the block diff --git a/lib/cli/commands/server/framework_bots.rb b/lib/cli/commands/server/framework_bots.rb index 83a3931..f5ae73b 100755 --- a/lib/cli/commands/server/framework_bots.rb +++ b/lib/cli/commands/server/framework_bots.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'docker-api' require 'socket' require 'config/config' +require 'util/docker_bot_starter' module Nutella class FrameworkBots - def self.start FrameworkBots.new.start end @@ -13,16 +15,21 @@ def self.stop FrameworkBots.new.stop end - # Starts all framework bots. + # Starts all framework bots. # @return [boolean] true if all bots are started correctly, false otherwise def start - result = true framework_bots.each do |bot| - unless bot_started?(bot) - result && start_bot(bot) + next if bot_started?(bot) + + begin + DockerBotStarter.new.start_framework_level_bot(bot) + rescue StandardError => e + console.error "Failed to start #{bot}!" + puts e + return false end end - result + true end def stop @@ -44,9 +51,7 @@ def bot_started?(bot_name) true end - def start_bot(bot_name) - bot_container_name = "nutella_f_#{bot_name}" - bot_dir = "#{Nutella::NUTELLA_SRC}lib/bots/#{bot_name}" + def start_bot(bot_name, bot_container_name, bot_dir) # Remove any other containers with the same name to avoid conflicts begin old_c = Docker::Container.get(bot_container_name) @@ -67,10 +72,10 @@ def start_bot(bot_name) 'Detach': true, 'HostConfig': { 'Binds': ["#{bot_dir}:/app"], - 'RestartPolicy': {'Name': 'unless-stopped'} + 'RestartPolicy': { 'Name': 'unless-stopped' } } ).start - rescue => e + rescue StandardError => e console.error "Failed to start #{bot_name}!" puts e return false @@ -82,11 +87,8 @@ def start_bot(bot_name) def framework_bots d = "#{Nutella::NUTELLA_SRC}lib/bots" Dir.entries(d) - .select {|entry| File.directory?(File.join(d, entry)) && !(entry =='.' || entry == '..') } - .select { |c| File.exist? "#{d}/#{c}/startup.rb" } + .select { |entry| File.directory?(File.join(d, entry)) && !(entry == '.' || entry == '..') } + .select { |c| File.exist? "#{d}/#{c}/startup.rb" } end - end end - - diff --git a/lib/util/docker_bot_starter.rb b/lib/util/docker_bot_starter.rb new file mode 100644 index 0000000..ca1a3af --- /dev/null +++ b/lib/util/docker_bot_starter.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'docker-api' + +module Nutella + class DockerBotStarter + def start_framework_level_bot(name) + container_name = "nutella_f_#{name}" + dir = "#{Nutella::NUTELLA_SRC}lib/bots/#{bot_name}" + start_bot(:framework, name, container_name, bot_dir, true) + end + + def start_app_level_bot(app, bot_name, restart = false) + container_name = "nutella_a_#{app.id}_#{bot_name}" + dir = "#{app.path}/bots/#{bot_name}" + start_bot(:app, bot_name, container_name, dir, restart, app.id) + end + + def start_run_level_bot(app, run_id, bot_name, restart = false) + container_name = "nutella_r_#{app.id}_#{run_id}_#{bot_name}" + dir = "#{app.path}/bots/#{bot_name}" + start_bot(:run, bot_name, container_name, dir, restart, app.id, run_id) + end + + private + + def start_bot(level, _name, container_name, dir, restart, app_id = nil, run_id = nil) + # Remove any other containers with the same name to avoid conflicts + begin + old_c = Docker::Container.get(container_name) + old_c.delete(force: true) + rescue Docker::Error::NotFoundError + # If the container is not there we just proceed + end + # Select runtime + # TODO error out on wrong runtime!!! + runtime = parse_runtime(dir) + cmd = build_cmd(runtime, level, app_id, run_id) + image = parse_image(runtime) + c = create_container(image, cmd, container_name, dir, restart) + c.start + end + + # Finds the runtime based on the suffix of startup script + def parse_runtime(dir) + return :ruby if File.file?("#{dir}/startup.rb") + return :js if File.file?("#{dir}/startup.js") + + :shell + end + + def parse_image(runtime) + map = { + ruby: 'nutella_ruby:1.0.0', + js: 'nutella_js:1.0.0' + } + map[runtime] + end + + # Builds the command based on the runtime and level of the bot + def build_cmd(runtime, level, app_id = nil, run_id = nil) + cmd = case runtime + when :ruby then ['ruby', 'startup.rb'] + when :js then ['node', 'startup.js'] + end + case level + when :framework then cmd << Config.file['broker'] + + when :app then cmd << [Config.file['broker'], app_id] + + when :run then cmd << [Config.file['broker'], app_id, run_id] + + end + end + + # Creates the right container based on runtime + def create_container(image, cmd, container_name, dir, restart) + host_config = { + 'Binds' => ["#{dir}:/app"] + } + host_config['RestartPolicy'] = { 'Name' => 'unless-stopped' } if restart + Docker::Container.create( + 'Cmd': cmd, + 'Image': image, + 'name': container_name, + 'Detach': true, + 'HostConfig': host_config + ) + end + end +end diff --git a/lib/config/nutella_app.rb b/lib/util/nutella_app.rb similarity index 100% rename from lib/config/nutella_app.rb rename to lib/util/nutella_app.rb diff --git a/spec/bots/commands_server/start_spec.rb b/spec/bots/commands_server/start_spec.rb index 1bcd1de..b1e5ea1 100644 --- a/spec/bots/commands_server/start_spec.rb +++ b/spec/bots/commands_server/start_spec.rb @@ -32,7 +32,7 @@ module CommandsServer before do allow(Nutella::NutellaApp).to receive(:exist?).and_return(true) allow_any_instance_of(Nutella::NutellaApp).to receive(:components_in_dir).and_return([]) - allow(@start).to receive(:app_bots_running_already?).and_return(true) + allow(@start).to receive(:app_level_bots_started?).and_return(true) end let(:opts) { { 'current_directory': 'whatever' } } it 'should return a failure' do From efab22a7c23d14c5976886010e3b2617d0035b5f Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 14 Dec 2019 13:21:24 -0800 Subject: [PATCH 39/43] Fix new command --- .../commands_server/commands/meta/command.rb | 6 ++-- lib/bots/commands_server/commands/new.rb | 8 ++--- lib/bots/commands_server/commands/start.rb | 4 +-- spec/bots/commands_server/new_spec.rb | 35 +++++++++++++++++++ spec/bots/commands_server/start_spec.rb | 14 ++------ 5 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 spec/bots/commands_server/new_spec.rb diff --git a/lib/bots/commands_server/commands/meta/command.rb b/lib/bots/commands_server/commands/meta/command.rb index e4a03c3..0d2e009 100755 --- a/lib/bots/commands_server/commands/meta/command.rb +++ b/lib/bots/commands_server/commands/meta/command.rb @@ -12,11 +12,11 @@ def run(_args = nil) console.error 'Running the generic command!!! WAT? https://www.destroyallsoftware.com/talks/wat' end - def success(message) - { success: true, message: message, message_level: 'success' } + def success(message, level = 'success') + { success: true, message: message, message_level: level } end - def failure(message, level, exception = nil) + def failure(message, level = 'error', exception = nil) if exception.nil? { success: false, message: message, message_level: level } else diff --git a/lib/bots/commands_server/commands/new.rb b/lib/bots/commands_server/commands/new.rb index b80d71a..c88e3cb 100755 --- a/lib/bots/commands_server/commands/new.rb +++ b/lib/bots/commands_server/commands/new.rb @@ -2,6 +2,7 @@ require_relative 'meta/command' require 'fileutils' +require 'json' module CommandsServer class New < Command @@ -9,7 +10,7 @@ class New < Command def run(opts = nil) # If no app name is provided, return an error - if opts['args'].empty? + if opts['args'].nil? || opts['args'].empty? return failure('You need to specify a name for your new application') end @@ -19,12 +20,9 @@ def run(opts = nil) # If it does, we looks into it to see if there is a nutella.json file and display # the proper error message app_dir = "#{opts['current_dir']}/#{app_id}" - puts app_dir if File.directory? app_dir - if File.exist? "#{app_dir}/nutella.json" + if File.file? "#{app_dir}/nutella.json" return failure("An application named #{app_id} already exists") - else - return failure("A directory named #{app_id} already exists") end end # If all seems good, generate the application skeleton diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index 6ce35c9..adf7c76 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -14,7 +14,7 @@ class Start < RunCommand def run(opts = nil) # If opts['current_dir'] is not a nutella application, return and error unless Nutella::NutellaApp.exist?(opts['current_dir']) - return failure('The current directory is not a nutella application', 'error') + return failure('The current directory is not a nutella application') end # Extract app path and id (i.e. name) @@ -23,7 +23,7 @@ def run(opts = nil) begin run_id = parse_run_id_from_args(opts['args']) rescue StandardError => e - return failure(e.message, 'error') + return failure(e.message) end # Check if there are actully bots that need to be started... if app_level_bots_started?(app.id) && app.run_level_bots.empty? diff --git a/spec/bots/commands_server/new_spec.rb b/spec/bots/commands_server/new_spec.rb new file mode 100644 index 0000000..4b0a5f8 --- /dev/null +++ b/spec/bots/commands_server/new_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'bots/commands_server/commands/new' +require 'fileutils' + +module CommandsServer + describe New do + let(:app_name) { 'specs_test_app' } + + describe '#run' do + before(:each) { @new = New.new } + + after(:each) { FileUtils.rm_rf(app_name) } + + context 'with empty args' do + let(:opts) { {} } + subject { @new.run(opts) } + it { is_expected.to eq(Command.new.failure('You need to specify a name for your new application')) } + end + + context 'with the right opts and args' do + let(:opts) { { 'current_dir' => Dir.pwd, 'args' => [app_name] } } + subject { @new.run(opts) } + + it { is_expected.to eq(Command.new.success("Your new nutella application #{app_name} is ready!")) } + + it 'creates the app skeleton' do + @new.run(opts) + expect(File.file?("#{opts['current_dir']}/#{app_name}/nutella.json")).to be true + end + end + end + end +end diff --git a/spec/bots/commands_server/start_spec.rb b/spec/bots/commands_server/start_spec.rb index b1e5ea1..3e1ee87 100644 --- a/spec/bots/commands_server/start_spec.rb +++ b/spec/bots/commands_server/start_spec.rb @@ -17,7 +17,7 @@ module CommandsServer context 'outside nutella app' do let(:opts) { { 'current_directory': 'whatever' } } it 'errors out' do - expect(@start.run(opts)).to eq(failure('The current directory is not a nutella application', 'error')) + expect(@start.run(opts)).to eq(Command.new.failure('The current directory is not a nutella application', 'error')) end end @@ -25,7 +25,7 @@ module CommandsServer before { allow(Nutella::NutellaApp).to receive(:exist?).and_return(true) } let(:opts) { { 'args' => ['default'] } } subject { @start.run(opts) } - it { is_expected.to eq(failure('Unfortunately you can\'t use `default` as a run_id because it is reserved :(', 'error')) } + it { is_expected.to eq(Command.new.failure('Unfortunately you can\'t use `default` as a run_id because it is reserved :(', 'error')) } end context 'app bots already running' do @@ -42,15 +42,5 @@ module CommandsServer # TODO: last part of start method end - - def failure(message, level, _exception = nil) - c = Command.new - c.failure(message, level, exception = nil) - end - - def success(message) - c = Command.new - c.success(message) - end end end From ee2f49038f77abd4df9089493307de897c638071 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 14 Dec 2019 15:23:09 -0800 Subject: [PATCH 40/43] Start working on spec for framework bots --- lib/bots/commands_server/commands/new.rb | 5 +- lib/bots/commands_server/commands/start.rb | 20 ++++-- lib/bots/commands_server/commands/stop.rb | 2 +- lib/bots/commands_server/commands/template.rb | 4 +- lib/cli/cli.rb | 28 ++++----- lib/cli/commands/checkup.rb | 61 ++++++++----------- lib/cli/commands/server.rb | 17 +++--- lib/util/{docker_bot_starter.rb => docker.rb} | 24 ++++++-- .../server => util}/framework_bots.rb | 35 ++++++----- lib/{cli/commands/server => util}/mongo.rb | 61 ++++++++++--------- .../commands/server => util}/mqtt_broker.rb | 55 +++++++++-------- spec/util/framework_bots_spec.rb | 19 ++++++ 12 files changed, 189 insertions(+), 142 deletions(-) rename lib/util/{docker_bot_starter.rb => docker.rb} (78%) rename lib/{cli/commands/server => util}/framework_bots.rb (79%) rename lib/{cli/commands/server => util}/mongo.rb (78%) rename lib/{cli/commands/server => util}/mqtt_broker.rb (81%) create mode 100644 spec/util/framework_bots_spec.rb diff --git a/lib/bots/commands_server/commands/new.rb b/lib/bots/commands_server/commands/new.rb index c88e3cb..d210673 100755 --- a/lib/bots/commands_server/commands/new.rb +++ b/lib/bots/commands_server/commands/new.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative 'meta/command' +require 'config/config' require 'fileutils' require 'json' @@ -41,9 +42,7 @@ def create_app(app_id, app_dir) config_file_hash = { name: app_id, version: '0.1.0', - # TODO: figure out how to do make this dynamic - # :nutella_version => File.open("#{Nutella::NUTELLA_SRC}VERSION", 'rb').read, - nutella_version: '2.0.0', + nutella_version: File.open("#{Nutella::Config.file['src_dir']}VERSION", 'rb').read, type: 'application', description: 'A quick description of your application' } diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index adf7c76..ae20c29 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -require 'docker-api' require 'config/config' require 'config/runlist' require 'util/nutella_app' -require 'util/docker_bot_starter' +require 'util/docker' require_relative 'meta/run_command' module CommandsServer @@ -19,12 +18,19 @@ def run(opts = nil) # Extract app path and id (i.e. name) app = Nutella::NutellaApp.new(opts['current_dir']) + # If there is an error parsing the run_id, return an error begin run_id = parse_run_id_from_args(opts['args']) rescue StandardError => e return failure(e.message) end + # Check if the brokers, mongo, and the framework level bots are running + unless stopped_framework_bots + return failure("It looks like the following framework level bots are NOT running:\n + #{stopped_framework_bots}\n + Try to stop/start them again with `nutella server stop` and `nutella server start`", 'error') + end # Check if there are actully bots that need to be started... if app_level_bots_started?(app.id) && app.run_level_bots.empty? return failure("Run #{run_id} not created!\n @@ -49,6 +55,12 @@ def run(opts = nil) private + # This method returns an array of framework-level bots that should be running but are not + # If the array is empty it means all framework-level bots are running correctly + def stopped_framework_bots + [] + end + # Check that the run_id we are trying to start has not been started already def run_exists?(app_id, run_id) runlist.include?(app_id, run_id) && run_level_bots_started?(app_id, run_id) @@ -67,14 +79,14 @@ def app_level_bots_started?(app_id) # Starts app level bots def start_app_level_bots(app) app.app_level_bots.each do |bot| - DockerBotStarter.new.start_app_level_bot(app, bot, true) + DockerClient.new.start_app_level_bot(app, bot, true) end end # Starts run level bots def start_run_level_bots(app, run_id) app.run_level_bots.each do |bot| - DockerBotStarter.new.start_run_level_bot(app, run_id, bot, true) + DockerClient.new.start_run_level_bot(app, run_id, bot, true) end end diff --git a/lib/bots/commands_server/commands/stop.rb b/lib/bots/commands_server/commands/stop.rb index 9cd8565..d62478b 100755 --- a/lib/bots/commands_server/commands/stop.rb +++ b/lib/bots/commands_server/commands/stop.rb @@ -61,7 +61,7 @@ def stop_app_bots(app_id) end def stop_framework_components - nutella_components_dir = "#{Nutella::NUTELLA_SRC}framework_components" + nutella_components_dir = "#{Config.file['src_dir']}framework_components" ComponentsList.for_each_component_in_dir nutella_components_dir do |component| pid_file_path = "#{nutella_components_dir}/#{component}/.pid" kill_process_with_pid pid_file_path diff --git a/lib/bots/commands_server/commands/template.rb b/lib/bots/commands_server/commands/template.rb index 177c2d0..e1b07b7 100755 --- a/lib/bots/commands_server/commands/template.rb +++ b/lib/bots/commands_server/commands/template.rb @@ -122,10 +122,10 @@ def create_template_files(json, template_dir) File.open("#{template_dir}/nutella.json", 'w') { |f| f.write(JSON.pretty_generate(json)) } # Add bot/interface specific files if json['type'] == 'bot' - FileUtils.copy(File.join(Nutella::NUTELLA_SRC, 'data/startup'), template_dir) + FileUtils.copy(File.join(Config.file['src_dir'], 'data/startup'), template_dir) end if json['type'] == 'interface' - FileUtils.copy(File.join(Nutella::NUTELLA_SRC, 'data/index.html'), template_dir) + FileUtils.copy(File.join(Config.file['src_dir'], 'data/index.html'), template_dir) end console.success "Template #{json['name']} created successfully!" end diff --git a/lib/cli/cli.rb b/lib/cli/cli.rb index a40a236..bb3d14f 100755 --- a/lib/cli/cli.rb +++ b/lib/cli/cli.rb @@ -1,14 +1,13 @@ +# frozen_string_literal: true + require_relative 'logger' # Require all the commands Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| require_relative "commands/#{File.basename(file, File.extname(file))}" end - module Nutella - class NutellaCLI - NUTELLA_LOGO = " _ _ _ | | | | | _ __ _ _| |_ ___| | | __ _ @@ -16,28 +15,28 @@ class NutellaCLI | | | | |_| | || __/ | | (_| | |_| |_|\\__,_|\\__\\___|_|_|\\__,_| " - + # Nutella entry point. Every time the "nutella" command is invoked this is # the method that gets called. # It reads the command line parameters and it invokes the right sub-command def self.run # Flush output immediately $stdout.sync = true - + # Read parameters args = ARGV.dup args.shift # Check that the command is not empty, if so, simply print the nutella logo command = ARGV.first - if command == nil + if command.nil? print_nutella_logo exit 0 end # If nutella is not ready to be used (i.e. nobody has invoked the "nutella checkup" command yet), # append warning/reminder message - if Config.file['ready'].nil? && command!='checkup' + if Config.file['ready'].nil? && command != 'checkup' console.warn 'Looks like this is a fresh installation of nutella. Please run \'nutella checkup\' to check all dependencies are installed.' end @@ -49,36 +48,35 @@ def self.run # This method executes a particular command # @param command [String] the name of the command # @param args [Array] command line parameters passed to the command - def self.execute_command(command, args=nil) + def self.execute_command(command, args = nil) # Check that the command exists and if it does, # execute its run method passing the args parameters if command_exists?(command) - Object::const_get("Nutella::#{command.capitalize}").new.run(args) + Object.const_get("Nutella::#{command.capitalize}").new.run(args) else console.error "Unknown command #{command}" end end - + # This method checks that a particular command exists # @return [Boolean] true if the command exists, false otherwise def self.command_exists?(command) - return Nutella.const_get("Nutella::#{command.capitalize}").is_a?(Class) && + Nutella.const_get("Nutella::#{command.capitalize}").is_a?(Class) && Nutella.const_get("Nutella::#{command.capitalize}").method_defined?(:run) rescue NameError - return false + false end # Print nutella logo def self.print_nutella_logo console.info(NUTELLA_LOGO) - nutella_version = File.open("#{Nutella::NUTELLA_SRC}VERSION", 'rb').read + nutella_version = File.open("#{Config.file['src_dir']}VERSION", 'rb').read console.info("Welcome to nutella version #{nutella_version}! For a complete lists of available commands type 'nutella help'\n") # If nutella is not ready to be used (i.e. nobody has invoked the "nutella checkup" command yet), # append warning/reminder message - if Config.file['ready'].nil? + if Config.file['ready'].nil? console.warn 'Looks like this is a fresh installation of nutella. Please run \'nutella checkup\' to check all dependencies are installed.' end end end - end diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index b35e52f..9c0931b 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'meta/command' require 'config/config' require 'semantic' @@ -6,12 +8,11 @@ module Nutella class Checkup < Command @description = 'Checks that all the framework dependencies are installed' - - def run( args=nil ) + def run(_args = nil) # First check that we have all the tools we need to run nutella return unless all_dependencies_installed? - + # Check if we have a local broker installed # and installs one if we don't return unless broker_docker_image_ready? @@ -23,17 +24,15 @@ def run( args=nil ) # Check that we have a nutella ruby image built # if not, build one return unless nutella_docker_image_ready? - + # Set ready flag in config.json Config.file['ready'] = true # Output success message console.success 'All systems go! You are ready to use nutella!' end - - - private - + + private def all_dependencies_installed? # Docker version lambda @@ -43,30 +42,32 @@ def all_dependencies_installed? # Git version lambda git_semver = lambda do out = `git --version` - out.slice!(0,12) - begin - semver = Semantic::Version.new(out[0..4]) - rescue + out.slice!(0, 12) + begin + semver = Semantic::Version.new(out[0..4]) + rescue StandardError semver = Semantic::Version.new(out[0..5]) end semver end # Check versions - return true if check_version?('docker', '17.0.0', docker_semver) && check_version?('git', '1.8.0', git_semver) + if check_version?('docker', '17.0.0', docker_semver) && check_version?('git', '1.8.0', git_semver) + return true + end + # If any of the checks fail, return false instead false end - - + def check_version?(dep, req_version, lambda) begin actual_version = lambda.call - rescue - console.warn "Doesn't look like #{dep} is installed in your system. " + - "Unfortunately nutella can't do much unless all the dependencies are installed :(" - return + rescue StandardError + console.warn "Doesn't look like #{dep} is installed in your system. " \ + "Unfortunately nutella can't do much unless all the dependencies are installed :(" + return end - required_version = Semantic::Version.new req_version + required_version = Semantic::Version.new req_version if actual_version < required_version console.warn "Your version of #{dep} is a little old (#{actual_version}). Nutella requires #{required_version}. Please upgrade!" false @@ -76,8 +77,7 @@ def check_version?(dep, req_version, lambda) end end - - # Checks that the broker image has been pulled and pulls it if not + # Checks that the broker image has been pulled and pulls it if not def broker_docker_image_ready? if broker_image_exists? console.info 'You have a local broker installed. Yay!' @@ -85,7 +85,7 @@ def broker_docker_image_ready? console.warn 'You don\'t seem to have a local broker installed so we are going to go ahead and install one for you. This might take some time...' begin install_local_broker - rescue => e + rescue StandardError => e puts e console.error 'Whoops...something went wrong while installing the broker, try running \'nutella checkup\' again' false @@ -95,14 +95,12 @@ def broker_docker_image_ready? true end - # Checks that: 1. The Docker image for the broker has been pulled and # 2. config.json has been correctly configured def broker_image_exists? Docker::Image.exist?('matteocollina/mosca:v2.3.0') && !Config.file['broker'].nil? end - def install_local_broker # Docker pull to install Docker::Image.create('fromImage': 'matteocollina/mosca:v2.3.0') @@ -114,7 +112,6 @@ def install_local_broker end end - # Checks that the mongo image has been pulled and pulls it if not def mongo_docker_image_ready? if mongo_image_exists? @@ -123,7 +120,7 @@ def mongo_docker_image_ready? console.warn 'You don\'t seem to have a mongo installed locally so we are going to go ahead and install it for you. This might take some time...' begin install_local_mongo - rescue => e + rescue StandardError => e puts e console.error 'Whoops...something went wrong while installing mongo, try running \'nutella checkup\' again' return false @@ -132,15 +129,13 @@ def mongo_docker_image_ready? end true end - - + # Checks that: 1. The Docker image for mongo has been pulled and # 2. config.json has been correctly configured def mongo_image_exists? Docker::Image.exist?('mongo:3.2.21') && !Config.file['mongo'].nil? end - def install_local_mongo # Docker pull to install Docker::Image.create('fromImage': 'mongo:3.2.21') @@ -152,7 +147,6 @@ def install_local_mongo end end - def nutella_docker_image_ready? if nutella_image_exists? console.info 'You have a nutella docker image ready. Yay!' @@ -160,7 +154,7 @@ def nutella_docker_image_ready? console.warn 'You don\'t seem to have a nutella docker image ready. We\'re gonna go ahead and build one for you. This might take some time...' begin build_nutella_docker_image - rescue => e + rescue StandardError => e puts e console.error 'Whoops...something went wrong while building the nutella docker image, try running \'nutella checkup\' again' return false @@ -177,9 +171,8 @@ def nutella_image_exists? # Builds a docker image that we can use to run framework level bots in a dockerized way def build_nutella_docker_image - img = Docker::Image.build_from_dir(NUTELLA_SRC, { 'dockerfile': 'Dockerfile.rubyimage' }) + img = Docker::Image.build_from_dir(Config.file['src_dir'], 'dockerfile': 'Dockerfile.rubyimage') img.tag('repo': 'nutella', 'tag': '1.0.0', force: true) end - end end diff --git a/lib/cli/commands/server.rb b/lib/cli/commands/server.rb index 2c284d8..b6a0976 100644 --- a/lib/cli/commands/server.rb +++ b/lib/cli/commands/server.rb @@ -1,23 +1,25 @@ +# frozen_string_literal: true + require_relative 'meta/command' -require_relative 'server/mqtt_broker' -require_relative 'server/mongo' -require_relative 'server/framework_bots' +require 'util/mqtt_broker' +require 'util/mongo' +require 'util/framework_bots' module Nutella class Server < Command @description = 'Starts the MQTT broker and the framework level bots' - def run(args=nil) + def run(_args = nil) if MQTTBroker.start console.success('MQTT broker started') else console.error('Failed to start MQTT broker') - end + end if Mongo.start console.success('Mongo started') else console.error('Failed to start Mongo') - end + end if FrameworkBots.start console.success('Framework-level bots started') else @@ -25,5 +27,4 @@ def run(args=nil) end end end - -end \ No newline at end of file +end diff --git a/lib/util/docker_bot_starter.rb b/lib/util/docker.rb similarity index 78% rename from lib/util/docker_bot_starter.rb rename to lib/util/docker.rb index ca1a3af..d0ba614 100644 --- a/lib/util/docker_bot_starter.rb +++ b/lib/util/docker.rb @@ -3,11 +3,15 @@ require 'docker-api' module Nutella - class DockerBotStarter - def start_framework_level_bot(name) - container_name = "nutella_f_#{name}" - dir = "#{Nutella::NUTELLA_SRC}lib/bots/#{bot_name}" - start_bot(:framework, name, container_name, bot_dir, true) + # This class contains a set of utility methods to start bots using DockerClient. + # The class can be used to start/stop bots and check the status of + # currently runnnig bots. + class DockerClient + # Starts a framework level bot. + def start_framework_level_bot(bot_name) + container_name = "nutella_f_#{bot_name}" + dir = "#{Config.file['src_dir']}lib/bots/#{bot_name}" + start_bot(:framework, bot_name, container_name, bot_dir, true) end def start_app_level_bot(app, bot_name, restart = false) @@ -22,6 +26,16 @@ def start_run_level_bot(app, run_id, bot_name, restart = false) start_bot(:run, bot_name, container_name, dir, restart, app.id, run_id) end + def container_running?(container) + begin + c = Docker::Container.get(container) + return c.info['State']['Running'] + rescue Docker::Error::NotFoundError + return false + end + true + end + private def start_bot(level, _name, container_name, dir, restart, app_id = nil, run_id = nil) diff --git a/lib/cli/commands/server/framework_bots.rb b/lib/util/framework_bots.rb similarity index 79% rename from lib/cli/commands/server/framework_bots.rb rename to lib/util/framework_bots.rb index f5ae73b..0079eca 100755 --- a/lib/cli/commands/server/framework_bots.rb +++ b/lib/util/framework_bots.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'docker-api' require 'socket' require 'config/config' -require 'util/docker_bot_starter' +require 'util/docker' module Nutella class FrameworkBots @@ -15,14 +14,23 @@ def self.stop FrameworkBots.new.stop end + def self.list + FrameworkBots.new.list_bots + end + + def self.running + FrameworkBots.new.list_running_bots + end + # Starts all framework bots. # @return [boolean] true if all bots are started correctly, false otherwise def start + dbs = DockerClient.new framework_bots.each do |bot| - next if bot_started?(bot) + next if dbs.container_running?("nutella_f_#{bot}") begin - DockerBotStarter.new.start_framework_level_bot(bot) + dbs.start_framework_level_bot(bot) rescue StandardError => e console.error "Failed to start #{bot}!" puts e @@ -39,18 +47,17 @@ def stop result end - private + def list_bots + framework_bots + end - def bot_started?(bot_name) - begin - c = Docker::Container.get("nutella_f_#{bot_name}") - return c.info['State']['Running'] - rescue Docker::Error::NotFoundError - return false - end - true + def list_running_bots + d = DockerClient.new + framework_bots.select { |bot| d.container_running?("nutella_f_#{bot}") } end + private + def start_bot(bot_name, bot_container_name, bot_dir) # Remove any other containers with the same name to avoid conflicts begin @@ -85,7 +92,7 @@ def start_bot(bot_name, bot_container_name, bot_dir) # Finds the framework level bots def framework_bots - d = "#{Nutella::NUTELLA_SRC}lib/bots" + d = "#{Config.file['src_dir']}lib/bots" Dir.entries(d) .select { |entry| File.directory?(File.join(d, entry)) && !(entry == '.' || entry == '..') } .select { |c| File.exist? "#{d}/#{c}/startup.rb" } diff --git a/lib/cli/commands/server/mongo.rb b/lib/util/mongo.rb similarity index 78% rename from lib/cli/commands/server/mongo.rb rename to lib/util/mongo.rb index 47595d0..1f4d515 100644 --- a/lib/cli/commands/server/mongo.rb +++ b/lib/util/mongo.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + require 'docker-api' require 'socket' require 'config/config' module Nutella class Mongo - def self.start Mongo.new.start_mongo_db end @@ -12,14 +13,19 @@ def self.start def self.stop Mongo.new.stop_mongo_db end - + + def self.started? + Mongo.new.mongo_started? + end + def start_mongo_db # Check if mongo has been started already return true if mongo_started? || mongo_started_unsupervised? + # Mongo is not running so we try to start it begin - start_mongo - rescue + start_mongo + rescue StandardError return false end # Wait until mongo is up @@ -40,27 +46,25 @@ def stop_mongo_db begin c.stop c.delete(force: true) - rescue + rescue StandardError return false end true end - - private - - def mongo_container_name - @mongo_container_name ||= 'mongodb' - end # Checks if mongo is running already # @return [boolean] true if there is a container for mongo running already def mongo_started? - begin - c = Docker::Container.get(mongo_container_name) - return c.info['State']['Running'] - rescue Docker::Error::NotFoundError - return false - end + c = Docker::Container.get(mongo_container_name) + c.info['State']['Running'] + rescue Docker::Error::NotFoundError + false + end + + private + + def mongo_container_name + @mongo_container_name ||= 'mongodb' end # Checks if port 27017 (MongoDB standard port) is free @@ -68,9 +72,9 @@ def mongo_started? # @return [boolean] true if there is no mongo listening on port 27017, false otherwise def mongo_started_unsupervised? begin - s = TCPServer.new('0.0.0.0', 27017) + s = TCPServer.new('0.0.0.0', 27_017) s.close - rescue + rescue StandardError return true end false @@ -91,11 +95,11 @@ def start_mongo 'name': mongo_container_name, 'Detach': true, 'HostConfig': { - 'PortBindings': { - '27017/tcp': [{ 'HostPort': '27017'}] + 'PortBindings': { + '27017/tcp': [{ 'HostPort': '27017' }] }, 'Binds': ["#{Config.file['home_dir']}mongo:/data/db"], - 'RestartPolicy': {'Name': 'unless-stopped'} + 'RestartPolicy': { 'Name': 'unless-stopped' } } ).start end @@ -103,14 +107,11 @@ def start_mongo # Checks if there is connectivity to localhost:27017. If not, # it waits 1/4 second and then tries again def wait_for_mongo - begin - s = TCPSocket.open('localhost', 27017) - s.close - rescue Errno::ECONNREFUSED - sleep 0.25 - wait_for_mongo - end + s = TCPSocket.open('localhost', 27_017) + s.close + rescue Errno::ECONNREFUSED + sleep 0.25 + wait_for_mongo end - end end diff --git a/lib/cli/commands/server/mqtt_broker.rb b/lib/util/mqtt_broker.rb similarity index 81% rename from lib/cli/commands/server/mqtt_broker.rb rename to lib/util/mqtt_broker.rb index 0c22315..24ed7da 100644 --- a/lib/cli/commands/server/mqtt_broker.rb +++ b/lib/util/mqtt_broker.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + require 'docker-api' require 'socket' require 'config/config' module Nutella class MQTTBroker - def self.start MQTTBroker.new.start_internal_broker end @@ -12,14 +13,19 @@ def self.start def self.stop MQTTBroker.new.stop_internal_broker end - + + def self.started? + MQTTBroker.new.broker_started? + end + def start_internal_broker # Check if the broker has been started already return true if broker_started? || broker_started_unsupervised? + # Broker is not running so we try to start it begin - start_broker - rescue + start_broker + rescue StandardError return false end # Wait until the broker is up @@ -32,7 +38,7 @@ def stop_internal_broker begin c = Docker::Container.get(broker_container_name) rescue Docker::Error::NotFoundError - # There is no container so the broker + # There is no container so the broker # is definitely not runnning, we're done return true end @@ -40,17 +46,11 @@ def stop_internal_broker begin c.stop c.delete(force: true) - rescue + rescue StandardError return false end true end - - private - - def broker_container_name - @broker_container_name ||= 'mqtt_broker' - end # Checks if the broker is running already # @return [boolean] true if there is a container for the broker running already @@ -64,6 +64,12 @@ def broker_started? true end + private + + def broker_container_name + @broker_container_name ||= 'mqtt_broker' + end + # Checks if port 1883 (MQTT broker port) is free # or some other service is already listening on it # @return [boolean] true if there is no broker listening on port 1883, false otherwise @@ -71,7 +77,7 @@ def broker_started_unsupervised? begin s = TCPServer.new('0.0.0.0', 1883) s.close - rescue + rescue StandardError return true end false @@ -92,12 +98,12 @@ def start_broker 'name': broker_container_name, 'Detach': true, 'HostConfig': { - 'PortBindings': { - '1883/tcp': [{ 'HostPort': '1883'}], - '80/tcp': [{ 'HostPort': '1884'}] + 'PortBindings': { + '1883/tcp': [{ 'HostPort': '1883' }], + '80/tcp': [{ 'HostPort': '1884' }] }, 'Binds': ["#{Config.file['home_dir']}broker:/db"], - 'RestartPolicy': {'Name': 'unless-stopped'} + 'RestartPolicy': { 'Name': 'unless-stopped' } } ).start end @@ -105,14 +111,11 @@ def start_broker # Checks if there is connectivity to localhost:1883. If not, # it waits 1/4 second and then tries again def wait_for_broker - begin - s = TCPSocket.open('localhost', 1883) - s.close - rescue Errno::ECONNREFUSED - sleep 0.25 - wait_for_broker - end + s = TCPSocket.open('localhost', 1883) + s.close + rescue Errno::ECONNREFUSED + sleep 0.25 + wait_for_broker end - end -end \ No newline at end of file +end diff --git a/spec/util/framework_bots_spec.rb b/spec/util/framework_bots_spec.rb new file mode 100644 index 0000000..53367f2 --- /dev/null +++ b/spec/util/framework_bots_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'nutella_framework' +require 'util/framework_bots' +module Nutella + describe FrameworkBots do + describe '.list' do + it 'returns the list of framework level bots' do + puts FrameworkBots.list + end + end + describe '.running' do + it 'returns the list of running framework level bots' do + puts FrameworkBots.running + end + end + end +end From bdddf154dfecbe515865ef4ce5976e2019a9482e Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 14 Dec 2019 18:25:30 -0800 Subject: [PATCH 41/43] Add image for js --- .gitignore | 1 - Dockerfile.jsimage | 3 + Gemfile.lock | 133 +++++++++++++++++++++++++++++++ lib/cli/commands/checkup.rb | 20 +++-- spec/util/framework_bots_spec.rb | 14 ++-- 5 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 Dockerfile.jsimage create mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index 742ee38..537fb00 100755 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ rdoc/ doc/ .yardoc .bundle -Gemfile.lock spec/examples.txt .gem diff --git a/Dockerfile.jsimage b/Dockerfile.jsimage new file mode 100644 index 0000000..d0ad70a --- /dev/null +++ b/Dockerfile.jsimage @@ -0,0 +1,3 @@ +# Builds an image to run js bots +FROM node:13.3.0 +WORKDIR /app \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..3e73802 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,133 @@ +GEM + remote: http://rubygems.org/ + specs: + addressable (2.4.0) + ansi (1.5.0) + awesome_print (1.8.0) + bson (3.2.7) + builder (3.2.4) + coderay (1.1.2) + daemons (1.3.1) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) + diff-lcs (1.3) + docker-api (1.34.2) + excon (>= 0.47.0) + multi_json + eventmachine (1.2.7) + excon (0.71.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + fuubar (2.5.0) + rspec-core (~> 3.0) + ruby-progressbar (~> 1.4) + git (1.5.0) + github_api (0.16.0) + addressable (~> 2.4.0) + descendants_tracker (~> 0.0.4) + faraday (~> 0.8, < 0.10) + hashie (>= 3.4) + mime-types (>= 1.16, < 3.0) + oauth2 (~> 1.0) + hashie (4.0.0) + highline (2.0.3) + jeweler (2.3.9) + builder + bundler + git (>= 1.2.5) + github_api (~> 0.16.0) + highline (>= 1.6.15) + nokogiri (>= 1.5.10) + psych + rake + rdoc + semver2 + jwt (2.2.1) + method_source (0.9.2) + mime-types (2.99.3) + mini_portile2 (2.4.0) + mongo (2.1.2) + bson (~> 3.0) + mqtt (0.5.0) + multi_json (1.14.1) + multi_xml (0.6.0) + multipart-post (2.1.1) + nokogiri (1.10.7) + mini_portile2 (~> 2.4.0) + nutella_lib (0.5.0) + ansi (~> 1.5, >= 1.5) + mongo (~> 2.0) + mqtt (~> 0.5, >= 0.5) + oauth2 (1.4.2) + faraday (>= 0.8, < 2.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + psych (3.1.0) + rack (1.6.11) + rack-protection (1.5.5) + rack + rake (13.0.1) + rdoc (4.3.0) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.0) + rspec-support (~> 3.9.0) + rspec-expectations (3.9.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.0) + ruby-progressbar (1.10.1) + semantic (1.6.1) + semver2 (3.4.2) + sinatra (1.4.8) + rack (~> 1.5) + rack-protection (~> 1.4) + tilt (>= 1.3, < 3) + sinatra-cross_origin (0.3.2) + thin (1.7.2) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (>= 1, < 3) + thread_safe (0.3.6) + tilt (2.0.10) + yard (0.9.20) + +PLATFORMS + ruby + +DEPENDENCIES + ansi (~> 1.5) + awesome_print (~> 1.8) + bson (~> 3.0) + bundler (~> 2.0) + docker-api (~> 1.34) + fuubar (~> 2.4) + git (~> 1.2) + jeweler (~> 2.3) + nokogiri (~> 1.6) + nutella_lib (~> 0.5) + pry (~> 0.12.2) + rake + rdoc (~> 4.0) + rspec (~> 3.8) + semantic (~> 1.4) + sinatra (~> 1.4) + sinatra-cross_origin (~> 0.3.2) + thin (~> 1.6) + yard (~> 0.9.11) + +RUBY VERSION + ruby 2.3.8p459 + +BUNDLED WITH + 2.0.2 diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index 9c0931b..0d1f9c5 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -23,7 +23,7 @@ def run(_args = nil) # Check that we have a nutella ruby image built # if not, build one - return unless nutella_docker_image_ready? + return unless nutella_docker_images_ready? # Set ready flag in config.json Config.file['ready'] = true @@ -147,13 +147,13 @@ def install_local_mongo end end - def nutella_docker_image_ready? - if nutella_image_exists? + def nutella_docker_images_ready? + if nutella_images_exist? console.info 'You have a nutella docker image ready. Yay!' else console.warn 'You don\'t seem to have a nutella docker image ready. We\'re gonna go ahead and build one for you. This might take some time...' begin - build_nutella_docker_image + build_nutella_docker_images rescue StandardError => e puts e console.error 'Whoops...something went wrong while building the nutella docker image, try running \'nutella checkup\' again' @@ -165,14 +165,18 @@ def nutella_docker_image_ready? end # Checks that the nutella image exists and if not tries to build it - def nutella_image_exists? - Docker::Image.exist?('nutella:1.0.0') + def nutella_images_exist? + version = File.open("#{Config.file['src_dir']}VERSION", 'rb').read + Docker::Image.exist?("nutella_rb:#{version}") && Docker::Image.exist?("nutella_js:#{version}") end # Builds a docker image that we can use to run framework level bots in a dockerized way - def build_nutella_docker_image + def build_nutella_docker_images + version = File.open("#{Config.file['src_dir']}VERSION", 'rb').read img = Docker::Image.build_from_dir(Config.file['src_dir'], 'dockerfile': 'Dockerfile.rubyimage') - img.tag('repo': 'nutella', 'tag': '1.0.0', force: true) + img.tag('repo': 'nutella_rb', 'tag': version, force: true) + img = Docker::Image.build_from_dir(Config.file['src_dir'], 'dockerfile': 'Dockerfile.jsimage') + img.tag('repo': 'nutella_js', 'tag': version, force: true) end end end diff --git a/spec/util/framework_bots_spec.rb b/spec/util/framework_bots_spec.rb index 53367f2..740ac0d 100644 --- a/spec/util/framework_bots_spec.rb +++ b/spec/util/framework_bots_spec.rb @@ -3,17 +3,21 @@ require 'spec_helper' require 'nutella_framework' require 'util/framework_bots' + module Nutella describe FrameworkBots do describe '.list' do - it 'returns the list of framework level bots' do - puts FrameworkBots.list - end + subject { FrameworkBots.list } + it { is_expected.to include('commands_server') } end + describe '.running' do - it 'returns the list of running framework level bots' do - puts FrameworkBots.running + before do + allow_any_instance_of(FrameworkBots).to receive(:framework_bots) { %w[bot1 bot2] } + allow_any_instance_of(DockerClient).to receive(:container_running?).and_return(true, false) end + subject { FrameworkBots.running } + it { is_expected.to eq(['bot1']) } end end end From 823980da1b6bf6507e2d2b42c0787f8931bec054 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Sat, 14 Dec 2019 19:11:02 -0800 Subject: [PATCH 42/43] Make docker image for js --- lib/cli/cli.rb | 3 +-- lib/cli/commands/checkup.rb | 13 +++++----- lib/cli/commands/server.rb | 42 ++++++++++++++++++++++---------- lib/nutella_framework.rb | 9 +++---- lib/util/docker.rb | 15 ++++++------ lib/util/version.rb | 9 +++++++ spec/cli/commands/server_spec.rb | 17 ++++++++++--- spec/util/version_spec.rb | 13 ++++++++++ 8 files changed, 83 insertions(+), 38 deletions(-) create mode 100644 lib/util/version.rb create mode 100644 spec/util/version_spec.rb diff --git a/lib/cli/cli.rb b/lib/cli/cli.rb index bb3d14f..b9fa6e9 100755 --- a/lib/cli/cli.rb +++ b/lib/cli/cli.rb @@ -70,8 +70,7 @@ def self.command_exists?(command) # Print nutella logo def self.print_nutella_logo console.info(NUTELLA_LOGO) - nutella_version = File.open("#{Config.file['src_dir']}VERSION", 'rb').read - console.info("Welcome to nutella version #{nutella_version}! For a complete lists of available commands type 'nutella help'\n") + console.info("Welcome to nutella version #{Version.get}! For a complete lists of available commands type 'nutella help'\n") # If nutella is not ready to be used (i.e. nobody has invoked the "nutella checkup" command yet), # append warning/reminder message if Config.file['ready'].nil? diff --git a/lib/cli/commands/checkup.rb b/lib/cli/commands/checkup.rb index 0d1f9c5..74bb88d 100755 --- a/lib/cli/commands/checkup.rb +++ b/lib/cli/commands/checkup.rb @@ -4,6 +4,7 @@ require 'config/config' require 'semantic' require 'docker-api' +require 'util/version' module Nutella class Checkup < Command @@ -151,12 +152,12 @@ def nutella_docker_images_ready? if nutella_images_exist? console.info 'You have a nutella docker image ready. Yay!' else - console.warn 'You don\'t seem to have a nutella docker image ready. We\'re gonna go ahead and build one for you. This might take some time...' + console.warn 'You don\'t seem to have all the required nutella docker images. We\'re gonna go ahead and build them for you. This might take some time...' begin build_nutella_docker_images rescue StandardError => e puts e - console.error 'Whoops...something went wrong while building the nutella docker image, try running \'nutella checkup\' again' + console.error 'Whoops...something went wrong while building the nutella docker images. There should be some output above to help you out.' return false end console.info 'nutella docker image built successfully!' @@ -166,17 +167,15 @@ def nutella_docker_images_ready? # Checks that the nutella image exists and if not tries to build it def nutella_images_exist? - version = File.open("#{Config.file['src_dir']}VERSION", 'rb').read - Docker::Image.exist?("nutella_rb:#{version}") && Docker::Image.exist?("nutella_js:#{version}") + Docker::Image.exist?("nutella_rb:#{Version.get}") && Docker::Image.exist?("nutella_js:#{Version.get}") end # Builds a docker image that we can use to run framework level bots in a dockerized way def build_nutella_docker_images - version = File.open("#{Config.file['src_dir']}VERSION", 'rb').read img = Docker::Image.build_from_dir(Config.file['src_dir'], 'dockerfile': 'Dockerfile.rubyimage') - img.tag('repo': 'nutella_rb', 'tag': version, force: true) + img.tag('repo': 'nutella_rb', 'tag': Version.get, force: true) img = Docker::Image.build_from_dir(Config.file['src_dir'], 'dockerfile': 'Dockerfile.jsimage') - img.tag('repo': 'nutella_js', 'tag': version, force: true) + img.tag('repo': 'nutella_js', 'tag': Version.get, force: true) end end end diff --git a/lib/cli/commands/server.rb b/lib/cli/commands/server.rb index b6a0976..9074553 100644 --- a/lib/cli/commands/server.rb +++ b/lib/cli/commands/server.rb @@ -9,21 +9,37 @@ module Nutella class Server < Command @description = 'Starts the MQTT broker and the framework level bots' - def run(_args = nil) - if MQTTBroker.start - console.success('MQTT broker started') - else - console.error('Failed to start MQTT broker') - end - if Mongo.start - console.success('Mongo started') - else - console.error('Failed to start Mongo') + def run(args = nil) + if args.nil? || (args[0] != 'start' && args[0] != 'stop') + console.error('You need to specify either start or stop') + return end - if FrameworkBots.start - console.success('Framework-level bots started') + if args[0] == 'start' + if MQTTBroker.start + console.success('MQTT broker started') + else + console.error('Failed to start MQTT broker') + return + end + if Mongo.start + console.success('Mongo started') + else + console.error('Failed to start Mongo') + return + end + if FrameworkBots.start + console.success('Framework-level bots started') + else + console.error('Failed to start Framework-level bots') + return + end else - console.error('Failed to start Framework-level bots') + FrameworkBots.stop + console.success('Framework-level bots stopped') + Mongo.stop + console.success('Mongo stopped') + MQTTBroker.stop + console.success('MQTT broker stopped') end end end diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index 443f046..c735733 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require 'config/config' require 'cli/cli' +require 'util/version' module Nutella - # Initialize nutella home to the folder where this source code is NUTELLA_SRC = File.dirname(__FILE__)[0..-4] NUTELLA_TMP = "#{NUTELLA_SRC}.tmp/" @@ -10,8 +12,5 @@ module Nutella # If the nutella configuration file (config.json) is empty (or doesn't exist) we're going to initialize it # with nutella constants and defaults - if Config.file.empty? - Config.init - end - + Config.init if Config.file.empty? end diff --git a/lib/util/docker.rb b/lib/util/docker.rb index d0ba614..f63f0f3 100644 --- a/lib/util/docker.rb +++ b/lib/util/docker.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'docker-api' +require 'util/version' module Nutella # This class contains a set of utility methods to start bots using DockerClient. @@ -10,20 +11,20 @@ class DockerClient # Starts a framework level bot. def start_framework_level_bot(bot_name) container_name = "nutella_f_#{bot_name}" - dir = "#{Config.file['src_dir']}lib/bots/#{bot_name}" + bot_dir = "#{Config.file['src_dir']}lib/bots/#{bot_name}" start_bot(:framework, bot_name, container_name, bot_dir, true) end def start_app_level_bot(app, bot_name, restart = false) container_name = "nutella_a_#{app.id}_#{bot_name}" - dir = "#{app.path}/bots/#{bot_name}" - start_bot(:app, bot_name, container_name, dir, restart, app.id) + bot_dir = "#{app.path}/bots/#{bot_name}" + start_bot(:app, bot_name, container_name, bot_dir, restart, app.id) end def start_run_level_bot(app, run_id, bot_name, restart = false) container_name = "nutella_r_#{app.id}_#{run_id}_#{bot_name}" - dir = "#{app.path}/bots/#{bot_name}" - start_bot(:run, bot_name, container_name, dir, restart, app.id, run_id) + bot_dir = "#{app.path}/bots/#{bot_name}" + start_bot(:run, bot_name, container_name, bot_dir, restart, app.id, run_id) end def container_running?(container) @@ -65,8 +66,8 @@ def parse_runtime(dir) def parse_image(runtime) map = { - ruby: 'nutella_ruby:1.0.0', - js: 'nutella_js:1.0.0' + ruby: "nutella_rb:#{Version.get}", + js: "nutella_js:#{Version.get}" } map[runtime] end diff --git a/lib/util/version.rb b/lib/util/version.rb new file mode 100644 index 0000000..3358df3 --- /dev/null +++ b/lib/util/version.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Nutella + class Version + def self.get + File.open("#{Config.file['src_dir']}VERSION", 'rb').read + end + end +end diff --git a/spec/cli/commands/server_spec.rb b/spec/cli/commands/server_spec.rb index 34d2334..fb98272 100644 --- a/spec/cli/commands/server_spec.rb +++ b/spec/cli/commands/server_spec.rb @@ -1,11 +1,20 @@ +# frozen_string_literal: true + require 'spec_helper' require 'nutella_framework' module Nutella describe Server do - # Skipped because it interacts heavily with supervisor - it 'Starts the MQTT broker' do - NutellaCLI.execute_command('server') + context 'no parameters' do + it 'prints an error message' do + expect { NutellaCLI.execute_command('server') }.to output(/need to specify either start or stop/).to_stdout + end + end + + context 'start parameter' do + it 'starts all the things' do + NutellaCLI.execute_command('server', ['start']) + end end end -end \ No newline at end of file +end diff --git a/spec/util/version_spec.rb b/spec/util/version_spec.rb new file mode 100644 index 0000000..bd2852d --- /dev/null +++ b/spec/util/version_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'nutella_framework' + +module Nutella + describe Version do + describe '.get' do + subject { Version.get } + it { is_expected.to be_a(String) } + end + end +end From fbfb34f560dcae99346a29ddd9fbfb04227751d9 Mon Sep 17 00:00:00 2001 From: Alessandro Gnoli Date: Tue, 1 Jun 2021 16:24:19 -0700 Subject: [PATCH 43/43] daily --- Gemfile | 2 +- Gemfile.lock | 4 ++-- lib/bots/commands_server/commands/new.rb | 4 ++-- lib/bots/commands_server/commands/start.rb | 8 ++++---- lib/bots/commands_server/startup.rb | 21 ++++++++++++--------- lib/config/runlist.rb | 2 +- lib/nutella_framework.rb | 1 - lib/util/docker.rb | 22 +++++++++++++--------- 8 files changed, 35 insertions(+), 29 deletions(-) diff --git a/Gemfile b/Gemfile index 2b8301a..37e0cbe 100755 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ gem 'ansi', '~> 1.5' gem 'bson', '~> 3.0' gem 'docker-api', '~> 1.34' gem 'git', '~> 1.2' -gem 'nutella_lib','~>0.5' +gem 'nutella_lib','~>0.6' gem 'nokogiri', '~>1.6' gem 'semantic', '~> 1.4' gem 'sinatra', '~>1.4' diff --git a/Gemfile.lock b/Gemfile.lock index 3e73802..6c39f80 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -54,7 +54,7 @@ GEM multipart-post (2.1.1) nokogiri (1.10.7) mini_portile2 (~> 2.4.0) - nutella_lib (0.5.0) + nutella_lib (0.6.0) ansi (~> 1.5, >= 1.5) mongo (~> 2.0) mqtt (~> 0.5, >= 0.5) @@ -115,7 +115,7 @@ DEPENDENCIES git (~> 1.2) jeweler (~> 2.3) nokogiri (~> 1.6) - nutella_lib (~> 0.5) + nutella_lib (~> 0.6) pry (~> 0.12.2) rake rdoc (~> 4.0) diff --git a/lib/bots/commands_server/commands/new.rb b/lib/bots/commands_server/commands/new.rb index d210673..8c7ac2c 100755 --- a/lib/bots/commands_server/commands/new.rb +++ b/lib/bots/commands_server/commands/new.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative 'meta/command' -require 'config/config' require 'fileutils' require 'json' +require_relative 'meta/command' +require_relative '../../../config/config' module CommandsServer class New < Command diff --git a/lib/bots/commands_server/commands/start.rb b/lib/bots/commands_server/commands/start.rb index ae20c29..8a32feb 100755 --- a/lib/bots/commands_server/commands/start.rb +++ b/lib/bots/commands_server/commands/start.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'config/config' -require 'config/runlist' -require 'util/nutella_app' -require 'util/docker' require_relative 'meta/run_command' +require_relative '../../../config/config' +require_relative '../../../config/runlist' +require_relative '../../../util/nutella_app' +require_relative '../../../util/docker' module CommandsServer class Start < RunCommand diff --git a/lib/bots/commands_server/startup.rb b/lib/bots/commands_server/startup.rb index 8f155bd..921bace 100644 --- a/lib/bots/commands_server/startup.rb +++ b/lib/bots/commands_server/startup.rb @@ -1,9 +1,12 @@ +# frozen_string_literal: true + # Commands server # Connects to the MQTT broker, listens for commands (basically RPC over MQTT), # executes the commands, and returns the output to the client # This is the heart of nutella and implements all the nutella logic really require 'nutella_lib' + # Load all available commands Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file| require_relative "commands/#{File.basename(file, File.extname(file))}" @@ -15,7 +18,7 @@ nutella.f.init(broker, component_id) # Commands handler -nutella.f.net.handle_requests('commands', lambda do |message, component_id| +nutella.f.net.handle_requests('commands', lambda do |message, _component_id| nutella.log.info("[#{Time.now}] Command received: #{message}") execute_command(message['command'], message['opts']) end) @@ -23,27 +26,27 @@ # This function executes a particular command # @param command [String] the name of the command # @param opts [Hash] parameters passed to the command -def execute_command(command, opts=nil) +def execute_command(command, opts = nil) if command_exists?(command) begin - return Object::const_get("Nutella::#{command.capitalize}").new.run(opts) - rescue => e + return Object.const_get("Nutella::#{command.capitalize}").new.run(opts) + rescue StandardError => e puts e.backtrace return { success: false, message: "Unexpected failure of command #{command}", exception: e } end else - return { success: false, message: "Unknown command #{command}" } + { success: false, message: "Unknown command #{command}" } end end # This function checks that a particular command exists # @return [Boolean] true if the command exists, false otherwise def self.command_exists?(command) - return Nutella.const_get("Nutella::#{command.capitalize}").is_a?(Class) && + Nutella.const_get("Nutella::#{command.capitalize}").is_a?(Class) && Nutella.const_get("Nutella::#{command.capitalize}").method_defined?(:run) rescue NameError - return false + false end -nutella.log.success "Starting commands server..." -nutella.f.net.listen \ No newline at end of file +nutella.log.success 'Starting commands server...' +nutella.f.net.listen diff --git a/lib/config/runlist.rb b/lib/config/runlist.rb index e40caac..bf86358 100755 --- a/lib/config/runlist.rb +++ b/lib/config/runlist.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'config/persisted_hash' +require_relative 'persisted_hash' module Nutella # Manages the list of nutella applications and runs handled by the framework. diff --git a/lib/nutella_framework.rb b/lib/nutella_framework.rb index c735733..f6d44a8 100755 --- a/lib/nutella_framework.rb +++ b/lib/nutella_framework.rb @@ -2,7 +2,6 @@ require 'config/config' require 'cli/cli' -require 'util/version' module Nutella # Initialize nutella home to the folder where this source code is diff --git a/lib/util/docker.rb b/lib/util/docker.rb index f63f0f3..250caa6 100644 --- a/lib/util/docker.rb +++ b/lib/util/docker.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'docker-api' -require 'util/version' +require_relative 'version' module Nutella # This class contains a set of utility methods to start bots using DockerClient. @@ -11,7 +11,7 @@ class DockerClient # Starts a framework level bot. def start_framework_level_bot(bot_name) container_name = "nutella_f_#{bot_name}" - bot_dir = "#{Config.file['src_dir']}lib/bots/#{bot_name}" + bot_dir = Config.file['src_dir'] start_bot(:framework, bot_name, container_name, bot_dir, true) end @@ -39,7 +39,7 @@ def container_running?(container) private - def start_bot(level, _name, container_name, dir, restart, app_id = nil, run_id = nil) + def start_bot(level, bot_name, container_name, dir, restart, app_id = nil, run_id = nil) # Remove any other containers with the same name to avoid conflicts begin old_c = Docker::Container.get(container_name) @@ -50,7 +50,7 @@ def start_bot(level, _name, container_name, dir, restart, app_id = nil, run_id = # Select runtime # TODO error out on wrong runtime!!! runtime = parse_runtime(dir) - cmd = build_cmd(runtime, level, app_id, run_id) + cmd = build_cmd(runtime, level, bot_name, app_id, run_id) image = parse_image(runtime) c = create_container(image, cmd, container_name, dir, restart) c.start @@ -73,11 +73,15 @@ def parse_image(runtime) end # Builds the command based on the runtime and level of the bot - def build_cmd(runtime, level, app_id = nil, run_id = nil) - cmd = case runtime - when :ruby then ['ruby', 'startup.rb'] - when :js then ['node', 'startup.js'] - end + def build_cmd(runtime, level, bot_name, app_id = nil, run_id = nil) + cmd = if level == :framework + ['ruby', "lib/bots/#{bot_name}/startup.rb"] + else + case runtime + when :ruby then ['ruby', 'startup.rb'] + when :js then ['node', 'startup.js'] + end + end case level when :framework then cmd << Config.file['broker']