Skip to content

Commit 0afb899

Browse files
authored
AC-1714 monai update task manager plugin with delete end point (#739)
* refacted argo client, added argo delete endpoint Signed-off-by: Lillie Dae <[email protected]>
1 parent 5297c24 commit 0afb899

File tree

15 files changed

+854
-259
lines changed

15 files changed

+854
-259
lines changed

guidelines/mwm-developer-setup.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ Note. if you already have docker container for Minio Rabbit etc running Stop the
3232
- `kubectl apply -n argo -f https://raw.githubusercontent.com/argoproj/argo-workflows/master/manifests/quick-start-postgres.yaml`
3333
- `kubectl config set-context --current --namespace=argo`
3434

35+
To disable argo authentication run
36+
37+
kubectl patch deployment \
38+
argo-server \
39+
--namespace argo \
40+
--type='json' \
41+
-p='[{"op": "replace", "path": "/spec/template/spec/containers/0/args", "value": [
42+
"server",
43+
"--auth-mode=server"
44+
]}]'
45+
3546
Note. below Im using bash as its my preferred option, But if you to are using bash and your on windows (wsl2) you MUST make sure you windows .kube/config is also pointing to the same K8's cluster, this is because the code running in vs will look in there for the context to write k8's secrets too!
3647

3748
now in a bash window (can be cmd or powershell)

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

Lines changed: 107 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,9 @@
2222

