Commit f0838c29 by John Doe

added more robust error handling

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