From 0043599da87c7373bbe6d57064886417cad5b429 Mon Sep 17 00:00:00 2001 From: Josh Day Date: Fri, 14 Jul 2023 14:49:11 -0400 Subject: [PATCH 1/2] wip --- src/SimulationService.jl | 39 ++++++++++------- test/tds.jl | 94 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 21 deletions(-) diff --git a/src/SimulationService.jl b/src/SimulationService.jl index ba0fedc..db2380b 100644 --- a/src/SimulationService.jl +++ b/src/SimulationService.jl @@ -3,7 +3,7 @@ module SimulationService import AMQPClient import CSV import DataFrames: DataFrame -import Dates: Dates, DateTime, now, UTC +import Dates: Dates, DateTime, now import DifferentialEquations import Downloads: download import Distributions: Uniform @@ -108,7 +108,7 @@ RABBITMQ_PORT = parse(Int, get(ENV, "SIMSERVICE_RABBITMQ_PORT", "5672")) JSON_HEADER = ["Content-Type" => "application/json"] # Get Config object from JSON at `url` -get_json(url::String)::Config = JSON3.read(HTTP.get(url, JSON_HEADER).body, Config) +get_json(url::String, T=Config) = JSON3.read(HTTP.get(url, JSON_HEADER).body, T) #-----------------------------------------------------------------------------# amr_get @@ -180,6 +180,7 @@ end # data function amr_get(df::DataFrame, sys::ODESystem, ::Val{:data}) + statelist = states(sys) statenames = string.(statelist) statenames = map(statenames) do n; n[1:end-3]; end # there's a better way to do this @@ -268,17 +269,19 @@ end #-----------------------------------------------------------------------------# DataServiceModel # https://raw.githubusercontent.com/DARPA-ASKEM/simulation-api-spec/main/schemas/simulation.json # How a model/simulation is represented in TDS +# you can HTTP.get("$TDS_URL/simulation") and the JSON3.read(body, DataServiceModel) Base.@kwdef mutable struct DataServiceModel # Required id::String = "UNINITIALIZED_ID" # matches with our `id` - engine::Symbol = :sciml # (ignore) TDS supports multiple engine. We are the `sciml` engine. - type::Symbol = :uninitialized # :simulate, :calibrate, etc. + engine::String = "sciml" # (ignore) TDS supports multiple engine. We are the `sciml` engine. + type::String = "uninitialized" # :calibration, :calibration_simulation, :ensemble, :simulation execution_payload::Config = Config() # untouched JSON from request sent by HMI workflow_id::String = "IGNORED" # (ignore) # Optional - timestamp::Union{Nothing, DateTime} = nothing # (ignore?) + description::String = "" # (ignore) + timestamp::Union{Nothing, DateTime} = nothing # (ignore?) result_files::Union{Nothing, Vector{String}} = nothing # URLs of result files in S3 - status::Union{Nothing, Symbol} = :queued # queued|running|complete|error|cancelled|failed" + status::Union{Nothing, String} = "queued" # queued|running|complete|error|cancelled|failed" reason::Union{Nothing, String} = nothing # why simulation failed (returned junk results) start_time::Union{Nothing, DateTime} = nothing # when job started completed_time::Union{Nothing, DateTime} = nothing # when job completed @@ -290,12 +293,17 @@ end StructTypes.StructType(::Type{DataServiceModel}) = StructTypes.Mutable() # Initialize a DataServiceModel +operation_to_dsm_type = Dict( + :simulate => "simulation", + :calibrate => "calibration_simulation", + :ensemble => "ensemble" +) + function DataServiceModel(o::OperationRequest) m = DataServiceModel() m.id = o.id - m.type = o.operation + m.type = operation_to_dsm_type[o.operation] m.execution_payload = o.obj - m.timestamp = now(UTC) return m end @@ -311,8 +319,8 @@ function DataServiceModel(id::String) delays = fill(1, TDS_RETRIES) try - m = retry(() -> JSON3.read(download("$TDS_URL/simulations/$id")); delays, check)() - return m + res = retry(() -> HTTP.get("$TDS_URL/simulations/$id"); delays, check)() + return JSON3.read(res.body, DataServiceModel) catch return error("No simulation found in TDS with id=$id.") end @@ -358,10 +366,11 @@ function create(o::OperationRequest) @warn "TDS disabled - create with JSON $body" return body end - HTTP.post("$TDS_URL/simulations/$(o.id)", JSON_HEADER; body) + HTTP.post("$TDS_URL/simulations/", JSON_HEADER; body) end # update the DataServiceModel in TDS: PUT /simulations/{id} +# kw args and their types much match field::fieldtype in DataServiceModel function update(o::OperationRequest; kw...) if !ENABLE_TDS @warn "TDS disabled - update OperationRequest with id=$(o.id), $kw" @@ -369,9 +378,7 @@ function update(o::OperationRequest; kw...) end m = DataServiceModel(o.id) for (k,v) in kw - hasfield(m, k) ? - setproperty!(o, k, v) : - @warn "Skipping `update` of unrecognized field: $k = $v." + setproperty!(m, k, v) end HTTP.put("$TDS_URL/simulations/$(o.id)", JSON_HEADER; body=JSON3.write(m)) end @@ -400,7 +407,7 @@ function complete(o::OperationRequest) tds_url = "$TDS_URL/simulations/sciml-$(o.id)/upload-url?filename=$filename)" s3_url = get_json(tds_url).url HTTP.put(s3_url, header; body=body) - update(o; status = :complete, completed_time = Dates.now(UTC), result_files = [s3_url]) + update(o; status = "complete", completed_time = Dates.now(), result_files = [s3_url]) end @@ -428,7 +435,7 @@ function operation(request::HTTP.Request, operation_name::String) @task begin # try @info "Updating job (id=$(o.id))...)" - update(o; status = :running, start_time = Dates.now(UTC)) # 4 + update(o; status = :running, start_time = Dates.now()) # 4 @info "Solving job (id=$(o.id))..." solve(o) # 5 @info "Completing job (id=$(o.id))...)" diff --git a/test/tds.jl b/test/tds.jl index 3a266eb..1a43be1 100644 --- a/test/tds.jl +++ b/test/tds.jl @@ -1,8 +1,92 @@ -using SimulationService -using Test +# This only works if you are using the TDS instance set up by Brandon Rose. +# Talk to Brandon or Josh Day for information. + +# Docs: $TDS_URL/#/Simulation/simulation_delete_simulations__simulation_id__delete + +using SimulationService: get_model, get_dataset, create, update, complete, get_json, solve, + DataServiceModel, OperationRequest, TDS_URL, TDS_RETRIES, ENABLE_TDS + +using HTTP, JSON3, DataFrames, EasyConfig, Test SimulationService.ENABLE_TDS = true -json = @config(model_config_id = "2b08c681-ee0e-4ad1-81d5-e0e3e203ffbe", timespan.start=0.0, timespan.end=100.0) -body = JSON3.write(json) -res = HTTP.post("$url/simulate", ["Content-Type" => "application/json"]; body) +#-----------------------------------------------------------------------------# Check that TDS is running +get_simulations() = get_json("$TDS_URL/simulations", Vector{Config}) + +simulations = get_simulations() + +for sim in simulations + res = HTTP.delete("$TDS_URL/simulations/$(sim.id)") + @test res.status == 200 +end + +sleep(2) +simulations = get_simulations() +@test isempty(simulations) + + +#-----------------------------------------------------------------------------# get_model ✓ +model_config_id = "2b08c681-ee0e-4ad1-81d5-e0e3e203ffbe" +obj = get_model(model_config_id) +@test obj.id == model_config_id + +#-----------------------------------------------------------------------------# get_dataset ??? +# TODO + +#-----------------------------------------------------------------------------# create ✓ +# mock request to SimulationService +payload = @config(model_config_id=model_config_id, timespan.start=1, timespan.end=100, engine="sciml") +req = HTTP.Request("POST", "", ["Content-Type" => "application/json"], JSON3.write(payload)) +o = OperationRequest(req, "simulate") +id = o.id + +res = create(o) # Create TDS representation of model +@test JSON3.read(res.body).id == id + +# Check that simulation with `id` now exists in TDS +sleep(2) +@test HTTP.get("$TDS_URL/simulations/$id").status == 200 + +#-----------------------------------------------------------------------------# DataServiceModel ✓ +m = DataServiceModel(id) + +#-----------------------------------------------------------------------------# update ✓ +res = update(o; status = "running") +@test res.status == 200 +@test JSON3.read(res.body).id == id + +#-----------------------------------------------------------------------------# complete ✓ +@test_throws "solve(" complete(o) + +o.result = DataFrame(fake=1:10, results=randn(10)) + +res = complete(o) + +#-----------------------------------------------------------------------------# ALL TOGETHER NOW ??? +SimulationService.operation(req, "simulate") + +#-----------------------------------------------------------------------------# with server ??? +start!() + +url = SimulationService.server_url[] + +sleep(3) # Give server a chance to start + +@testset "/" begin + res = HTTP.get(url) + @test res.status == 200 + @test JSON3.read(res.body).status == "ok" +end + + +res = HTTP.post("$url/simulate", ["Content-Type" => "application/json"]; body) +@test res.status == 201 +id = JSON3.read(res.body).simulation_id +done_or_failed = false +while !done_or_failed + st = JSON3.read(HTTP.get("$url/status/$id").body).status + st in ["queued", "complete", "running"] ? @test(true) : @test(false) + done_or_failed = st in ["complete", "error"] + sleep(1) +end +@test SimulationService.last_operation[].result isa DataFrame From 18c38fdfa228569d94a030cec3cf6c4eed3a320d Mon Sep 17 00:00:00 2001 From: Josh Day Date: Fri, 14 Jul 2023 15:05:06 -0400 Subject: [PATCH 2/2] wip. remove multi-threaded ci for now --- .../workflows/julia-tests-multithread.yaml | 32 ------------------- src/SimulationService.jl | 2 +- test/runtests.jl | 12 ++----- test/tds.jl | 8 ++--- 4 files changed, 7 insertions(+), 47 deletions(-) delete mode 100644 .github/workflows/julia-tests-multithread.yaml diff --git a/.github/workflows/julia-tests-multithread.yaml b/.github/workflows/julia-tests-multithread.yaml deleted file mode 100644 index f93729c..0000000 --- a/.github/workflows/julia-tests-multithread.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -# Julia Tests (Same as julia-tests but with JULIA_NUM_THREADS set) - -name: SimulationService.jl Tests (Multi-threaded) -on: - push: - branches: ['main'] - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - env: - JULIA_NUM_THREADS: 4 - steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 - with: - version: '1.9' - - uses: julia-actions/cache@v1 - with: - cache-compiled: "true" - - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-runtest@v1 - - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 - with: - file: lcov.info - - uses: actions/upload-artifact@v3 - with: - name: payloads - path: test/logs/ diff --git a/src/SimulationService.jl b/src/SimulationService.jl index db2380b..3865bcb 100644 --- a/src/SimulationService.jl +++ b/src/SimulationService.jl @@ -435,7 +435,7 @@ function operation(request::HTTP.Request, operation_name::String) @task begin # try @info "Updating job (id=$(o.id))...)" - update(o; status = :running, start_time = Dates.now()) # 4 + update(o; status = "running", start_time = Dates.now()) # 4 @info "Solving job (id=$(o.id))..." solve(o) # 5 @info "Completing job (id=$(o.id))...)" diff --git a/test/runtests.jl b/test/runtests.jl index d36afb5..86c7ff6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -57,10 +57,10 @@ end #-----------------------------------------------------------# DataServiceModel and OperationRequest @testset "DataServiceModel and OperationRequest" begin m = DataServiceModel() - @test m.engine == :sciml + @test m.engine == "sciml" @test m.id == "UNINITIALIZED_ID" - o = OperationRequest() + o = OperationRequest(operation = :simulate) m2 = DataServiceModel(o) # OperationRequest constructor with dummy HTTP.Request @@ -135,11 +135,3 @@ end stop!() end - -#-----------------------------------------------------------------------------# TDS Enable -@testset "TDS Enabled" begin - - if SimulationService.ENABLE_TDS - - end -end diff --git a/test/tds.jl b/test/tds.jl index 1a43be1..94595af 100644 --- a/test/tds.jl +++ b/test/tds.jl @@ -12,19 +12,15 @@ SimulationService.ENABLE_TDS = true #-----------------------------------------------------------------------------# Check that TDS is running get_simulations() = get_json("$TDS_URL/simulations", Vector{Config}) - simulations = get_simulations() - for sim in simulations res = HTTP.delete("$TDS_URL/simulations/$(sim.id)") @test res.status == 200 end - sleep(2) simulations = get_simulations() @test isempty(simulations) - #-----------------------------------------------------------------------------# get_model ✓ model_config_id = "2b08c681-ee0e-4ad1-81d5-e0e3e203ffbe" obj = get_model(model_config_id) @@ -49,6 +45,7 @@ sleep(2) #-----------------------------------------------------------------------------# DataServiceModel ✓ m = DataServiceModel(id) +@test m.id == id #-----------------------------------------------------------------------------# update ✓ res = update(o; status = "running") @@ -61,6 +58,9 @@ res = update(o; status = "running") o.result = DataFrame(fake=1:10, results=randn(10)) res = complete(o) +@test res.status == 200 +@test JSON3.read(res.body).id == id + #-----------------------------------------------------------------------------# ALL TOGETHER NOW ??? SimulationService.operation(req, "simulate")