2323
namespace Monai.Deploy.WorkflowManager.TaskManager.Argo
2424
{
25-
public interface IArgoClient
25+
public class ArgoClient : BaseArgoClient, IArgoClient
2626
{
27-
Task<Workflow> Argo_CreateWorkflowAsync(string argoNamespace, WorkflowCreateRequest body, CancellationToken cancellationToken);
28-
29-
Task<Workflow> Argo_GetWorkflowAsync(string argoNamespace, string name, string getOptions_resourceVersion, string fields, CancellationToken cancellationToken);
30-
31-
Task<WorkflowTemplate> Argo_GetWorkflowTemplateAsync(string argoNamespace, string name, string getOptions_resourceVersion);
32-
33-
Task<Workflow> Argo_StopWorkflowAsync(string argoNamespace, string name, WorkflowStopRequest body);
34-
35-
Task<Workflow> Argo_TerminateWorkflowAsync(string argoNamespace, string name, WorkflowTerminateRequest body);
36-
37-
Task<Version?> Argo_GetVersionAsync();
38-
39-
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);
42-
}
43-
44-
public class ArgoClient : IArgoClient
45-
{
46-
private readonly HttpClient _httpClient;
47-
48-
public string BaseUrl { get; set; } = "http://localhost:2746";
49-
50-
private string FormattedBaseUrl { get { return BaseUrl != null ? BaseUrl.TrimEnd('/') : ""; } }
51-
52-
public ArgoClient(HttpClient httpClient)
53-
{
54-
_httpClient = httpClient;
55-
}
27+
public ArgoClient(HttpClient httpClient) : base(httpClient) { }
5628

5729
public async Task<Workflow> Argo_CreateWorkflowAsync(string argoNamespace, WorkflowCreateRequest body, CancellationToken cancellationToken)
5830
{
@@ -164,27 +136,113 @@ public async Task<WorkflowTemplate> Argo_GetWorkflowTemplateAsync(string argoNam
164136
urlBuilder.Length--;
165137
return await GetRequest<string>(urlBuilder, true).ConfigureAwait(false);
166138
}
167-
private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder urlBuilder, string Method, CancellationToken cancellationToken)
139+
140+
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
141+
/// <returns>A successful response.</returns>
142+
/// <exception cref="ApiException">A server side error occurred.</exception>
143+
public virtual async Task<WorkflowTemplate> Argo_CreateWorkflowTemplateAsync(string argoNamespace, WorkflowTemplateCreateRequest body, CancellationToken cancellationToken)
144+
{
145+
Guard.Against.NullOrWhiteSpace(argoNamespace);
146+
Guard.Against.Null(body);
147+
148+
var urlBuilder = new StringBuilder();
149+
urlBuilder.Append(CultureInfo.InvariantCulture, $"{FormattedBaseUrl}/api/v1/workflow-templates/{argoNamespace}");
150+
151+
var method = "POST";
152+
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body));
153+
return await SendRequest<WorkflowTemplate>(content, urlBuilder, method, cancellationToken).ConfigureAwait(false);
154+
}
155+
156+
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
157+
/// <returns>A successful response.</returns>
158+
/// <exception cref="ApiException">A server side error occurred.</exception>
159+
public virtual async Task<bool> Argo_DeleteWorkflowTemplateAsync(string argoNamespace, string templateName, CancellationToken cancellationToken)
168160
{
169-
using (var request = new HttpRequestMessage())
161+
Guard.Against.NullOrWhiteSpace(argoNamespace);
162+
163+
var urlBuilder = new StringBuilder();
164+
urlBuilder.Append(CultureInfo.InvariantCulture, $"{FormattedBaseUrl}/api/v1/workflow-templates/{argoNamespace}/{templateName}");
165+
166+
var method = "DELETE";
167+
var response = await HttpClient.SendAsync(new HttpRequestMessage(new HttpMethod(method), urlBuilder.ToString()), cancellationToken).ConfigureAwait(false);
168+
return (int)response.StatusCode == 200;
169+
}
170+
171+
public static string ConvertToString(object value, CultureInfo cultureInfo)
172+
{
173+
if (value == null)
170174
{
171-
stringContent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
172-
request.Content = stringContent;
173-
request.Method = new HttpMethod(Method);
174-
request.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
175-
request.RequestUri = new Uri(urlBuilder.ToString(), UriKind.RelativeOrAbsolute);
175+
return "";
176+
}
176177

177-
HttpResponseMessage? response = null;
178-
try
178+
if (value is Enum)
179+
{
180+
var name = Enum.GetName(value.GetType(), value);
181+
if (name != null)
179182
{
180-
response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
183+
var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
184+
if (field != null)
185+
{
186+
if (System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) is System.Runtime.Serialization.EnumMemberAttribute attribute)
187+
{
188+
return attribute.Value ?? name;
189+
}
190+
}
191+
192+
var converted = Convert.ToString(Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), cultureInfo));
193+
return converted ?? string.Empty;
181194
}
182-
catch (Exception ex)
195+
}
196+
else if (value is bool boolean)
197+
{
198+
return Convert.ToString(boolean, cultureInfo).ToLowerInvariant();
199+
}
200+
else if (value is byte[] v)
201+
{
202+
return Convert.ToBase64String(v);
203+
}
204+
else if (value.GetType().IsArray)
205+
{
206+
var array = Enumerable.OfType<object>((Array)value);
207+
return string.Join(",", Enumerable.Select(array, o => ConvertToString(o, cultureInfo)));
208+
}
209+
210+
var result = Convert.ToString(value, cultureInfo);
211+
return result ?? "";
212+
}
213+
}
214+
215+
/// <summary>
216+
/// <see cref="BaseArgoClient"/> generic functions relating to argo requests
217+
/// </summary>
218+
public class BaseArgoClient
219+
{
220+
public string BaseUrl { get; set; } = "http://localhost:2746";
221+
222+
protected string FormattedBaseUrl { get { return BaseUrl != null ? BaseUrl.TrimEnd('/') : ""; } }
223+
224+
protected readonly HttpClient HttpClient;
225+
226+
public BaseArgoClient(HttpClient httpClient)
227+
{
228+
HttpClient = httpClient;
229+
}
230+
231+
protected async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder urlBuilder, string method, CancellationToken cancellationToken)
232+
{
233+
using (var request = new HttpRequestMessage())
234+
{
235+
if (stringContent is not null)
183236
{
184-
var mess = ex.Message;
185-
throw;
237+
stringContent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
238+
request.Content = stringContent;
186239
}
240+
request.Method = new HttpMethod(method);
241+
request.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
242+
request.RequestUri = new Uri(urlBuilder.ToString(), UriKind.RelativeOrAbsolute);
187243

244+
HttpResponseMessage? response = null;
245+
response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
188246

189247
try
190248
{
@@ -222,7 +280,7 @@ private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder
222280
}
223281
}
224282

225-
private async Task<T?> GetRequest<T>(StringBuilder urlBuilder, bool isLogs = false)
283+
protected async Task<T?> GetRequest<T>(StringBuilder urlBuilder, bool isLogs = false)
226284
{
227285

228286
using (var request = new HttpRequestMessage())
@@ -231,7 +289,7 @@ private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder
231289
request.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
232290
request.RequestUri = new Uri(urlBuilder.ToString(), UriKind.RelativeOrAbsolute);
233291

234-
var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
292+
var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
235293
try
236294
{
237295
var headers_ = Enumerable.ToDictionary(response.Headers, h_ => h_.Key, h_ => h_.Value);
@@ -271,10 +329,9 @@ private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder
271329
}
272330
}
273331

274-
275332
protected virtual async Task<ObjectResponseResult<T?>> ReadObjectResponseAsync<T>(HttpResponseMessage response, IReadOnlyDictionary<string, IEnumerable<string>> headers, bool isLogs = false)
276333
{
277-
if (response == null || response.Content == null)
334+
if (response == null || response.Content == null || response.Content.GetType().Name == "EmptyContent")
278335
{
279336
return new ObjectResponseResult<T?>(default, string.Empty);
280337
}
@@ -306,7 +363,7 @@ private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder
306363
}
307364
}
308365

