[UNSTABLE][WIP] Need to finish moving files into correct dirs, coverage, and adding batch support to more resources. #squashThis

pull/915/head
Rick Carlino 2018-07-13 13:07:50 -05:00
parent 4134a893f6
commit e0a0f8f11e
10 changed files with 159 additions and 154 deletions

View File

@ -0,0 +1,25 @@
module Resources
DEVICE_REGEX = /device_\d*/
ACTIONS = [
DESTROY = "destroy"
]
RESOURCES = { # Because I don't trust Kernel.const_get
"DeviceConfig" => DeviceConfig,
"DiagnosticDump" => DiagnosticDump,
"FarmEvent" => FarmEvent,
"FarmwareInstallations" => FarmwareInstallations,
"Image" => Image,
"Log" => Log,
"Peripheral" => Peripheral,
"PinBinding" => PinBinding,
"PlantTemplate" => PlantTemplate,
"Point" => Point,
"Regimen" => Regimen,
"SavedGarden" => SavedGarden,
"SensorReading" => SensorReading,
"Sensor" => Sensor,
"Sequence" => Sequence,
"Tool" => Tool,
"WebcamFeed" => WebcamFeed,
}
end # Resources

View File

@ -0,0 +1,45 @@
module Resources
class Job < Mutations::Command
required do
duck :body, methods: [:[], :[]=]
duck :resource, duck: [:where, :find_by]
integer :resource_id
model :device, class: Device
string :action, in: ACTIONS
string :uuid
end
def validate
# Should never trigger in production.
never unless RESOURCES.values.include?(resource) # Security critical
end
def execute
case action
when DESTROY then do_deletion
else; never
end
end
private
def plural_resource
@plural_resource ||= resource.name.pluralize
end
def do_deletion
model_name = resource.model_name
mutation = Kernel.const_get(model_name.name.pluralize)::Destroy
mutation.run!(model_name.singular => model, device: device)
end
def model
@model ||= device.send(plural_resource.downcase).find(resource_id)
end
# Escape hatch for things that should "never happen".
def never
raise "PANIC"
end
end # Job
end # Resources

View File

@ -0,0 +1,65 @@
module Resources
# Takes a bunch of unsafe, string-y data that came in over AMQP and parses it
# into fully formed
class PreProcessor < Mutations::Command
def self.from_amqp(delivery_info, body)
# ["bot", "device_3", "resources_v0", "destroy", "Sequence", "2", "123-456
_, device_name, _, action, resource, resource_id, uuid = \
delivery_info.routing_key.split(".")
run!(device_name: device_name,
action: action,
resource: resource,
resource_id: resource_id,
uuid: uuid,
body: body.empty? ? "{}" : body)
end
required do
string :action, in: ACTIONS # "destroy"
string :device_name, matches: DEVICE_REGEX # "device_3"
string :resource, in: RESOURCES.keys # "Sequence"
end
optional do
integer :resource_id, default: 0 # 2
string :body # "{\"json\":true}"
string :uuid, default: "NONE" # "0dce-1d-41-1d-e95c3b"
end
def validate
maybe_set_device
maybe_set_body
end
def execute
{
action: action,
device: @device,
body: @body,
resource_id: resource_id,
resource: RESOURCES.fetch(resource),
uuid: uuid,
}
end
private
def fail_body
add_error :body, :body, "body must be a JSON object"
end
def maybe_set_body
hash = JSON.parse(body)
fail_body unless hash.is_a?(Hash)
@body = hash
rescue JSON::ParserError
fail_body
end
def maybe_set_device
id = device_name.gsub("device_", "").to_i
@device = Device.find_by(id: id)
add_error :device, :device, "Can't find device ##{id}" unless @device
end
end # PreProcessor
end # Resources

View File

@ -0,0 +1,23 @@
module Resources
class Service
def self.process(delivery_info, body)
params = PreProcessor.from_amqp(delivery_info, body)
Job.run!(params)
puts "Transport.amqp_send(message, id, channel)"
rescue Mutations::ValidationException => q
params ||= {}
device_id = params[:device].try(:id)
if device_id
message = {
kind: "rpc_error",
args: { label: params.fetch(:uuid) { "NONE" } },
body: (q
.errors
.values
.map { |err| { kind: "explanation", args: { message: err.message }} })
}.to_json
Transport.amqp_send(message, device_id, "from_api")
end
end
end # Service
end # Resources

