Skip to content

Commit

Permalink
[DevBox] Start the Dev Box while configuring (#273)
Browse files Browse the repository at this point in the history
* Added restart capability

* Remove check

* Changes as per PR comments

* Add try/catch

* Changed timeout

* Changed user message

---------

Co-authored-by: Huzaifa Danish <[email protected]>
  • Loading branch information
huzaifa-d and huzaifa-msft authored Sep 24, 2024
1 parent 9eb59f1 commit c63dd93
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 31 deletions.
2 changes: 1 addition & 1 deletion src/AzureExtension/DevBox/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public static class Constants
/// </summary>
public const uint IndefiniteProgress = 0;

public static readonly TimeSpan OneMinutePeriod = TimeSpan.FromMinutes(1);
public static readonly TimeSpan HalfMinutePeriod = TimeSpan.FromSeconds(30);

public static readonly TimeSpan ThreeMinutePeriod = TimeSpan.FromMinutes(3);

Expand Down
2 changes: 1 addition & 1 deletion src/AzureExtension/DevBox/DevBoxInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ public IAsyncOperation<IEnumerable<ComputeSystemProperty>> GetComputeSystemPrope

public IApplyConfigurationOperation CreateApplyConfigurationOperation(string configuration)
{
return new WingetConfigWrapper(configuration, DevBoxState.Uri, _devBoxManagementService, AssociatedDeveloperId, _log, GetState(), ConnectAsync);
return new WingetConfigWrapper(this, configuration, _devBoxManagementService, _log);
}

// Unsupported operations
Expand Down
82 changes: 58 additions & 24 deletions src/AzureExtension/DevBox/Helpers/WingetConfigWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ public class WingetConfigWrapper : IApplyConfigurationOperation, IDisposable

public const string ValidationFailedKey = "DevBox_ValidationFailedKey";

public const string NotRunningFailedKey = "DevBox_NotRunningFailedKey";
public const string DevBoxErrorStateKey = "DevBox_ErrorStateKey";

public const string DevBoxErrorStartKey = "DevBox_ErrorStartKey";

public event TypedEventHandler<IApplyConfigurationOperation, ApplyConfigurationActionRequiredEventArgs> ActionRequired = (s, e) => { };

Expand All @@ -70,10 +72,6 @@ public class WingetConfigWrapper : IApplyConfigurationOperation, IDisposable

private readonly ManualResetEvent _resumeEvent = new(false);

private readonly ComputeSystemState _computeSystemState;

private readonly Func<string, IAsyncOperation<ComputeSystemOperationResult>> _connectAsync;

// Using a common failure result for all the tasks
// since we don't get any other information from the REST API
private readonly ConfigurationUnitResultInformation _commonFailureResult = new(
Expand All @@ -93,23 +91,20 @@ public class WingetConfigWrapper : IApplyConfigurationOperation, IDisposable

private bool _alreadyUpdatedUI;

private DevBoxInstance _devBox;

public WingetConfigWrapper(
DevBoxInstance devBoxInstance,
string configuration,
string baseAPI,
IDevBoxManagementService devBoxManagementService,
IDeveloperId associatedDeveloperId,
Serilog.ILogger log,
ComputeSystemState computeSystemState,
Func<string, IAsyncOperation<ComputeSystemOperationResult>> connectAsync)
Serilog.ILogger log)
{
_baseAPI = baseAPI;
_devBox = devBoxInstance;
_baseAPI = _devBox.DevBoxState.Uri;
_taskAPI = $"{_baseAPI}{Constants.CustomizationAPI}{DateTime.Now.ToFileTimeUtc()}?{Constants.APIVersion}";
_managementService = devBoxManagementService;
_devId = associatedDeveloperId;
_devId = _devBox.AssociatedDeveloperId;
_log = log;
_computeSystemState = computeSystemState;
_connectAsync = connectAsync;

Initialize(configuration);
}

Expand Down Expand Up @@ -300,7 +295,7 @@ private void SetStateForCustomizationTask(TaskJSONToCSClasses.BaseClass response
if (_launchEvent.WaitOne(0))
{
_log.Information("Launching the dev box");
_connectAsync(string.Empty).GetAwaiter().GetResult();
_devBox.ConnectAsync(string.Empty).GetAwaiter().GetResult();
Thread.Sleep(TimeSpan.FromSeconds(30));
_launchEvent.Reset();
}
Expand Down Expand Up @@ -328,21 +323,60 @@ private void SetStateForCustomizationTask(TaskJSONToCSClasses.BaseClass response
}
}

// Start the Dev Box if it isn't already running
private async Task HandleNonRunningState()
{
// Check if the dev box might have been started in the meantime
if (_devBox.GetState() == ComputeSystemState.Running)
{
return;
}

// Start the Dev Box
try
{
_log.Information("Starting the dev box to apply configuration");
var startResult = await _devBox.StartAsync(string.Empty);
}
catch (Exception ex)
{
_log.Error(ex, "Unable to start the dev box");
throw new InvalidOperationException(Resources.GetResource(DevBoxErrorStateKey, _devBox.DisplayName));
}

// Notify the user
ConfigurationSetStateChanged?.Invoke(this, new(new(ConfigurationSetChangeEventType.SetStateChanged, ConfigurationSetState.StartingDevice, ConfigurationUnitState.Unknown, null, null)));

// Wait for the Dev Box to start with a timeout of 15 minutes
var delay = TimeSpan.FromSeconds(30);
var waitTimeLeft = TimeSpan.FromMinutes(15);

// Wait for the Dev Box to start, polling every 30 seconds
while ((waitTimeLeft > TimeSpan.Zero) && (_devBox.GetState() != ComputeSystemState.Running))
{
await Task.Delay(delay);
waitTimeLeft -= delay;
}

// If there was a timeout
if (_devBox.GetState() != ComputeSystemState.Running)
{
throw new InvalidOperationException(Resources.GetResource(DevBoxErrorStateKey, _devBox.DisplayName));
}

var timeTaken = TimeSpan.FromMinutes(15) - waitTimeLeft;
_log.Information($"Started successfully after {timeTaken.Minutes} minutes");
}

IAsyncOperation<ApplyConfigurationResult> IApplyConfigurationOperation.StartAsync()
{
return Task.Run(async () =>
{
try
{
if (_computeSystemState != ComputeSystemState.Running)
if (_devBox.GetState() != ComputeSystemState.Running)
{
// Check if the dev box might have been started in the meantime
var stateRequest = await _managementService.HttpsRequestToDataPlane(new Uri(_baseAPI), _devId, HttpMethod.Get, null);
var state = JsonSerializer.Deserialize<DevBoxMachineState>(stateRequest.JsonResponseRoot.ToString(), Constants.JsonOptions)!;
if (state.PowerState != Constants.DevBoxPowerStates.Running)
{
throw new InvalidOperationException(Resources.GetResource(NotRunningFailedKey));
}
await HandleNonRunningState();
}

_log.Information($"Applying config {_fullTaskJSON}");
Expand Down
2 changes: 1 addition & 1 deletion src/AzureExtension/Services/DevBox/TimeSpanService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public TimeSpan GetPeriodIntervalBasedOnAction(DevBoxActionToPerform actionToPer
case DevBoxActionToPerform.Create:
return DevBoxConstants.ThreeMinutePeriod;
default:
return DevBoxConstants.OneMinutePeriod;
return DevBoxConstants.HalfMinutePeriod;
}
}
}
12 changes: 8 additions & 4 deletions src/AzureExtension/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -551,12 +551,16 @@
<comment>Title text of the dialog asking to log in to the Dev Box.</comment>
</data>
<data name="DevBox_NoDefaultUserFailKey" xml:space="preserve">
<value>No default user logged in. Please login from the Settings page.</value>
<value>No default user logged in. Please login from the Settings page to view the associated Dev Boxes.</value>
<comment>Error shown when no user name is sent to the extension.</comment>
</data>
<data name="DevBox_NotRunningFailedKey" xml:space="preserve">
<value>The Dev Box isn't running. Please start the Dev Box and try again.</value>
<comment>Error shown when the operation fails due to the Dev Box not currently running.</comment>
<data name="DevBox_ErrorStartKey" xml:space="preserve">
<value>The Dev Box {0} couldn't be started</value>
<comment>Error shown when the start operation fails on the Dev Box.</comment>
</data>
<data name="DevBox_ErrorStateKey" xml:space="preserve">
<value>The Dev Box {0} isn't in a valid state</value>
<comment>Error shown when the operation fails due to the Dev Box not being in a valid state.</comment>
</data>
<data name="DevBox_AdaptiveCard_ResumeText" xml:space="preserve">
<value>Resume</value>
Expand Down

0 comments on commit c63dd93

Please sign in to comment.