Skip to content

Commit 0df8db8

Browse files
authored
Merge pull request #717 from Project-MONAI/AC1709-nds-add-plugin-controllers
adding dll scanning and loading
2 parents 44d2563 + ff52e97 commit 0df8db8

16 files changed

+1152
-15
lines changed
+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
<!--
2+
~ Copyright 2022 MONAI Consortium
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
# Task Manager - Argo Template API
18+
19+
The template endpoint provides the following APIs to post a new template to Argo plugin of
20+
the Task Manager.
21+
22+
23+
## POST /argo/template
24+
25+
Posts a new template to Argo and returns the resultant template.
26+
27+
### Parameters
28+
29+
body, json string of the wrapped template
30+
31+
### Responses
32+
33+
Response Content Type: JSON
34+
35+
- the actual Argo resultant template.
36+
37+
| Code | Description |
38+
| ---- | ------------------------------- |
39+
| 200 | template posted sucsessfully. |
40+
| 400 | bad json or template post error |
41+
| 500 | Internal service error. |
42+
43+
### Example Request
44+
45+
file saved locally as workflowtemplate.json
46+
```json
47+
{
48+
"Namespace": "argo",
49+
"serverDryRun": false,
50+
"submitOptions": {
51+
"parameters": [
52+
"name=value"
53+
]
54+
},
55+
"template": {
56+
"metadata": {
57+
"name": "fantastic-tiger",
58+
"namespace": "argo",
59+
"labels": {
60+
"example": "true"
61+
}
62+
},
63+
"spec": {
64+
"workflowMetadata": {
65+
"labels": {
66+
"example": "true"
67+
}
68+
},
69+
"entrypoint": "argosay",
70+
"arguments": {
71+
"parameters": [
72+
{
73+
"name": "message",
74+
"value": "hello argo"
75+
}
76+
]
77+
},
78+
"templates": [
79+
{
80+
"name": "argosay",
81+
"inputs": {
82+
"parameters": [
83+
{
84+
"name": "message",
85+
"value": "{{workflow.parameters.message}}"
86+
}
87+
]
88+
},
89+
"container": {
90+
"name": "main",
91+
"image": "argoproj/argosay:v2",
92+
"command": [
93+
"/argosay"
94+
],
95+
"args": [
96+
"echo",
97+
"{{inputs.parameters.message}}"
98+
]
99+
}
100+
}
101+
],
102+
"ttlStrategy": {
103+
"secondsAfterCompletion": 300
104+
},
105+
"podGC": {
106+
"strategy": "OnPodCompletion"
107+
}
108+
}
109+
}
110+
}
111+
```
112+
113+
```bash
114+
curl -d @workflowtemplate.json 'http://localhost:5000/argo/template'
115+
```
116+
117+
### Example Response
118+
119+
```json
120+
{
121+
"metadata": {
122+
"creationTimestamp": "2023-03-23T17:28:34+00:00",
123+
"generation": 1,
124+
"labels": {
125+
"example": "true",
126+
"workflows.argoproj.io/creator": "system-serviceaccount-argo-argo-argo-workflows-server"
127+
},
128+
"managedFields": [
129+
{
130+
"apiVersion": "argoproj.io/v1alpha1",
131+
"fieldsType": "FieldsV1",
132+
"fieldsV1": {},
133+
"manager": "argo",
134+
"operation": "Update",
135+
"time": "2023-03-23T17:28:34+00:00"
136+
}
137+
],
138+
"name": "fantastic-tiger",
139+
"namespace": "argo",
140+
"resourceVersion": "12030505",
141+
"uid": "e7609791-2d30-4dae-a47e-f1796846068d"
142+
},
143+
"spec": {
144+
"arguments": {
145+
"parameters": [
146+
{
147+
"name": "message",
148+
"value": "hello argo"
149+
}
150+
]
151+
},
152+
"entrypoint": "argosay",
153+
"podGC": {
154+
"strategy": "OnPodCompletion"
155+
},
156+
"templates": [
157+
{
158+
"container": {
159+
"args": [
160+
"echo",
161+
"{{inputs.parameters.message}}"
162+
],
163+
"command": [
164+
"/argosay"
165+
],
166+
"image": "argoproj/argosay:v2",
167+
"name": "main",
168+
"resources": {}
169+
},
170+
"inputs": {
171+
"parameters": [
172+
{
173+
"name": "message",
174+
"value": "{{workflow.parameters.message}}"
175+
}
176+
]
177+
},
178+
"metadata": {},
179+
"name": "argosay",
180+
"outputs": {}
181+
}
182+
],
183+
"ttlStrategy": {
184+
"secondsAfterCompletion": 300
185+
},
186+
"workflowMetadata": {
187+
"labels": {
188+
"example": "true"
189+
}
190+
}
191+
}
192+
}
193+
```

docs/changelog.md

+2
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ Enhanced the ArgoClient -> Argo_Get_WorkflowLogsAsync method to decode the json
2424
Added Mongo Migraions, to allow changes without breaking current stored entries
2525