View File

@ -1,153 +0,0 @@
module Resources
DEVICE_REGEX = /device_\d*/
ACTIONS = [
DESTROY = "destroy"
]
RESOURCES = { # Because I don't trust Kernel.const_get
"DeviceConfig" => DeviceConfig,
"DiagnosticDump" => DiagnosticDump,
"FarmEvent" => FarmEvent,
"FarmwareInstallations" => FarmwareInstallations,
"Image" => Image,
"Log" => Log,
"Peripheral" => Peripheral,
"PinBinding" => PinBinding,
"PlantTemplate" => PlantTemplate,
"Point" => Point,
"Regimen" => Regimen,
"SavedGarden" => SavedGarden,
"SensorReading" => SensorReading,
"Sensor" => Sensor,
"Sequence" => Sequence,
"Tool" => Tool,
"WebcamFeed" => WebcamFeed,
}
class Job < Mutations::Command
required do
duck :body, methods: [:[], :[]=]
duck :resource, duck: [:where, :find_by]
integer :resource_id
model :device, class: Device
string :action, in: ACTIONS
string :uuid
end
def validate
# Security critical. Should never occur in production.
never unless RESOURCES.values.include?(resource)
end
def execute
case action
when DESTROY then do_deletion
else; never
end
end
private
def plural_resource
@plural_resource ||= resource.name.pluralize
end
def do_deletion
model_name = resource.model_name
mutation = Kernel.const_get(model_name.name.pluralize)::Destroy
mutation.run!(model_name.singular => model, device: device)
end
def model
@model ||= device.send(plural_resource.downcase).find(resource_id)
end
# Escape hatch for things that should "never happen".
def never
raise "PANIC"
end
end # Job
class Service
def self.process(delivery_info, body)
params = PreProcessor.from_amqp(delivery_info, body)
Job.run!(params)
puts "Transport.amqp_send(message, id, channel)"
rescue Mutations::ValidationException => q
params ||= {}
device_id = params[:device].try(:id)
if device_id
message = {
kind: "rpc_error",
args: { label: params.fetch(:uuid) { "NONE" } },
body: (q
.errors
.values
.map { |err| { kind: "explanation", args: { message: err.message }} })
}.to_json
Transport.amqp_send(message, device_id, "from_api")
end
end
end # Service
class PreProcessor < Mutations::Command
def self.from_amqp(delivery_info, body)
# ["bot", "device_3", "resources_v0", "destroy", "Sequence", "2", "123-456
_, device_name, _, action, resource, resource_id, uuid = \
delivery_info.routing_key.split(".")
run!(device_name: device_name,
action: action,
resource: resource,
resource_id: resource_id,
uuid: uuid,
body: body.empty? ? "{}" : body)
end
required do
string :action, in: ACTIONS # "destroy"
string :device_name, matches: DEVICE_REGEX # "device_3"
string :resource, in: RESOURCES.keys # "Sequence"
end
optional do
integer :resource_id, default: 0 # 2
string :body # "{\"json\":true}"
string :uuid, default: "NONE" # "0dce-1d-41-1d-e95c3b"
end
def validate
maybe_set_device
maybe_set_body
end
def execute
{
action: action,
device: @device,
body: @body,
resource_id: resource_id,
resource: RESOURCES.fetch(resource),
uuid: uuid,
}
end
private
def fail_body
add_error :body, :body, "body must be a JSON object"
end
def maybe_set_body
hash = JSON.parse(body)
fail_body unless hash.is_a?(Hash)
@body = hash
rescue JSON::ParserError
fail_body
end
def maybe_set_device
id = device_name.gsub("device_", "").to_i
@device = Device.find_by(id: id)
add_error :device, :device, "Can't find device ##{id}" unless @device
end
end # PreProcessor
end # Resources

View File

@ -1,5 +1,5 @@
require "spec_helper"
require_relative "../../lib/log_service"
# require_relative "../../lib/log_service"
describe LogService do
normal_payl = '{"meta":{"z":0,"y":0,"x":0,"type":"info","major_version":6},' +