mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-10 01:01:34 +00:00
Ignore regression update when the work item is in some states (#3532)
* Ignore regression update when the work item is in some states * format * formatting * don't hide messages in the poison queue * fix typo * update regression logic update test_template to support regression * build fix * mypy fix * build fix * move regression ignore state under ADODuplicateTemplate * replace extend with append * update set_tcp_keepalive * mke mypy happy * copy ADODuplicateTemplate.OnDuplicate.RegressionIgnoreStates
This commit is contained in:
parent
e10d8eae26
commit
7b79dd61bd
@ -2033,6 +2033,10 @@ If webhook is set to have Event Grid message format then the payload will look a
|
||||
},
|
||||
"original_crash_test_result": {
|
||||
"$ref": "#/definitions/CrashTestResult"
|
||||
},
|
||||
"report_url": {
|
||||
"title": "Report Url",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -6427,6 +6431,10 @@ If webhook is set to have Event Grid message format then the payload will look a
|
||||
},
|
||||
"original_crash_test_result": {
|
||||
"$ref": "#/definitions/CrashTestResult"
|
||||
},
|
||||
"report_url": {
|
||||
"title": "Report Url",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -128,20 +128,22 @@ public class QueueFileChanges {
|
||||
newCustomDequeueCount = json["data"]!["customDequeueCount"]!.GetValue<int>();
|
||||
}
|
||||
|
||||
var queueName = QueueFileChangesQueueName;
|
||||
if (newCustomDequeueCount > MAX_DEQUEUE_COUNT) {
|
||||
_log.LogWarning("Message retried more than {MAX_DEQUEUE_COUNT} times with no success: {msg}", MAX_DEQUEUE_COUNT, msg);
|
||||
queueName = QueueFileChangesPoisonQueueName;
|
||||
await _context.Queue.QueueObject(
|
||||
QueueFileChangesPoisonQueueName,
|
||||
json,
|
||||
StorageType.Config)
|
||||
.IgnoreResult();
|
||||
} else {
|
||||
json!["data"]!["customDequeueCount"] = newCustomDequeueCount + 1;
|
||||
await _context.Queue.QueueObject(
|
||||
QueueFileChangesQueueName,
|
||||
json,
|
||||
StorageType.Config,
|
||||
visibilityTimeout ?? CalculateExponentialBackoff(newCustomDequeueCount))
|
||||
.IgnoreResult();
|
||||
}
|
||||
|
||||
json!["data"]!["customDequeueCount"] = newCustomDequeueCount + 1;
|
||||
|
||||
await _context.Queue.QueueObject(
|
||||
queueName,
|
||||
json,
|
||||
StorageType.Config,
|
||||
visibilityTimeout ?? CalculateExponentialBackoff(newCustomDequeueCount))
|
||||
.IgnoreResult();
|
||||
}
|
||||
|
||||
// Possible return values:
|
||||
|
@ -678,7 +678,8 @@ public record ADODuplicateTemplate(
|
||||
Dictionary<string, string> SetState,
|
||||
Dictionary<string, string> AdoFields,
|
||||
string? Comment = null,
|
||||
List<Dictionary<string, string>>? Unless = null
|
||||
List<Dictionary<string, string>>? Unless = null,
|
||||
List<string>? RegressionIgnoreStates = null
|
||||
);
|
||||
|
||||
public record AdoTemplate(
|
||||
@ -707,7 +708,7 @@ public record RenderedAdoTemplate(
|
||||
ADODuplicateTemplate OnDuplicate,
|
||||
Dictionary<string, string>? AdoDuplicateFields = null,
|
||||
string? Comment = null
|
||||
) : AdoTemplate(BaseUrl, AuthToken, Project, Type, UniqueFields, AdoFields, OnDuplicate, AdoDuplicateFields, Comment);
|
||||
) : AdoTemplate(BaseUrl, AuthToken, Project, Type, UniqueFields, AdoFields, OnDuplicate, AdoDuplicateFields, Comment) { }
|
||||
|
||||
public record TeamsTemplate(SecretData<string> Url) : NotificationTemplate {
|
||||
public Task<OneFuzzResultVoid> Validate() {
|
||||
|
@ -131,7 +131,7 @@ public record NotificationSearch(
|
||||
|
||||
|
||||
public record NotificationTest(
|
||||
[property: Required] Report Report,
|
||||
[property: Required] IReport Report,
|
||||
[property: Required] Notification Notification
|
||||
) : BaseRequest;
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public interface IReports {
|
||||
@ -85,6 +88,7 @@ public class Reports : IReports {
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(ReportConverter))]
|
||||
public interface IReport {
|
||||
Uri? ReportUrl {
|
||||
init;
|
||||
@ -95,3 +99,19 @@ public interface IReport {
|
||||
return string.Concat(segments);
|
||||
}
|
||||
};
|
||||
|
||||
public class ReportConverter : JsonConverter<IReport> {
|
||||
|
||||
public override IReport? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||
using var templateJson = JsonDocument.ParseValue(ref reader);
|
||||
|
||||
if (templateJson.RootElement.TryGetProperty("crash_test_result", out _)) {
|
||||
return templateJson.Deserialize<RegressionReport>(options);
|
||||
}
|
||||
return templateJson.Deserialize<Report>(options);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, IReport value, JsonSerializerOptions options) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ public class Ado : NotificationsBase, IAdo {
|
||||
// https://github.com/MicrosoftDocs/azure-devops-docs/issues/5890#issuecomment-539632059
|
||||
private const int MAX_SYSTEM_TITLE_LENGTH = 128;
|
||||
private const string TITLE_FIELD = "System.Title";
|
||||
private static List<string> DEFAULT_REGRESSION_IGNORE_STATES = new() { "New", "Commited", "Active" };
|
||||
|
||||
public Ado(ILogger<Ado> logTracer, IOnefuzzContext context) : base(logTracer, context) {
|
||||
}
|
||||
@ -56,7 +57,7 @@ public class Ado : NotificationsBase, IAdo {
|
||||
_logTracer.LogEvent(adoEventType);
|
||||
|
||||
try {
|
||||
await ProcessNotification(_context, container, filename, config, report, _logTracer, notificationInfo);
|
||||
await ProcessNotification(_context, container, filename, config, report, _logTracer, notificationInfo, isRegression: reportable is RegressionReport);
|
||||
} catch (Exception e)
|
||||
when (e is VssUnauthorizedException || e is VssAuthenticationException || e is VssServiceException) {
|
||||
if (config.AdoFields.TryGetValue("System.AssignedTo", out var assignedTo)) {
|
||||
@ -298,7 +299,7 @@ public class Ado : NotificationsBase, IAdo {
|
||||
.ToDictionary(field => field.ReferenceName.ToLowerInvariant());
|
||||
}
|
||||
|
||||
private static async Async.Task ProcessNotification(IOnefuzzContext context, Container container, string filename, AdoTemplate config, Report report, ILogger logTracer, IList<(string, string)> notificationInfo, Renderer? renderer = null) {
|
||||
private static async Async.Task ProcessNotification(IOnefuzzContext context, Container container, string filename, AdoTemplate config, Report report, ILogger logTracer, IList<(string, string)> notificationInfo, Renderer? renderer = null, bool isRegression = false) {
|
||||
if (!config.AdoFields.TryGetValue(TITLE_FIELD, out var issueTitle)) {
|
||||
issueTitle = "{{ report.crash_site }} - {{ report.executable }}";
|
||||
}
|
||||
@ -311,7 +312,7 @@ public class Ado : NotificationsBase, IAdo {
|
||||
|
||||
var renderedConfig = RenderAdoTemplate(logTracer, renderer, config, instanceUrl);
|
||||
var ado = new AdoConnector(renderedConfig, project!, client, instanceUrl, logTracer, await GetValidFields(client, project));
|
||||
await ado.Process(notificationInfo);
|
||||
await ado.Process(notificationInfo, isRegression);
|
||||
}
|
||||
|
||||
public static RenderedAdoTemplate RenderAdoTemplate(ILogger logTracer, Renderer renderer, AdoTemplate original, Uri instanceUrl) {
|
||||
@ -352,7 +353,8 @@ public class Ado : NotificationsBase, IAdo {
|
||||
original.OnDuplicate.SetState,
|
||||
onDuplicateAdoFields,
|
||||
original.OnDuplicate.Comment != null ? Render(renderer, original.OnDuplicate.Comment, instanceUrl, logTracer) : null,
|
||||
onDuplicateUnless
|
||||
onDuplicateUnless,
|
||||
original.OnDuplicate.RegressionIgnoreStates
|
||||
);
|
||||
|
||||
return new RenderedAdoTemplate(
|
||||
@ -598,7 +600,7 @@ public class Ado : NotificationsBase, IAdo {
|
||||
return (taskType, document);
|
||||
}
|
||||
|
||||
public async Async.Task Process(IList<(string, string)> notificationInfo) {
|
||||
public async Async.Task Process(IList<(string, string)> notificationInfo, bool isRegression) {
|
||||
var updated = false;
|
||||
WorkItem? oldestWorkItem = null;
|
||||
await foreach (var workItem in ExistingWorkItems(notificationInfo)) {
|
||||
@ -612,6 +614,13 @@ public class Ado : NotificationsBase, IAdo {
|
||||
continue;
|
||||
}
|
||||
|
||||
var regressionStatesToIgnore = _config.OnDuplicate.RegressionIgnoreStates != null ? _config.OnDuplicate.RegressionIgnoreStates : DEFAULT_REGRESSION_IGNORE_STATES;
|
||||
if (isRegression) {
|
||||
var state = (string)workItem.Fields["System.State"];
|
||||
if (regressionStatesToIgnore.Contains(state, StringComparer.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
}
|
||||
|
||||
using (_logTracer.BeginScope("Non-duplicate work item")) {
|
||||
_logTracer.AddTags(new List<(string, string)> { ("NonDuplicateWorkItemId", $"{workItem.Id}") });
|
||||
_logTracer.LogInformation("Found matching non-duplicate work item");
|
||||
@ -621,30 +630,32 @@ public class Ado : NotificationsBase, IAdo {
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!updated) {
|
||||
if (oldestWorkItem != null) {
|
||||
// We have matching work items but all are duplicates
|
||||
_logTracer.AddTags(notificationInfo);
|
||||
_logTracer.LogInformation($"All matching work items were duplicates, re-opening the oldest one");
|
||||
var stateChanged = await UpdateExisting(oldestWorkItem, notificationInfo);
|
||||
if (stateChanged) {
|
||||
// add a comment if we re-opened the bug
|
||||
_ = await _client.AddCommentAsync(
|
||||
new CommentCreate() {
|
||||
Text =
|
||||
"This work item was re-opened because OneFuzz could only find related work items that are marked as duplicate."
|
||||
},
|
||||
_project,
|
||||
(int)oldestWorkItem.Id!);
|
||||
}
|
||||
} else {
|
||||
// We never saw a work item like this before, it must be new
|
||||
var entry = await CreateNew();
|
||||
var adoEventType = "AdoNewItem";
|
||||
_logTracer.AddTags(notificationInfo);
|
||||
_logTracer.AddTag("WorkItemId", entry.Id.HasValue ? entry.Id.Value.ToString() : "");
|
||||
_logTracer.LogEvent(adoEventType);
|
||||
if (updated || isRegression) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldestWorkItem != null) {
|
||||
// We have matching work items but all are duplicates
|
||||
_logTracer.AddTags(notificationInfo);
|
||||
_logTracer.LogInformation($"All matching work items were duplicates, re-opening the oldest one");
|
||||
var stateChanged = await UpdateExisting(oldestWorkItem, notificationInfo);
|
||||
if (stateChanged) {
|
||||
// add a comment if we re-opened the bug
|
||||
_ = await _client.AddCommentAsync(
|
||||
new CommentCreate() {
|
||||
Text =
|
||||
"This work item was re-opened because OneFuzz could only find related work items that are marked as duplicate."
|
||||
},
|
||||
_project,
|
||||
(int)oldestWorkItem.Id!);
|
||||
}
|
||||
} else {
|
||||
// We never saw a work item like this before, it must be new
|
||||
var entry = await CreateNew();
|
||||
var adoEventType = "AdoNewItem";
|
||||
_logTracer.AddTags(notificationInfo);
|
||||
_logTracer.AddTag("WorkItemId", entry.Id.HasValue ? entry.Id.Value.ToString() : "");
|
||||
_logTracer.LogEvent(adoEventType);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ from typing import (
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
@ -551,8 +552,16 @@ def set_tcp_keepalive() -> None:
|
||||
# Azure Load Balancer default timeout (4 minutes)
|
||||
#
|
||||
# https://urllib3.readthedocs.io/en/stable/reference/urllib3.connection.html?highlight=keep-alive#:~:text=For%20example%2C%20if,socket.SO_KEEPALIVE%2C%201)%2C%0A%5D
|
||||
if value not in urllib3.connection.HTTPConnection.default_socket_options:
|
||||
urllib3.connection.HTTPConnection.default_socket_options.extend((value,))
|
||||
|
||||
default_socket_options = cast(
|
||||
List[Tuple[int, int, int]],
|
||||
urllib3.connection.HTTPConnection.default_socket_options,
|
||||
)
|
||||
|
||||
if value not in default_socket_options:
|
||||
default_socket_options + [
|
||||
value,
|
||||
]
|
||||
|
||||
|
||||
def execute_api(api: Any, api_types: List[Any], version: str) -> int:
|
||||
|
@ -21,7 +21,15 @@ from azure.identity import AzureCliCredential
|
||||
from azure.storage.blob import ContainerClient
|
||||
from onefuzztypes import models, requests, responses
|
||||
from onefuzztypes.enums import ContainerType, TaskType
|
||||
from onefuzztypes.models import BlobRef, Job, NodeAssignment, Report, Task, TaskConfig
|
||||
from onefuzztypes.models import (
|
||||
BlobRef,
|
||||
Job,
|
||||
NodeAssignment,
|
||||
RegressionReport,
|
||||
Report,
|
||||
Task,
|
||||
TaskConfig,
|
||||
)
|
||||
from onefuzztypes.primitives import Container, Directory, PoolName
|
||||
from onefuzztypes.responses import TemplateValidationResponse
|
||||
|
||||
@ -633,35 +641,50 @@ class DebugNotification(Command):
|
||||
self,
|
||||
notificationConfig: models.NotificationConfig,
|
||||
task_id: Optional[UUID] = None,
|
||||
report: Optional[Report] = None,
|
||||
report: Optional[str] = None,
|
||||
) -> responses.NotificationTestResponse:
|
||||
"""Test a notification template"""
|
||||
|
||||
the_report: Union[Report, RegressionReport, None] = None
|
||||
|
||||
if report is not None:
|
||||
try:
|
||||
the_report = RegressionReport.parse_raw(report)
|
||||
print("testing regression report")
|
||||
except Exception:
|
||||
the_report = Report.parse_raw(report)
|
||||
print("testing normal report")
|
||||
|
||||
if task_id is not None:
|
||||
task = self.onefuzz.tasks.get(task_id)
|
||||
if report is None:
|
||||
if the_report is None:
|
||||
input_blob_ref = BlobRef(
|
||||
account="dummy-storage-account",
|
||||
container="test-notification-crashes",
|
||||
name="fake-crash-sample",
|
||||
)
|
||||
report = self._create_report(
|
||||
the_report = self._create_report(
|
||||
task.job_id, task.task_id, "fake_target.exe", input_blob_ref
|
||||
)
|
||||
elif isinstance(the_report, RegressionReport):
|
||||
if the_report.crash_test_result.crash_report is None:
|
||||
raise Exception("invalid regression report: no crash report")
|
||||
the_report.crash_test_result.crash_report.task_id = task.task_id
|
||||
the_report.crash_test_result.crash_report.job_id = task.job_id
|
||||
else:
|
||||
report.task_id = task.task_id
|
||||
report.job_id = task.job_id
|
||||
elif report is None:
|
||||
the_report.task_id = task.task_id
|
||||
the_report.job_id = task.job_id
|
||||
elif the_report is None:
|
||||
raise Exception("must specify either task_id or report")
|
||||
|
||||
report.report_url = "https://dummy-container.blob.core.windows.net/dummy-reports/dummy-report.json"
|
||||
the_report.report_url = "https://dummy-container.blob.core.windows.net/dummy-reports/dummy-report.json"
|
||||
|
||||
endpoint = Endpoint(self.onefuzz)
|
||||
return endpoint._req_model(
|
||||
"POST",
|
||||
responses.NotificationTestResponse,
|
||||
data=requests.NotificationTest(
|
||||
report=report,
|
||||
report=the_report,
|
||||
notification=models.Notification(
|
||||
container=Container("test-notification-reports"),
|
||||
notification_id=uuid.uuid4(),
|
||||
|
@ -256,6 +256,7 @@ class CrashTestResult(BaseModel):
|
||||
class RegressionReport(BaseModel):
|
||||
crash_test_result: CrashTestResult
|
||||
original_crash_test_result: Optional[CrashTestResult]
|
||||
report_url: Optional[str]
|
||||
|
||||
|
||||
class ADODuplicateTemplate(BaseModel):
|
||||
@ -263,6 +264,7 @@ class ADODuplicateTemplate(BaseModel):
|
||||
comment: Optional[str]
|
||||
set_state: Dict[str, str]
|
||||
ado_fields: Dict[str, str]
|
||||
regression_ignore_states: Optional[List[str]]
|
||||
|
||||
|
||||
class ADOTemplate(BaseModel):
|
||||
|
@ -3,7 +3,7 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import AnyHttpUrl, BaseModel, Field, root_validator
|
||||
@ -26,6 +26,7 @@ from .models import (
|
||||
AutoScaleConfig,
|
||||
InstanceConfig,
|
||||
NotificationConfig,
|
||||
RegressionReport,
|
||||
Report,
|
||||
TemplateRenderContext,
|
||||
)
|
||||
@ -280,7 +281,7 @@ class EventsGet(BaseModel):
|
||||
|
||||
|
||||
class NotificationTest(BaseModel):
|
||||
report: Report
|
||||
report: Union[Report, RegressionReport]
|
||||
notification: models.Notification
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user