309-
protected static string DecodeLogs(string logInput)
366+
public static string DecodeLogs(string logInput)
310367
{
311368
var rows = logInput.Split(new String[] { "\n" }, StringSplitOptions.None);
312369
var jsonBody = $"[{string.Join(",", rows)}]";
@@ -348,23 +405,7 @@ protected virtual async Task<ObjectResponseResult<string>> ReadLogResponseAsync(
348405
}
349406
}
350407

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-
367-
protected struct ObjectResponseResult<T>
408+
protected readonly struct ObjectResponseResult<T>
368409
{
369410
public ObjectResponseResult(T responseObject, string responseText)
370411
{
@@ -377,61 +418,19 @@ public ObjectResponseResult(T responseObject, string responseText)
377418
public string Text { get; }
378419
}
379420

380-
private string ConvertToString(object value, CultureInfo cultureInfo)
381-
{
382-
if (value == null)
383-
{
384-
return "";
385-
}
386-
387-
if (value is Enum)
388-
{
389-
var name = Enum.GetName(value.GetType(), value);
390-
if (name != null)
391-
{
392-
var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
393-
if (field != null)
394-
{
395-
if (System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) is System.Runtime.Serialization.EnumMemberAttribute attribute)
396-
{
397-
return attribute.Value ?? name;
398-
}
399-
}
400-
401-
var converted = Convert.ToString(Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), cultureInfo));
402-
return converted ?? string.Empty;
403-
}
404-
}
405-
else if (value is bool boolean)
406-
{
407-
return Convert.ToString(boolean, cultureInfo).ToLowerInvariant();
408-
}
409-
else if (value is byte[] v)
410-
{
411-
return Convert.ToBase64String(v);
412-
}
413-
else if (value.GetType().IsArray)
414-
{
415-
var array = Enumerable.OfType<object>((Array)value);
416-
return string.Join(",", Enumerable.Select(array, o => ConvertToString(o, cultureInfo)));
417-
}
418-
419-
var result = Convert.ToString(value, cultureInfo);
420-
return result ?? "";
421-
}
422421
class ArgoLogEntry
423422
{
424423
public string Content { get; set; } = "";
425424

426425
public string PodName { get; set; } = "";
427426
}
427+
428428
class ArgoLogEntryResult
429429
{
430430
public ArgoLogEntry Result { get; set; } = new ArgoLogEntry();
431431
}
432432
}
433433

434-
435434
public class Version
436435
{
437436
[Newtonsoft.Json.JsonProperty("buildDate", Required = Newtonsoft.Json.Required.Always)]

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,10 @@ private void ProcessTaskPluginArguments(Workflow workflow)
468468
template.PriorityClassName = priorityClassName;
469469
}
470470
}
471+
if (priorityClassName is not null)
472+
{
473+
workflow.Spec.PodPriorityClassName = priorityClassName;
474+
}
471475
}
472476

473477
private static void AddLimit(Dictionary<string, string>? resources, Template2 template, ResourcesKey key)
@@ -981,5 +985,19 @@ public async Task<WorkflowTemplate> CreateArgoTemplate(string template)
981985
throw;
982986
}
983987
}
988+
989+
public async Task<bool> DeleteArgoTemplate(string templateName)
990+
{
991+
try
992+
{
993+
var client = _argoProvider.CreateClient(_baseUrl, _apiToken, _allowInsecure);
994+
return await client.Argo_DeleteWorkflowTemplateAsync(_namespace, templateName, new CancellationToken()).ConfigureAwait(false);
995+
}
996+
catch (Exception ex)
997+
{
998+
_logger.ErrorDeletingWorkflowTemplate(ex);
999+
throw;
1000+
}
1001+
}
9841002
}
9851003
}

src/TaskManager/Plug-ins/Argo/Controllers/TemplateController.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class TemplateController : ControllerBase
3131
{
3232
private readonly ArgoPlugin _argoPlugin;
3333
private readonly ILogger<TemplateController> _tempLogger;
34+
3435
public TemplateController(
3536
IServiceScopeFactory scopeFactory,
3637
ILogger<TemplateController> tempLogger,
@@ -51,7 +52,7 @@ public async Task<ActionResult<WorkflowTemplate>> CreateArgoTemplate()
5152

5253
var value2 = await reader.ReadToEndAsync();
5354

54-
if (String.IsNullOrWhiteSpace(value2))
55+
if (string.IsNullOrWhiteSpace(value2))
5556
{
5657
return BadRequest("No file recieved");
5758
}
@@ -68,5 +69,24 @@ public async Task<ActionResult<WorkflowTemplate>> CreateArgoTemplate()
6869

6970
return Ok(workflowTemplate);
7071
}
72+
73+
[Route("{name}")]
74+
[HttpDelete]
75+
public async Task<ActionResult<bool>> DeleteArgoTemplate(string name)
76+
{
77+
if (string.IsNullOrWhiteSpace(name))
78+
{
79+
return BadRequest("No name parameter provided");
80+
}
81+
82+
try
83+
{
84+
return Ok(await _argoPlugin.DeleteArgoTemplate(name));
85+
}
86+
catch (Exception)
87+
{
88+
return BadRequest("message: Argo unable to process template");
89+
}
90+
}
7191
}
7292
}

0 commit comments

Comments
 (0)