Commit de5f3b52 by John Doe

added joule modules

parent 14a6cfba
class JouleModulesControllerController < ApplicationController
require 'uri'
class JouleModulesController < ApplicationController
before_action :authenticate_user!, only: [:show]
before_action :authorize_viewer, only: [:show]
# GET /joule_modules/<nilm_id>.json
def show
if(params[:refresh])
adapter = JouleAdapter.new(@nilm.url)
@service = UpdateJouleModules.new(@nilm)
@service.run(adapter.module_info)
else
@service = StubService.new
end
@joule_modules = @nilm.joule_modules
# create the unique URL for module proxy traffic
@url_template = "http://%s.modules.wattsworth.local"
render status: @service.success? ? :ok : :unprocessable_entity
end
private
def authorize_viewer
@nilm = Nilm.find(params[:id])
head :unauthorized unless current_user.views_nilm?(@nilm)
end
end
......@@ -9,7 +9,7 @@ class Db < ApplicationRecord
class_name: 'DbFolder',
dependent: :destroy
belongs_to :nilm
has_many :db_streams #flat map of all streams in database
#---Validations
validates :max_points_per_plot, numericality: { only_integer: true }
......
class JouleModule < ApplicationRecord
belongs_to :nilm
has_many :joule_pipes
has_many :joule_pipes, dependent: :destroy
def self.json_keys
[:name, :description, :web_interface, :exec_cmd,
:status, :pid]
end
end
......@@ -10,7 +10,7 @@ class Nilm < ApplicationRecord
has_many :user_groups, through: :permissions
has_many :data_views_nilms
has_many :data_views, through: :data_views_nilms
has_many :joule_modules, dependent: :destroy
#---Validations-----
validates :name, presence: true, uniqueness: true
validates :url, presence: true, uniqueness: true
......
......@@ -4,11 +4,51 @@
class UpdateJouleModules
include ServiceStatus
def initialize()
def initialize(nilm)
super()
@nilm = nilm
end
def run()
def run(module_info)
#module_info as returned by JouleAdapter
if module_info.nil?
add_error("unable to retrieve module information")
return self
end
#remove the previous modules
@nilm.joule_modules.destroy_all
module_info.each do |info|
@nilm.joule_modules << _build_module(info)
end
set_notice("refreshed modules")
self
end
def _build_module(info)
# create JouleModule and associated pipes from
# hash returned by the JouleAdapter.module_info
params = info.extract!(*JouleModule.json_keys)
m = JouleModule.new(params)
# link inputs to database streams
info[:input_paths].each do |name, path|
m.joule_pipes << JoulePipe.new(direction: 'input',
name: name,
db_stream: _retrieve_stream(path))
end
info[:output_paths].each do |name, path|
m.joule_pipes << JoulePipe.new(direction: 'output',
name: name,
db_stream: _retrieve_stream(path))
end
return m
end
def _retrieve_stream(path)
dbStream = @nilm.db.db_streams.find_by_path(path)
if dbStream.nil?
add_warning("[#{path}] not in database")
end
dbStream
end
end
json.data do
json.array! @joule_modules do |m|
json.extract! m, *JouleModule.json_keys
json.url @url_template % [m.id]
end
end
json.partial! "helpers/messages", service: @service
......@@ -32,7 +32,7 @@ module ControlPanel
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
# Add folders under the services and adapters directory
%w(data nilm db db_folder db_stream permission user_group user data_view).each do |service|
%w(data nilm db db_folder db_stream permission user_group user data_view joule_modules).each do |service|
config.autoload_paths << Rails.root.join("app/services/#{service}")
end
config.autoload_paths << Rails.root.join("app/adapters")
......
......@@ -10,6 +10,7 @@ Rails.application.routes.draw do
get 'home' #retrieve a user's home data view
end
end
resources :joule_modules, only: [:show]
resources :dbs, only: [:show, :update]
resources :db_folders, only: [:show, :update]
resources :db_streams, only: [:index, :update] do
......
......@@ -8,11 +8,14 @@ class CreateJouleModules < ActiveRecord::Migration[5.1]
t.string :status
t.integer :pid
t.string :joule_id
t.belongs_to :nilm, index: true
t.timestamps
end
create_table :joule_pipes do |t|
t.belongs_to :joule_pipe, index: true
t.belongs_to :joule_module, index: true
t.belongs_to :db_stream, index: true
t.string :name
t.string :direction
t.timestamps
end
......
......@@ -112,17 +112,22 @@ ActiveRecord::Schema.define(version: 20180224021655) do
t.string "status"
t.integer "pid"
t.string "joule_id"
t.integer "nilm_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["nilm_id"], name: "index_joule_modules_on_nilm_id"
end
create_table "joule_pipes", force: :cascade do |t|
t.integer "joule_pipe_id"
t.integer "joule_module_id"
t.integer "db_stream_id"
t.string "name"
t.string "direction"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["db_stream_id"], name: "index_joule_pipes_on_db_stream_id"
t.index ["joule_module_id"], name: "index_joule_pipes_on_joule_module_id"
t.index ["joule_pipe_id"], name: "index_joule_pipes_on_joule_pipe_id"
end
......
require 'rails_helper'
RSpec.describe JouleModulesControllerController, type: :controller do
end
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe JouleModulesController, type: :request do
let(:john) { create(:user, first_name: 'John') }
let(:steve) { create(:user, first_name: 'Steve') }
let(:john_nilm) { create(:nilm, name: "John's NILM", admins: [john]) }
let(:lab_nilm) { create(:nilm, name: 'Lab NILM', owners: [john]) }
let(:pete_nilm) { create(:nilm, name: "Pete's NILM", viewers: [john])}
# index action does not exist
describe 'GET show' do
context 'with any permissions' do
it 'returns the modules as json' do
# john has some permission on all 3 nilms
@auth_headers = john.create_new_auth_token
[pete_nilm, lab_nilm, john_nilm].each do |nilm|
get "/joule_modules/#{nilm.id}.json",
headers: @auth_headers
expect(response.status).to eq(200)
expect(response.header['Content-Type']).to include('application/json')
end
end
it 'refreshes modules when requested' do
@mock_adapter = double(JouleAdapter)
allow(JouleAdapter).to receive(:new).and_return(@mock_adapter)
expect(@mock_adapter).to receive(:module_info).and_return([])
@auth_headers = john.create_new_auth_token
get "/joule_modules/#{john_nilm.id}.json?refresh=1",
headers: @auth_headers
expect(response.status).to eq(200)
expect(response.header['Content-Type']).to include('application/json')
end
it 'injects the proxy URL parameter into the module json' do
test_module = create(:joule_module, name: 'test', description: 'sample')
john_nilm.joule_modules << test_module
get "/joule_modules/#{john_nilm.id}.json",
headers: john.create_new_auth_token
body = JSON.parse(response.body)
expect(body['data'][0]['name']).to eq(test_module.name)
expect(body['data'][0]['url']).to start_with("http://#{test_module.id}.modules")
end
end
context 'without permissions' do
it 'returns unauthorized' do
# steve does NOT have permissions on john_nilm
@auth_headers = steve.create_new_auth_token
get "/joule_modules/#{john_nilm.id}.json",
headers: @auth_headers
expect(response.status).to eq(401)
end
end
context 'without sign-in' do
it 'returns unauthorized' do
# no headers: nobody is signed in, deny all
get "/joule_modules/#{john_nilm.id}.json"
expect(response.status).to eq(401)
end
end
end
end
# frozen_string_literal: true
# Mock class to test clients
class MockJouleAdapter
attr_reader :module_info
def initialize
@module_info = []
end
def add_module(name, inputs={}, outputs={})
@module_info << {"name": name,
"description": "mock module",
"web_interface": true,
"exec_cmd": "/path/to/cmd",
"args": ["--message", "argval"],
"input_paths": inputs,
"output_paths": outputs,
"status": "running",
"pid": 26749,
"id": 3,
"socket": "/tmp/wattsworth.joule.2"}
end
end
FactoryGirl.define do
factory :joule_module do
name { Faker::Lorem.words(3).join(' ') }
description { Faker::Lorem.sentence }
exec_cmd '/path/to/cmd'
web_interface false
status 'running'
joule_id Faker::Number.number(3).to_i
end
end
......@@ -15,17 +15,14 @@ RSpec.describe JouleModule, type: :model do
it 'removes pipes when destroyed' do
@joule_module = JouleModule.create
@joule_module.joule_pipes << JoulePipe.create(
db_stream: DbStream.create,
db_stream: create(:db_stream),
direction: 'output')
expect(JouleModule.find_by_id(@joule_module.id).pipes.count).to equal 1
expect(JouleModule.find_by_id(@joule_module.id).joule_pipes.count).to equal 1
@joule_module.destroy
expect(JouleModule.count).to equal 0
# deletes associated pipes
expect(JoulePipe.count).to equal 0
# does not delete the streams
expect(DbStream.count).to equal 2
end
expect(DbStream.count).to equal 1
end
end
......@@ -8,6 +8,8 @@ RSpec.describe 'Nilm' do
specify { expect(nilm).to respond_to(:description) }
specify { expect(nilm).to respond_to(:url) }
specify { expect(nilm).to respond_to(:db) }
specify { expect(nilm).to respond_to(:joule_modules) }
end
it 'removes associated db when destroyed' do
......
# frozen_string_literal: true
require 'rails_helper'
describe 'UpdateJouleModules' do
it 'replaces existing modules with new ones' do
nilm = create(:nilm)
nilm.joule_modules << create(:joule_module, name: 'prev1')
nilm.joule_modules << create(:joule_module, name: 'prev2')
adapter = MockJouleAdapter.new
adapter.add_module("new1",inputs={i1: '/path/1'},
outputs={o1: '/path/2'})
adapter.add_module("new2",inputs={i1: '/path/3',i2: '/path/4'},
outputs={o1: '/path/5',o2: '/path/5'})
service = UpdateJouleModules.new(nilm)
service.run(adapter.module_info)
expect(service.success?).to be true
# new modules are in the database
expect(nilm.joule_modules.find_by_name('new1')).to be_present
expect(nilm.joule_modules.find_by_name('new2')).to be_present
# old ones are gone
expect(JouleModule.count).to eq 2
# pipes are updated as well
n1 = nilm.joule_modules.find_by_name('new1')
expect(n1.joule_pipes.count).to eq 2
n2 = nilm.joule_modules.find_by_name('new2')
expect(n2.joule_pipes.count).to eq 4
# old pipes are gone
expect(JoulePipe.count).to eq 6
end
it 'produces a warning if a stream is not in the database' do
nilm = create(:nilm)
adapter = MockJouleAdapter.new
adapter.add_module("module",outputs={output: '/missing/path'})
service = UpdateJouleModules.new(nilm)
service.run(adapter.module_info)
expect(service.warnings?).to be true
end
it 'links db_stream to the pipe if the stream is in the database' do
nilm = create(:nilm)
nilm.db.db_streams << create(:db_stream, path: '/matched/path1')
nilm.db.db_streams << create(:db_stream, path: '/matched/path2')
adapter = MockJouleAdapter.new
adapter.add_module("module",inputs={input: '/matched/path1'},
outputs={output: '/matched/path2'})
service = UpdateJouleModules.new(nilm)
service.run(adapter.module_info)
expect(service.warnings?).to be false
end
it 'returns error if Joule server is unavailable' do
nilm = Nilm.create(name: 'test', url: 'invalid')
mock_service = instance_double(UpdateDb,
run: StubService.new)
allow(UpdateDb).to receive(:new)
.and_return(mock_service)
service = UpdateNilm.new()
service.run(nilm)
expect(service.success?).to be false
expect(mock_service).to_not have_received(:run)
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment