Allow splitting of sequence files

This commit is contained in:
Christian Smith 2017-03-28 12:52:16 +11:00
Родитель 5ec1a6f009
Коммит 11b89a76f3
13 изменённых файлов: 426 добавлений и 81 удалений

Просмотреть файл

@ -42,9 +42,9 @@ namespace Microsoft.Azure.Blast.Web.Controllers.Api
}
[Route("searches/{searchId}/queries"), HttpGet]
public IEnumerable<SearchQuery> GetSearchQueries(Guid searchId)
public IEnumerable<SearchQueryEntity> GetSearchQueries(Guid searchId)
{
return _searchProvider.ListSearchQueries(searchId).OrderBy(q => q.InputFilename);
return _searchProvider.ListSearchQueries(searchId).OrderBy(q => q.QueryFilename);
}
[Route("searches/{searchId}"), HttpGet]
@ -116,13 +116,89 @@ namespace Microsoft.Azure.Blast.Web.Controllers.Api
using (var reader = fileInfo.OpenText())
{
var sequenceText = reader.ReadToEnd();
searchInputFiles.Add(new SearchInputFile
var fullFilename = file.Headers.ContentDisposition.FileName.Replace("\"", "");
if (searchModel.SplitSequenceFile)
{
Filename = file.Headers.ContentDisposition.FileName.Replace("\"", ""),
Length = fileInfo.Length,
Content = new MemoryStream(Encoding.UTF8.GetBytes(sequenceText)),
});
var filename = Path.GetFileNameWithoutExtension(fullFilename);
var extension = Path.GetExtension(fullFilename);
int currentSequenceCount = 0;
int currentFileCount = 1;
string currentSequenceContent = null;
string sequenceFilename;
var line = reader.ReadLine();
while (line != null)
{
if (string.IsNullOrEmpty(line) || line.Trim() == "")
{
continue;
}
if (line.StartsWith(">"))
{
if (string.IsNullOrEmpty(currentSequenceContent))
{
// We're the first sequence
currentSequenceContent = line;
}
else
{
currentSequenceCount++;
if (currentSequenceCount % searchModel.SequencesPerQuery == 0)
{
// Flush previous sequence(s)
sequenceFilename = string.Format("{0}_part{1}{2}",
filename, currentFileCount++, extension);
searchInputFiles.Add(new SearchInputFile
{
Filename = sequenceFilename,
Length = Encoding.UTF8.GetByteCount(currentSequenceContent),
Content = new MemoryStream(Encoding.UTF8.GetBytes(currentSequenceContent)),
});
currentSequenceContent = line;
}
else
{
// Keep appending
currentSequenceContent += "\n" + line;
}
}
}
else
{
currentSequenceContent += "\n" + line;
}
line = reader.ReadLine();
}
if (!string.IsNullOrEmpty(currentSequenceContent))
{
// Flush the final one
sequenceFilename = string.Format("{0}_part{1}{2}",
filename, currentFileCount, extension);
searchInputFiles.Add(new SearchInputFile
{
Filename = sequenceFilename,
Length = Encoding.UTF8.GetByteCount(currentSequenceContent),
Content = new MemoryStream(Encoding.UTF8.GetBytes(currentSequenceContent)),
});
}
}
else
{
var sequenceText = reader.ReadToEnd();
searchInputFiles.Add(new SearchInputFile
{
Filename = fullFilename,
Length = fileInfo.Length,
Content = new MemoryStream(Encoding.UTF8.GetBytes(sequenceText)),
});
}
}
try
@ -157,6 +233,12 @@ namespace Microsoft.Azure.Blast.Web.Controllers.Api
"The program name must be provided with the search");
}
if (searchModel.SplitSequenceFile && searchModel.SequencesPerQuery < 1)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest,
"Sequences per query must ne greater than 0 when splitting a sequence file.");
}
if (!searchModel.SearchInputFiles.Any())
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest,
@ -191,6 +273,8 @@ namespace Microsoft.Azure.Blast.Web.Controllers.Api
Executable = formData["executable"],
ExecutableArgs = formData["executableArgs"],
SearchInputFiles = new List<SearchInputFile>(),
SplitSequenceFile = ToBoolean(formData["splitSequenceFile"]),
SequencesPerQuery = ToInt(formData["seqencesPerQuery"], 1),
};
AddPoolSpecToSearch(formData, spec);
@ -231,5 +315,25 @@ namespace Microsoft.Azure.Blast.Web.Controllers.Api
spec.PoolDisplayName = formData["poolName"];
}
}
private bool ToBoolean(string formValue)
{
bool value;
if (Boolean.TryParse(formValue, out value))
{
return value;
}
return false;
}
private int ToInt(string formValue, int defaultValue)
{
int value;
if (int.TryParse(formValue, out value))
{
return value;
}
return defaultValue;
}
}
}

Просмотреть файл

@ -54,7 +54,19 @@
<div class="form-group">
<label class="control-label col-sm-2" for="searchFile">Sequence file(s)</label>
<div class="col-sm-10">
<input id="searchFile" name="searchFile" type="file" multiple="multiple" accept="*" />
<input id="searchFile" name="searchFile" type="file" multiple="multiple" accept="*"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="searchFile">Split sequence file</label>
<div class="col-sm-10">
<input id="splitSequenceFileCheckBox" type="checkbox" name="splitSequenceFile" value="0">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="seqencesPerQuery">Sequences per query</label>
<div class="col-sm-10">
<input id="seqencesPerQuery" type="text" class="form-control" placeholder="1" aria-describedby="basic-addon1" value="1">
</div>
</div>
</div>
@ -235,6 +247,9 @@
var searchName = $('#searchName').val().trim();
var searchSequenceText = $('#searchSequenceText').val().trim();
var splitSequenceFile = $('#splitSequenceFileCheckBox').is(':checked');
var seqencesPerQuery = $('#seqencesPerQuery').val().trim();
var poolId = $('#poolListBtn').text().trim();
var vmSize = $('#vmSizeListBtn').text().trim();
@ -245,6 +260,8 @@
var searchFile = $("#searchFile")[0];
formData.append("searchName", searchName);
formData.append("searchSequenceText", searchSequenceText);
formData.append("splitSequenceFile", splitSequenceFile);
formData.append("seqencesPerQuery", seqencesPerQuery);
formData.append("databaseName", database);
formData.append("executable", executable);
formData.append("executableArgs", executableArgs);

Просмотреть файл

@ -116,7 +116,7 @@
{
<script id="query-template" type="text/x-handlebars-template">
<tr>
<td>{{inputFilename}}</td>
<td>{{queryFilename}}</td>
<td>{{state}}</td>
<td>{{startTimeFormatted}}</td>
<td>{{endTimeFormatted}}</td>

Просмотреть файл

@ -157,10 +157,10 @@
<Compile Include="Searches\QueryState.cs" />
<Compile Include="Searches\Search.cs" />
<Compile Include="Searches\SearchInputFile.cs" />
<Compile Include="Searches\SearchQuery.cs" />
<Compile Include="Searches\SearchSpecification.cs" />
<Compile Include="Searches\SearchState.cs" />
<Compile Include="Searches\TimeSpanExtensions.cs" />
<Compile Include="Storage\Entities\SearchQueryEntity.cs" />
<Compile Include="Storage\InputFileStager.cs" />
<Compile Include="Storage\RenewableBlobLease.cs" />
<Compile Include="Storage\AzureBlobStorageProvider.cs" />
@ -189,6 +189,9 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="BatchScripts\SearchJobManager.py">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="BatchScripts\jobmanager.py">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

Просмотреть файл

@ -0,0 +1,119 @@
import os
import sys
import datetime
import time
import azure.batch.batch_service_client as batch
import azure.batch.batch_auth as batchauth
import azure.batch.models as batchmodels
from azure.storage.table import TableService, TableBatch
from azure.storage.blob import BlockBlobService
def get_search_state(all_tasks_complete, any_failures):
if all_tasks_complete and any_failures:
return 'Error'
if all_tasks_complete:
return 'Complete'
return 'Running'
def get_query_state(task):
if task.state == batchmodels.TaskState.active:
return 'Waiting'
if task.state == batchmodels.TaskState.preparing:
return 'Waiting'
if task.state == batchmodels.TaskState.running:
return 'Running'
if task.state == batchmodels.TaskState.completed:
if task.execution_info.exit_code == 0:
return 'Success'
return 'Error'
def wait_for_tasks_to_complete(
table_service, batch_client, entity_pk, entity_rk, job_id):
"""
Returns when all tasks in the specified job reach the Completed state.
"""
while True:
entity = table_service.get_entity(
'SearchEntity', entity_pk, entity_rk)
tasks = batch_client.task.list(job_id)
incomplete_tasks = [task for task in tasks if
task.id != 'JobManager' and
task.state != batchmodels.TaskState.completed]
complete_tasks = [task for task in tasks if
task.id != 'JobManager' and
task.state == batchmodels.TaskState.completed]
failed_tasks = [task for task in complete_tasks if
task.execution_info.exit_code != 0 or
task.execution_info.scheduling_error is not None]
queries = table_service.query_entities(
'SearchQueryEntity',
filter="PartitionKey eq '{}'".format(entity.RowKey))
updateBatch = TableBatch()
for task in tasks:
matching_queries = [q for q in queries if q.RowKey == task.id]
if not matching_queries:
print('Could not find query {}'.format(task.id))
continue
query = matching_queries[0]
update = False
state = get_query_state(task)
if query._State != state:
query._State = state
update = True
if task.state == batchmodels.TaskState.running:
if not hasattr(query, 'StartTime'):
query.StartTime = task.execution_info.start_time
update = True
if task.state == batchmodels.TaskState.completed:
if not hasattr(query, 'EndTime'):
query.EndTime = task.execution_info.end_time
update = True
if update:
updateBatch.update_entity(query)
table_service.commit_batch('SearchQueryEntity', updateBatch)
all_tasks_complete = not incomplete_tasks
any_failures = len(failed_tasks) > 0
entity.CompletedTasks = len(complete_tasks)
entity._State = get_search_state(all_tasks_complete, any_failures)
if not incomplete_tasks:
entity.EndTime = datetime.datetime.utcnow()
table_service.update_entity('SearchEntity', entity)
return
else:
table_service.update_entity('SearchEntity', entity)
time.sleep(5)
if __name__ == '__main__':
storage_account = sys.argv[1]
storage_key = sys.argv[2]
batch_account = sys.argv[3]
batch_key = sys.argv[4]
batch_url = sys.argv[5]
job_id = sys.argv[6]
entity_pk = sys.argv[7]
entity_rk = sys.argv[8]
table_service = TableService(account_name=storage_account,
account_key=storage_key)
blob_service = BlockBlobService(account_name=storage_account,
account_key=storage_key)
credentials = batchauth.SharedKeyCredentials(batch_account, batch_key)
batch_client = batch.BatchServiceClient(credentials, base_url=batch_url)
wait_for_tasks_to_complete(table_service, batch_client, entity_pk, entity_rk, job_id)

Просмотреть файл

@ -68,13 +68,25 @@ namespace Microsoft.Azure.Batch.Blast.Searches
_blobStorageProvider.UploadBlobFromStream(searchEntity.InputContainer, filename, queryFile.Content);
});
// Keep record of the filenames
searchEntity.Files =
search.SearchInputFiles.Select(queryFile => Path.GetFileName(queryFile.Filename)).ToList();
var queryIndex = 0;
var searchQueries = new List<SearchQueryEntity>();
foreach (var queryFile in search.SearchInputFiles)
{
var query = new SearchQueryEntity(searchEntity.Id, queryIndex.ToString());
query.OutputContainer = searchEntity.OutputContainer;
query.QueryFilename = Path.GetFileName(queryFile.Filename);
query.State = QueryState.Waiting;
query.QueryOutputFilename = GetQueryOutputFilename(searchEntity.OutputfileFormat, queryIndex.ToString());
query.LogOutputFilename = GetLogFilename(searchEntity.OutputfileFormat, queryIndex.ToString());
searchQueries.Add(query);
queryIndex++;
}
_tableStorageProvider.InsertEntities(searchQueries);
// Stage the generic batch scripts to storage
var resourceFiles = InputFileStager.StageImportScripts(_blobStorageProvider);
SubmitBatchJob(searchEntity, resourceFiles);
SubmitBatchJob(searchEntity, searchQueries, resourceFiles);
searchEntity.State = SearchState.Running;
_tableStorageProvider.UpdateEntity(searchEntity);
@ -143,7 +155,7 @@ namespace Microsoft.Azure.Batch.Blast.Searches
return searchEntity;
}
private void SubmitBatchJob(SearchEntity searchEntity, List<ResourceFile> resourceFiles)
private void SubmitBatchJob(SearchEntity searchEntity, IEnumerable<SearchQueryEntity> queries, List<ResourceFile> resourceFiles)
{
PoolInformation poolInfo;
@ -188,32 +200,29 @@ namespace Microsoft.Azure.Batch.Blast.Searches
resourceFiles);
job.Commit();
var tasks = GetTasks(searchEntity);
var tasks = GetTasks(searchEntity, queries);
job.Refresh();
job.AddTask(tasks);
job.Refresh();
job.OnAllTasksComplete = OnAllTasksComplete.TerminateJob;
// job.Refresh();
// job.OnAllTasksComplete = OnAllTasksComplete.TerminateJob;
}
public IEnumerable<CloudTask> GetTasks(SearchEntity searchEntity)
private IEnumerable<CloudTask> GetTasks(SearchEntity searchEntity, IEnumerable<SearchQueryEntity> queries)
{
var taskId = 0;
foreach (var filename in searchEntity.Files)
foreach (var query in queries)
{
var outputFilename = GetQueryFilename(searchEntity.OutputfileFormat, taskId.ToString());
var blastOutputFilename = GetLogFilename(taskId.ToString());
var cmd = string.Format("/bin/bash -c '{0}; result=$?; {1}; exit $result'",
GetBlastCommandLine(searchEntity.DatabaseId, searchEntity.Executable, searchEntity.ExecutableArgsSanitised, filename, outputFilename, blastOutputFilename),
GetBlastCommandLine(searchEntity.DatabaseId, searchEntity.Executable, searchEntity.ExecutableArgsSanitised, query.QueryFilename, query.QueryOutputFilename, query.LogOutputFilename),
GetUploadCommandLine(searchEntity.OutputContainer));
var task = new CloudTask(taskId.ToString(), cmd);
task.DisplayName = filename;
task.DisplayName = query.QueryFilename;
task.UserIdentity = new UserIdentity(new AutoUserSpecification(elevationLevel: ElevationLevel.Admin));
task.ResourceFiles = new List<ResourceFile>
{
new ResourceFile(_blobStorageProvider.GetBlobSAS(searchEntity.InputContainer, filename), filename)
new ResourceFile(_blobStorageProvider.GetBlobSAS(searchEntity.InputContainer, query.QueryFilename), query.QueryFilename)
};
task.EnvironmentSettings = new[]
@ -227,18 +236,27 @@ namespace Microsoft.Azure.Batch.Blast.Searches
}
}
private static string GetQueryFilename(string outputFilenameFormat, string taskId)
private static string GetQueryOutputFilename(string outputFilenameFormat, string taskId)
{
// outputFilenameFormat is only used here to determine if we should use the old legacy names.
if (string.IsNullOrEmpty(outputFilenameFormat))
{
return string.Format("queryoutput-{0}.xml", taskId);
}
return string.Format(outputFilenameFormat, taskId);
}
private static string GetLogFilename(string taskId)
private static string GetLogFilename(string outputFilenameFormat, string taskId)
{
if (string.IsNullOrEmpty(outputFilenameFormat))
{
return string.Format("blastoutput-{0}.log", taskId);
}
return string.Format("log-{0}.txt", taskId);
}
private string GetBlastCommandLine(string databaseName, string executable, string executableArgs,
string queryFilename, string queryOutputFilename, string blastOutputFilename)
string queryFilename, string queryOutputFilename, string logOutputFilename)
{
var outputFormat = "-outfmt 5"; // XML
if (!string.IsNullOrEmpty(executableArgs) && executableArgs.Contains(" -outfmt "))
@ -254,7 +272,7 @@ namespace Microsoft.Azure.Batch.Blast.Searches
outputFormat,
queryOutputFilename,
executableArgs,
blastOutputFilename);
logOutputFilename);
}
/// <summary>
@ -304,13 +322,12 @@ namespace Microsoft.Azure.Batch.Blast.Searches
public JobManagerTask GetJobManagerTask(string searchId, List<ResourceFile> resourceFiles)
{
var cmd =
string.Format("/bin/bash -c 'python3 jobmanager.py {0} {1} {2} {3} {4} {5} {6} {7} {8}'",
string.Format("/bin/bash -c 'python3 SearchJobManager.py {0} {1} {2} {3} {4} {5} {6} {7}'",
_storageCredentials.Account,
_storageCredentials.Key,
_batchCredentials.Account,
_batchCredentials.Key,
_batchCredentials.Url,
typeof(SearchEntity).Name, // Table name
"$AZ_BATCH_JOB_ID",
SearchEntity.AllUsersPk, // PK for JobMananger
searchId); // RK for JobManager
@ -322,7 +339,8 @@ namespace Microsoft.Azure.Batch.Blast.Searches
RunExclusive = false,
UserIdentity = new UserIdentity(new AutoUserSpecification(elevationLevel: ElevationLevel.Admin)),
ResourceFiles = resourceFiles,
Constraints = new TaskConstraints(null, null, 3),
Constraints = new TaskConstraints(null, null, 5),
KillJobOnCompletion = true,
};
}
@ -430,7 +448,7 @@ namespace Microsoft.Azure.Batch.Blast.Searches
return _tableStorageProvider.ListEntities<SearchEntity>(SearchEntity.AllUsersPk);
}
public IEnumerable<SearchQuery> ListSearchQueries(Guid searchId)
public IEnumerable<SearchQueryEntity> ListSearchQueries(Guid searchId)
{
var entity = _tableStorageProvider.GetEntity<SearchEntity>(SearchEntity.AllUsersPk, searchId.ToString());
@ -439,9 +457,24 @@ namespace Microsoft.Azure.Batch.Blast.Searches
throw new Exception("No such search " + searchId);
}
if (entity.Version == 0)
{
return ListLegacySearchQueries(entity);
}
if (entity.Version == 1)
{
return ListV1SearchQueries(entity);
}
throw new ArgumentException("Unknown search version: " + entity.Version);
}
private IEnumerable<SearchQueryEntity> ListLegacySearchQueries(SearchEntity entity)
{
IEnumerable<QueryOutput> queryOutputs = GetAllQueryOutputs(entity).ToList();
List<SearchQuery> searchQueries = new List<SearchQuery>();
List<SearchQueryEntity> searchQueries = new List<SearchQueryEntity>();
try
{
@ -450,16 +483,16 @@ namespace Microsoft.Azure.Batch.Blast.Searches
foreach (var task in tasks)
{
var queryOutput = GetQueryFilename(entity.OutputfileFormat, task.Id);
var logOutput = GetLogFilename(task.Id);
var queryOutput = GetQueryOutputFilename(entity.OutputfileFormat, task.Id);
var logOutput = GetLogFilename(entity.OutputfileFormat, task.Id);
var outputs =
queryOutputs.Where(output => output.Filename == queryOutput || output.Filename == logOutput)
.ToList();
searchQueries.Add(new SearchQuery
searchQueries.Add(new SearchQueryEntity
{
Id = task.Id,
InputFilename = task.DisplayName,
QueryFilename = task.DisplayName,
Outputs = outputs,
State = BatchToQueryState(task),
StartTime = task.ExecutionInformation?.StartTime,
@ -470,20 +503,20 @@ namespace Microsoft.Azure.Batch.Blast.Searches
catch (Exception)
{
var inputFiles = entity.Files;
foreach (var queryNumber in Enumerable.Range(0, (int) entity.TotalTasks))
foreach (var queryNumber in Enumerable.Range(0, (int)entity.TotalTasks))
{
var queryOutput = GetQueryFilename(entity.OutputfileFormat, queryNumber.ToString());
var logOutput = GetLogFilename(queryNumber.ToString());
var queryOutput = GetQueryOutputFilename(entity.OutputfileFormat, queryNumber.ToString());
var logOutput = GetLogFilename(entity.OutputfileFormat, queryNumber.ToString());
var outputs =
queryOutputs.Where(output => output.Filename == queryOutput || output.Filename == logOutput)
.ToList();
try
{
searchQueries.Add(new SearchQuery
searchQueries.Add(new SearchQueryEntity
{
Id = queryNumber.ToString(),
InputFilename = inputFiles[queryNumber],
QueryFilename = inputFiles[queryNumber],
Outputs = outputs,
State = QueryState.Success,
StartTime = null,
@ -500,6 +533,25 @@ namespace Microsoft.Azure.Batch.Blast.Searches
return searchQueries;
}
private IEnumerable<SearchQueryEntity> ListV1SearchQueries(SearchEntity entity)
{
IEnumerable<QueryOutput> queryOutputs = GetAllQueryOutputs(entity).ToList();
var queries = _tableStorageProvider.ListEntities<SearchQueryEntity>(entity.Id.ToString()).ToList();
foreach (var searchQueryEntity in queries)
{
var queryOutput = GetQueryOutputFilename(entity.OutputfileFormat, searchQueryEntity.Id);
var logOutput = GetLogFilename(entity.OutputfileFormat, searchQueryEntity.Id);
var outputs =
queryOutputs.Where(output => output.Filename == queryOutput || output.Filename == logOutput)
.ToList();
searchQueryEntity.Outputs = outputs;
}
return queries;
}
private QueryState BatchToQueryState(CloudTask task)
{
switch (task.State)

Просмотреть файл

@ -19,7 +19,7 @@ namespace Microsoft.Azure.Batch.Blast.Searches
IEnumerable<SearchEntity> ListSearches();
IEnumerable<SearchQuery> ListSearchQueries(Guid searchId);
IEnumerable<SearchQueryEntity> ListSearchQueries(Guid searchId);
string GetSearchQueryOutput(Guid searchId, string queryId, string filename);
}

Просмотреть файл

@ -1,35 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.Azure.Batch.Blast.Searches
{
public class SearchQuery
{
public string Id { get; set; }
public QueryState State { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public string Duration
{
get
{
if (StartTime == null || EndTime == null)
{
return "";
}
return (EndTime.Value - StartTime.Value).GetFriendlyDuration();
}
}
public string InputFilename { get; set; }
public IEnumerable<QueryOutput> Outputs { get; set; }
}
}

Просмотреть файл

@ -17,6 +17,10 @@ namespace Microsoft.Azure.Batch.Blast.Searches
public IEnumerable<SearchInputFile> SearchInputFiles { get; set; }
public bool SplitSequenceFile { get; set; }
public int SequencesPerQuery { get; set; }
public string PoolId { get; set; }
public int? TargetDedicated { get; set; }

Просмотреть файл

@ -27,6 +27,18 @@ namespace Microsoft.Azure.Batch.Blast.Storage
table.Execute(insertOperation);
}
public void InsertEntities<T>(IEnumerable<T> entities) where T : TableEntity
{
CloudTable table = _cloudTableClient.GetTableReference(typeof(T).Name);
table.CreateIfNotExists();
TableBatchOperation batchInsert = new TableBatchOperation();
foreach (var entity in entities)
{
batchInsert.Insert(entity);
}
table.ExecuteBatch(batchInsert);
}
public void UpdateEntity<T>(T entity) where T : TableEntity
{
CloudTable table = _cloudTableClient.GetTableReference(entity.GetType().Name);

Просмотреть файл

@ -23,6 +23,7 @@ namespace Microsoft.Azure.Batch.Blast.Storage.Entities
{
PartitionKey = AllUsersPk;
RowKey = queryId.ToString();
Version = 1;
}
public Guid Id { get { return Guid.Parse(RowKey); } }
@ -33,6 +34,8 @@ namespace Microsoft.Azure.Batch.Blast.Storage.Entities
public Int64 CompletedTasks { get; set; }
public int Version { get; set; }
public string InputContainer { get; set; }
public string OutputContainer { get; set; }

Просмотреть файл

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Batch.Blast.Searches;
using Microsoft.WindowsAzure.Storage.Table;
namespace Microsoft.Azure.Batch.Blast.Storage.Entities
{
public class SearchQueryEntity : TableEntity
{
public SearchQueryEntity(Guid searchId, string queryId)
{
PartitionKey = searchId.ToString();
RowKey = queryId;
CreationTime = DateTime.UtcNow;
}
public SearchQueryEntity()
{ }
public string Id { get { return RowKey; } set { RowKey = value; } }
public string OutputContainer { get; set; }
public string QueryFilename { get; set; }
public string LogOutputFilename { get; set; }
public string QueryOutputFilename { get; set; }
public string _State { get; set; }
[IgnoreProperty]
public QueryState State
{
get { return (QueryState)Enum.Parse(typeof(QueryState), _State); }
set { _State = value.ToString(); }
}
public DateTime CreationTime { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
[IgnoreProperty]
public string Duration
{
get
{
if (StartTime == null || EndTime == null)
{
return "";
}
return (EndTime.Value - StartTime.Value).GetFriendlyDuration();
}
}
[IgnoreProperty]
public IEnumerable<QueryOutput> Outputs { get; set; }
}
}

Просмотреть файл

@ -10,6 +10,8 @@ namespace Microsoft.Azure.Batch.Blast.Storage
{
void InsertEntity<T>(T entity) where T : TableEntity;
void InsertEntities<T>(IEnumerable<T> entities) where T : TableEntity;
void UpdateEntity<T>(T entity) where T : TableEntity;
void UpsertEntity<T>(T entity) where T : TableEntity;