Commit f0838c29 by John Doe

added more robust error handling

parent 7d7b0e32
...@@ -9,19 +9,36 @@ class DbAdapter ...@@ -9,19 +9,36 @@ class DbAdapter
end end
def dbinfo def dbinfo
version = self.class.get("#{@url}/version").parsed_response begin
info = self.class.get("#{@url}/dbinfo").parsed_response resp = self.class.get("#{@url}/version")
return nil unless resp.success?
version = resp.parsed_response
resp = self.class.get("#{@url}/dbinfo")
return nil unless resp.success?
info = resp.parsed_response
rescue
return nil
end
{ {
version: version, version: version,
size_db: info['size'], size_db: info['size'],
size_other: info['other'], size_other: info['other'],
size_total: info["size"]+info["other"]+info["free"]+info["reserved"] size_total: info['size'] + info['other'] + info['free'] + info['reserved']
} }
end end
def schema def schema
# GET extended info stream list # GET extended info stream list
dump = self.class.get("#{@url}/stream/list?extended=1") begin
dump.parsed_response.map do |entry| resp = self.class.get("#{@url}/stream/list?extended=1")
return nil unless resp.success?
rescue
return nil
end
resp.parsed_response.map do |entry|
metadata = if entry[0].match(UpdateStream.decimation_tag).nil? metadata = if entry[0].match(UpdateStream.decimation_tag).nil?
__get_metadata(entry[0]) __get_metadata(entry[0])
else else
...@@ -59,14 +76,16 @@ class DbAdapter ...@@ -59,14 +76,16 @@ class DbAdapter
def _set_path_metadata(path, data) def _set_path_metadata(path, data)
params = { path: path, params = { path: path,
data: data }.to_json data: data }.to_json
response = self.class.post("#{@url}/stream/update_metadata", begin
body: params, response = self.class.post("#{@url}/stream/update_metadata",
headers: { 'Content-Type' => 'application/json' }) body: params,
if response.code != 200 headers: { 'Content-Type' => 'application/json' })
Rails.logger.warn rescue
"#{@url}: update_metadata(#{path})"\ return { error: true, msg: 'cannot contact NilmDB server' }
" => #{response.code}:#{response.body}" end
unless response.success?
Rails.logger.warn("#{@url}: update_metadata(#{path})"+
" => #{response.code}:#{response.body}")
return { error: true, msg: "error updating #{path} metadata" } return { error: true, msg: "error updating #{path} metadata" }
end end
{ error: false, msg: 'success' } { error: false, msg: 'success' }
...@@ -86,8 +105,9 @@ class DbAdapter ...@@ -86,8 +105,9 @@ class DbAdapter
.slice('name', 'name_abbrev', 'description', 'hidden') .slice('name', 'name_abbrev', 'description', 'hidden')
# elements are called streams in the nilmdb metadata # elements are called streams in the nilmdb metadata
# and they don't have id or timestamp fields # and they don't have id or timestamp fields
attribs[:streams] = db_stream.db_elements.map {|e| attribs[:streams] = db_stream.db_elements.map do |e|
e.attributes.except("id","created_at","updated_at","db_stream_id")} e.attributes.except('id', 'created_at', 'updated_at', 'db_stream_id')
end
{ config_key__: attribs.to_json }.to_json { config_key__: attribs.to_json }.to_json
end end
......
...@@ -13,10 +13,11 @@ class DbFoldersController < ApplicationController ...@@ -13,10 +13,11 @@ class DbFoldersController < ApplicationController
adapter = DbAdapter.new(folder.db.url) adapter = DbAdapter.new(folder.db.url)
service = EditFolder.new(adapter) service = EditFolder.new(adapter)
service.run(folder, folder_params) service.run(folder, folder_params)
if(service.success?) if service.success?
render json: folder, shallow: false render json: {data: folder, messages: service}, shallow: false
else else
render json: service, status: :unprocessable_entity render json: {data: nil, messages: service},
status: :unprocessable_entity
end end
end end
......
...@@ -8,9 +8,10 @@ class DbStreamsController < ApplicationController ...@@ -8,9 +8,10 @@ class DbStreamsController < ApplicationController
service = EditStream.new(adapter) service = EditStream.new(adapter)
service.run(stream, stream_params) service.run(stream, stream_params)
if service.success? if service.success?
render json: stream render json: {data: stream, messages: service}
else else
render json: service, status: :unprocessable_entity render json: {data: nil, messages: service},
status: :unprocessable_entity
end end
end end
......
...@@ -14,7 +14,10 @@ class DbsController < ApplicationController ...@@ -14,7 +14,10 @@ class DbsController < ApplicationController
if(prev_url != db.url || params[:refresh]) if(prev_url != db.url || params[:refresh])
refresh(db) and return refresh(db) and return
end end
render json: db stub = StubService.new()
stub.add_notice("database updated")
render json: {data: db,
messages: stub}
else else
render json: "adfs" render json: "adfs"
end end
...@@ -27,9 +30,10 @@ class DbsController < ApplicationController ...@@ -27,9 +30,10 @@ class DbsController < ApplicationController
service = UpdateDb.new(db: db) service = UpdateDb.new(db: db)
service.run(adapter.dbinfo, adapter.schema) service.run(adapter.dbinfo, adapter.schema)
if(service.success?) if(service.success?)
render json: db render json: {data: db, messages: service}
else else
render json: service, status: :unprocessable_entity render json: {data: nil, messages: service},
status: :unprocessable_entity
end end
end end
......
...@@ -6,4 +6,9 @@ class NilmsController < ApplicationController ...@@ -6,4 +6,9 @@ class NilmsController < ApplicationController
nilms = Nilm.all nilms = Nilm.all
render json: nilms render json: nilms
end end
def show
nilm = Nilm.find(params[:id])
render json: nilm
end
end end
...@@ -7,7 +7,7 @@ class DbElement < ApplicationRecord ...@@ -7,7 +7,7 @@ class DbElement < ApplicationRecord
validates :name, presence: true validates :name, presence: true
validates :name, uniqueness: { scope: :db_stream_id, validates :name, uniqueness: { scope: :db_stream_id,
message: ' is already used in this folder'} message: ' is already used in this stream'}
validates :scale_factor, presence: true, numericality: true validates :scale_factor, presence: true, numericality: true
validates :scale_factor, presence: true, numericality: true validates :scale_factor, presence: true, numericality: true
......
...@@ -52,7 +52,7 @@ class DbStream < ApplicationRecord ...@@ -52,7 +52,7 @@ class DbStream < ApplicationRecord
self.description = '' self.description = ''
end end
def as_json(_options = {}) def as_json(_options = {})
stream = super(except: [:created_at, :updated_at]) stream = super(except: [:created_at, :updated_at])
stream[:elements] = db_elements.map(&:as_json) stream[:elements] = db_elements.map(&:as_json)
......
...@@ -6,6 +6,7 @@ class Nilm < ApplicationRecord ...@@ -6,6 +6,7 @@ class Nilm < ApplicationRecord
def as_json(_options = {}) def as_json(_options = {})
nilm = super(except: [:created_at, :updated_at]) nilm = super(except: [:created_at, :updated_at])
nilm[:available] = db.available
nilm[:db] = db.as_json() nilm[:db] = db.as_json()
nilm nilm
end end
......
...@@ -10,6 +10,17 @@ class UpdateDb ...@@ -10,6 +10,17 @@ class UpdateDb
end end
def run(dbinfo, schema) def run(dbinfo, schema)
# check to make sure dbinfo and schema are set
# if either is nil, the database is not available
if(dbinfo.nil? || schema.nil?)
add_error("cannot contact database at #{@db.url}")
@db.update_attributes(available: false)
return self
else
@db.available = true
end
# create the root folder if it doesn't exist # create the root folder if it doesn't exist
@db.root_folder ||= DbFolder.create(db: @db, name: 'root', path: '/') @db.root_folder ||= DbFolder.create(db: @db, name: 'root', path: '/')
@root_folder = @db.root_folder @root_folder = @db.root_folder
...@@ -26,6 +37,7 @@ class UpdateDb ...@@ -26,6 +37,7 @@ class UpdateDb
absorb_status(updater.run) absorb_status(updater.run)
@db.save @db.save
set_notice("Database refreshed")
self self
end end
......
...@@ -30,6 +30,7 @@ class EditFolder ...@@ -30,6 +30,7 @@ class EditFolder
end end
# everything went well, save the model # everything went well, save the model
db_folder.save! db_folder.save!
set_notice("Folder updated")
self self
end end
end end
...@@ -55,6 +55,7 @@ class UpdateFolder ...@@ -55,6 +55,7 @@ class UpdateFolder
byebug byebug
end end
@folder.save! @folder.save!
set_notice("Folder updated")
self self
end end
......
...@@ -30,7 +30,8 @@ class EditStream ...@@ -30,7 +30,8 @@ class EditStream
return self return self
end end
# everything went well, save the model # everything went well, save the model
db_stream.save db_stream.save!
set_notice("Stream updated")
self self
end end
......
...@@ -18,6 +18,7 @@ class UpdateStream ...@@ -18,6 +18,7 @@ class UpdateStream
def run def run
__update_stream(@stream, @base_entry, @decimation_entries) __update_stream(@stream, @base_entry, @decimation_entries)
set_notice("Stream updated")
self self
end end
......
# frozen_string_literal: true # frozen_string_literal: true
# Handles service errors and warnings. Design pattern: # Handles service notices, errors and warnings. Design pattern:
# All service action should occur in the run() function # All service action should occur in the run() function
# Within run, call add_error or add_warning with string # Within run, call add_error or add_warning with string
# messages. At the end of run() return the service object itself # messages. At the end of run() return the service object itself
...@@ -9,8 +9,11 @@ ...@@ -9,8 +9,11 @@
# the action parameter to NEVER_FAIL, FAIL_ON_WARNING, or # the action parameter to NEVER_FAIL, FAIL_ON_WARNING, or
# FAIL_ON_ERROR to determine when (if ever), absorb_status # FAIL_ON_ERROR to determine when (if ever), absorb_status
# returns false # returns false
# Services should set a notice message when run successfully
# eg "database updated"
#
module ServiceStatus module ServiceStatus
attr_reader :errors, :warnings attr_reader :errors, :warnings, :notices
FAIL_ON_ERROR = 0 FAIL_ON_ERROR = 0
FAIL_ON_WARNING = 1 FAIL_ON_WARNING = 1
...@@ -19,6 +22,18 @@ module ServiceStatus ...@@ -19,6 +22,18 @@ module ServiceStatus
def initialize def initialize
@errors = [] @errors = []
@warnings = [] @warnings = []
@notices = []
end
def add_notice(message)
# ignore duplicates
return if @notices.include?(message)
@notices << String(message)
end
# clear out notices, and set to current message
def set_notice(message)
@notices = [message]
end end
def add_error(message) def add_error(message)
...@@ -42,7 +57,7 @@ module ServiceStatus ...@@ -42,7 +57,7 @@ module ServiceStatus
end end
def success? def success?
!warnings? && !errors? !errors?
end end
def run def run
...@@ -50,6 +65,7 @@ module ServiceStatus ...@@ -50,6 +65,7 @@ module ServiceStatus
end end
def absorb_status(service, action: FAIL_ON_ERROR) def absorb_status(service, action: FAIL_ON_ERROR)
@notices += service.notices
@warnings += service.warnings @warnings += service.warnings
@errors += service.errors @errors += service.errors
case action case action
...@@ -63,6 +79,7 @@ module ServiceStatus ...@@ -63,6 +79,7 @@ module ServiceStatus
def as_json(_options = {}) def as_json(_options = {})
{ {
notices: @notices,
errors: @errors, errors: @errors,
warnings: @warnings warnings: @warnings
} }
......
# frozen_string_literal: true
# No run method, just implements messages
# used by controllers that need to return messsages but do not
# run a service (eg a simple update)
class StubService
include ServiceStatus
end
class AddAvailableToDb < ActiveRecord::Migration[5.0]
def change
add_column :dbs, :available, :boolean
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170127033854) do ActiveRecord::Schema.define(version: 20170130022828) do
create_table "db_decimations", force: :cascade do |t| create_table "db_decimations", force: :cascade do |t|
t.integer "start_time", limit: 8 t.integer "start_time", limit: 8
...@@ -83,6 +83,7 @@ ActiveRecord::Schema.define(version: 20170127033854) do ...@@ -83,6 +83,7 @@ ActiveRecord::Schema.define(version: 20170127033854) do
t.integer "size_other", limit: 8 t.integer "size_other", limit: 8
t.string "version" t.string "version"
t.integer "max_points_per_plot", default: 3600 t.integer "max_points_per_plot", default: 3600
t.boolean "available"
end end
create_table "nilms", force: :cascade do |t| create_table "nilms", force: :cascade do |t|
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
# generic DbStream # generic DbStream
FactoryGirl.define do FactoryGirl.define do
factory :db_element do factory :db_element do
db_stream
name { Faker::Lorem.word } name { Faker::Lorem.word }
scale_factor 1.0
offset 0.0
end end
end end
...@@ -22,9 +22,9 @@ RSpec.describe 'DbElement' do ...@@ -22,9 +22,9 @@ RSpec.describe 'DbElement' do
expect(element.errors[:name].any?).to be true expect(element.errors[:name].any?).to be true
end end
it 'name is unique in stream' do it 'name is unique in stream' do
stream = DbStream.create(name: 'parent') stream = FactoryGirl.create(:db_stream, name: 'parent')
elem1 = DbElement.create(name: 'shared', db_stream: stream) elem1 = FactoryGirl.create(:db_element, name: 'shared', db_stream: stream)
elem2 = DbElement.new(name: 'shared', db_stream: stream) elem2 = FactoryGirl.build(:db_element, name: 'shared', db_stream: stream)
elem2.validate elem2.validate
expect(elem2.errors[:name].any?).to be true expect(elem2.errors[:name].any?).to be true
# but if element is in a different stream its ok # but if element is in a different stream its ok
......
...@@ -6,7 +6,8 @@ RSpec.describe 'Db' do ...@@ -6,7 +6,8 @@ RSpec.describe 'Db' do
let(:db) { Db.new } let(:db) { Db.new }
specify { expect(db).to respond_to(:url) } specify { expect(db).to respond_to(:url) }
specify { expect(db).to respond_to(:root_folder) } specify { expect(db).to respond_to(:root_folder) }
specify { expect(db).to respond_to(:available) }
specify { expect(db).to respond_to(:size_db) } specify { expect(db).to respond_to(:size_db) }
specify { expect(db).to respond_to(:size_total) } specify { expect(db).to respond_to(:size_total) }
specify { expect(db).to respond_to(:size_other) } specify { expect(db).to respond_to(:size_other) }
......
...@@ -24,7 +24,7 @@ simple_db = [ ...@@ -24,7 +24,7 @@ simple_db = [
] ]
describe 'UpdateDb' do describe 'UpdateDb' do
let(:dbinfo) { double('dbinfo').as_null_object} let(:dbinfo) { {} }
describe '*run*' do describe '*run*' do
def update_with_schema(schema, db: nil) def update_with_schema(schema, db: nil)
@db = db || Db.new @db = db || Db.new
......
...@@ -5,7 +5,7 @@ require 'rails_helper' ...@@ -5,7 +5,7 @@ require 'rails_helper'
describe 'EditStream service' do describe 'EditStream service' do
let(:db_adapter) { instance_double(DbAdapter) } let(:db_adapter) { instance_double(DbAdapter) }
let(:stream) { FactoryGirl.create(:db_stream, path: '/stream/path', name: 'old') } let(:stream) { FactoryGirl.create(:db_stream, path: '/stream/path', name: 'old') }
let(:element) { DbElement.create(name: 'elem', db_stream: stream)} let(:element) { FactoryGirl.create(:db_element, db_stream: stream)}
let(:service) { EditStream.new(db_adapter) } let(:service) { EditStream.new(db_adapter) }
# db adapter return values # db adapter return values
let(:success) { { error: false, msg: '' } } let(:success) { { error: false, msg: '' } }
......
...@@ -6,7 +6,7 @@ describe 'UpdateStream service' do ...@@ -6,7 +6,7 @@ describe 'UpdateStream service' do
let(:db) { Db.new } let(:db) { Db.new }
let(:service) { UpdateDb.new(db: db) } let(:service) { UpdateDb.new(db: db) }
let(:helper) { DbSchemaHelper.new } let(:helper) { DbSchemaHelper.new }
let(:mock_dbinfo) { double('dbinfo').as_null_object} let(:mock_dbinfo) { {} }
def build_entry(path, start, last, rows, width) def build_entry(path, start, last, rows, width)
helper.entry(path, start_time: start, end_time: last, helper.entry(path, start_time: start, end_time: last,
...@@ -50,6 +50,10 @@ describe 'UpdateStream service' do ...@@ -50,6 +50,10 @@ describe 'UpdateStream service' do
schema = [helper.entry('/folder1/subfolder/stream', schema = [helper.entry('/folder1/subfolder/stream',
element_count: 1)] element_count: 1)]
schema[0][:elements][0][:name] = 'old_name' schema[0][:elements][0][:name] = 'old_name'
# add mandatory metadata ow it will be rejected
schema[0][:elements][0][:scale_factor] = 1.0
schema[0][:elements][0][:offset] = 0.0
service.run(mock_dbinfo, schema) service.run(mock_dbinfo, schema)
element = DbElement.find_by_name('old_name') element = DbElement.find_by_name('old_name')
expect(element).to be_present expect(element).to be_present
......
...@@ -6,7 +6,7 @@ describe 'UpdateFolder service' do ...@@ -6,7 +6,7 @@ describe 'UpdateFolder service' do
let(:db) { Db.new } let(:db) { Db.new }
let(:service) { UpdateDb.new(db: db) } let(:service) { UpdateDb.new(db: db) }
let(:helper) { DbSchemaHelper.new } let(:helper) { DbSchemaHelper.new }
let(:mock_dbinfo) { double('dbinfo').as_null_object } let(:mock_dbinfo) { {} }
def build_entry(path, start, last, rows, width) def build_entry(path, start, last, rows, width)
helper.entry(path, start_time: start, end_time: last, helper.entry(path, start_time: start, end_time: last,
......
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