2626
Added resource constraints to the generated ARGO templates, so these can operate within a Kubernetes cluster with a Resource Quota in the ARGO namespace.
27+
28+
Added ability for plugins (currently Argo) to have controllers, any dll marked with the new Monai.Deploy.WorkflowManager.Shared.PlugInAttribute will have any controllers added to TaskManager

src/Monai.Deploy.WorkflowManager.sln

+5-5
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ Global
140140
{013395D4-2091-4AB8-96CB-867C0F982096}.Debug|Any CPU.Build.0 = Debug|Any CPU
141141
{013395D4-2091-4AB8-96CB-867C0F982096}.Release|Any CPU.ActiveCfg = Release|Any CPU
142142
{013395D4-2091-4AB8-96CB-867C0F982096}.Release|Any CPU.Build.0 = Release|Any CPU
143+
{973A5B90-C143-46B6-899D-79E3D46370C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
144+
{973A5B90-C143-46B6-899D-79E3D46370C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
145+
{973A5B90-C143-46B6-899D-79E3D46370C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
146+
{973A5B90-C143-46B6-899D-79E3D46370C4}.Release|Any CPU.Build.0 = Release|Any CPU
143147
{91A0D599-472F-4238-A1A3-07807F9C5F61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
144148
{91A0D599-472F-4238-A1A3-07807F9C5F61}.Debug|Any CPU.Build.0 = Debug|Any CPU
145149
{91A0D599-472F-4238-A1A3-07807F9C5F61}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -224,10 +228,6 @@ Global
224228
{75A4AEDA-0386-4B2D-9DBA-BC9AE733660E}.Debug|Any CPU.Build.0 = Debug|Any CPU
225229
{75A4AEDA-0386-4B2D-9DBA-BC9AE733660E}.Release|Any CPU.ActiveCfg = Release|Any CPU
226230
{75A4AEDA-0386-4B2D-9DBA-BC9AE733660E}.Release|Any CPU.Build.0 = Release|Any CPU
227-
{973A5B90-C143-46B6-899D-79E3D46370C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
228-
{973A5B90-C143-46B6-899D-79E3D46370C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
229-
{973A5B90-C143-46B6-899D-79E3D46370C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
230-
{973A5B90-C143-46B6-899D-79E3D46370C4}.Release|Any CPU.Build.0 = Release|Any CPU
231231
EndGlobalSection
232232
GlobalSection(SolutionProperties) = preSolution
233233
HideSolutionNode = FALSE
@@ -246,6 +246,7 @@ Global
246246
{918E4DE3-A7BF-4B7F-9B5A-5C36FEFA3C30} = {71DDEE7B-E213-4E39-A7F4-4646783A27F7}
247247
{722C0D57-49F8-4178-88F0-06E369B797A3} = {71DDEE7B-E213-4E39-A7F4-4646783A27F7}
248248
{013395D4-2091-4AB8-96CB-867C0F982096} = {541C5347-5D7D-44B7-95D3-B6FB3D9EB955}
249+
{973A5B90-C143-46B6-899D-79E3D46370C4} = {71DDEE7B-E213-4E39-A7F4-4646783A27F7}
249250
{91A0D599-472F-4238-A1A3-07807F9C5F61} = {71DDEE7B-E213-4E39-A7F4-4646783A27F7}
250251
{7D85E95C-A263-429F-BF8B-8F4A922FD579} = {541C5347-5D7D-44B7-95D3-B6FB3D9EB955}
251252
{A966A7B9-2D4E-4A93-8D20-BD140E7A7F85} = {71DDEE7B-E213-4E39-A7F4-4646783A27F7}
@@ -268,7 +269,6 @@ Global
268269
{81E3F943-B992-4C81-AA09-A17C05081236} = {37A19144-CEA5-47A2-9FFD-22C522E8B895}
269270
{C853A9E3-C53D-4B1A-BFDA-228689A8C94C} = {71DDEE7B-E213-4E39-A7F4-4646783A27F7}
270271
{75A4AEDA-0386-4B2D-9DBA-BC9AE733660E} = {71DDEE7B-E213-4E39-A7F4-4646783A27F7}
271-
{973A5B90-C143-46B6-899D-79E3D46370C4} = {71DDEE7B-E213-4E39-A7F4-4646783A27F7}
272272
EndGlobalSection
273273
GlobalSection(ExtensibilityGlobals) = postSolution
274274
SolutionGuid = {DC0D56C8-D8CB-45CE-B528-F3DCF86D63ED}

src/Shared/Shared/PlugInAttribute.cs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2022 MONAI Consortium
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
namespace Monai.Deploy.WorkflowManager.Shared
18+
{
19+
[AttributeUsage(AttributeTargets.Assembly)]
20+
public class PlugInAttribute : Attribute
21+
{
22+
public PlugInAttribute() { }
23+
24+
}
25+
}

src/TaskManager/Plug-ins/Argo/ArgoClient.cs

+34-6
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public interface IArgoClient
3737
Task<Version?> Argo_GetVersionAsync();
3838

3939
Task<string?> Argo_Get_WorkflowLogsAsync(string argoNamespace, string name, string podName, string logOptions_container);
40+
41+
Task<WorkflowTemplate> Argo_CreateWorkflowTemplateAsync(string argoNamespace, WorkflowTemplateCreateRequest body, CancellationToken cancellationToken);
4042
}
4143

4244
public class ArgoClient : IArgoClient
@@ -62,7 +64,7 @@ public async Task<Workflow> Argo_CreateWorkflowAsync(string argoNamespace, Workf
6264

6365
var Method = "POST";
6466
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body));
65-
return await SendRequest(content, urlBuilder, Method, cancellationToken).ConfigureAwait(false);
67+
return await SendRequest<Workflow>(content, urlBuilder, Method, cancellationToken).ConfigureAwait(false);
6668

6769
}
6870

@@ -99,7 +101,7 @@ public async Task<Workflow> Argo_StopWorkflowAsync(string argoNamespace, string
99101

100102
var Method = "PUT";
101103
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body));
102-
return await SendRequest(content, urlBuilder, Method, new CancellationToken()).ConfigureAwait(false);
104+
return await SendRequest<Workflow>(content, urlBuilder, Method, new CancellationToken()).ConfigureAwait(false);
103105

104106
}
105107

@@ -114,7 +116,7 @@ public async Task<Workflow> Argo_TerminateWorkflowAsync(string argoNamespace, st
114116

115117
var Method = "PUT";
116118
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body));
117-
return await SendRequest(content, urlBuilder, Method, new CancellationToken()).ConfigureAwait(false);
119+
return await SendRequest<Workflow>(content, urlBuilder, Method, new CancellationToken()).ConfigureAwait(false);
118120
}
119121

120122
public async Task<WorkflowTemplate> Argo_GetWorkflowTemplateAsync(string argoNamespace, string name, string getOptions_resourceVersion)
@@ -162,7 +164,7 @@ public async Task<WorkflowTemplate> Argo_GetWorkflowTemplateAsync(string argoNam
162164
urlBuilder.Length--;
163165
return await GetRequest<string>(urlBuilder, true).ConfigureAwait(false);
164166
}
165-
private async Task<Workflow> SendRequest(StringContent stringContent, StringBuilder urlBuilder, string Method, CancellationToken cancellationToken)
167+
private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder urlBuilder, string Method, CancellationToken cancellationToken)
166168
{
167169
using (var request = new HttpRequestMessage())
168170
{
@@ -172,7 +174,17 @@ private async Task<Workflow> SendRequest(StringContent stringContent, StringBuil
172174
request.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
173175
request.RequestUri = new Uri(urlBuilder.ToString(), UriKind.RelativeOrAbsolute);
174176

175-
var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
177+
HttpResponseMessage? response = null;
178+
try
179+
{
180+
response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
181+
}
182+
catch (Exception ex)
183+
{
184+
var mess = ex.Message;
185+
throw;
186+
}
187+
176188

177189
try
178190
{
@@ -186,7 +198,7 @@ private async Task<Workflow> SendRequest(StringContent stringContent, StringBuil
186198
var status = (int)response.StatusCode;
187199
if (status == 200)
188200
{
189-
var objectResponse_ = await ReadObjectResponseAsync<Workflow>(response, headers).ConfigureAwait(false);
201+
var objectResponse_ = await ReadObjectResponseAsync<T>(response, headers).ConfigureAwait(false);
190202
if (objectResponse_.Object == null)
191203
{
192204
throw new ApiException("Response was null which was not expected.", status, objectResponse_.Text, headers, null);
@@ -336,6 +348,22 @@ protected virtual async Task<ObjectResponseResult<string>> ReadLogResponseAsync(
336348
}
337349
}
338350

351+
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
352+
/// <returns>A successful response.</returns>
353+
/// <exception cref="ApiException">A server side error occurred.</exception>
354+
public virtual async Task<WorkflowTemplate> Argo_CreateWorkflowTemplateAsync(string argoNamespace, WorkflowTemplateCreateRequest body, CancellationToken cancellationToken)
355+
{
356+
Guard.Against.NullOrWhiteSpace(argoNamespace);
357+
Guard.Against.Null(body);
358+
359+
var urlBuilder = new StringBuilder();
360+
urlBuilder.Append(CultureInfo.InvariantCulture, $"{FormattedBaseUrl}/api/v1/workflow-templates/{argoNamespace}");
361+
362+
var Method = "POST";
363+
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body));
364+
return await SendRequest<WorkflowTemplate>(content, urlBuilder, Method, cancellationToken).ConfigureAwait(false);
365+
}
366+
339367
protected struct ObjectResponseResult<T>
340368
{
341369
public ObjectResponseResult(T responseObject, string responseText)

0 commit comments

Comments
 (0)