Skip to content

Refactor workload set and workload install/update logic #39991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Jul 1, 2024

Conversation

dsplaisted
Copy link
Member

@dsplaisted dsplaisted commented Apr 3, 2024

  • Fix file not found error updating in workload set mode if no workload sets for feature band were found
  • Add workload set installation records when installing workload sets in file based installer
  • Refactor workload set install / update logic to pass around workload set version instead of using a file in the advertising manifest path to pass the version
  • Refactor workload update and install commands to share update logic
  • Refactor workload set installation logic in file based installer to share logic with workload manifest installation
  • Update logic mapping from workload set version to feature band, to resolve ambiguity that previously existed. Now the preview part of the workload set version will be used for the feature band if the workload set version doesn't have a fourth version number part.
  • Unify logic to read workload set json files into WorkloadSet.FromWorkloadSetFolder

@ghost ghost added Area-Workloads untriaged Request triage from a team member labels Apr 3, 2024
@dsplaisted dsplaisted force-pushed the refactor-workload-sets branch from 68f2114 to 43f7012 Compare April 4, 2024 03:56
@dsplaisted dsplaisted force-pushed the refactor-workload-sets branch from 43f7012 to 58bf7f6 Compare April 4, 2024 11:34
@dsplaisted dsplaisted changed the title Refactor workload sets Refactor workload set and workload install/update logic Apr 4, 2024
@dsplaisted dsplaisted marked this pull request as ready for review April 4, 2024 12:28
@dsplaisted dsplaisted requested review from joeloff, Forgind and a team April 4, 2024 12:38
{
Directory.Delete(dir);
directoriesDeleted++;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a concern that we'll get stuck in this loop?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really—we'll start out a finite number of directories deep, and that provides an upper limit to how many times it'll run.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to use this as part of cleaning up the workload install records, but I didn't want it to delete directories further than a certain level. It probably would have been OK to delete empty directories and they would have been recreated if needed, but it felt safer to me to limit it.

Copy link
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm most of the way through this but want to give it another look later. Appreciate the refactoring!

_workloadInstaller.RemoveManifestsFromInstallState(_sdkFeatureBand);
}

_workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, string.IsNullOrWhiteSpace(_workloadSetVersion) ? null : _workloadSetVersion);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we're removing the workload set from the install state if it had been pinned previously even if we're doing an install. As in your todo, we need to leave it if the workload set version is pinned in the install state for install. I did that in my PR here, though it might be cleaner to handle this with the 'resolvedWorkloadSetVersion' you made.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolvedWorkloadSetVersion wasn't what I'd thought it was, so ignore that part.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also be sure we handle global.json properly.

}
// All installation records for the manifest were removed, so we can delete the manifest
_reporter.WriteLine(string.Format(LocalizableStrings.DeletingWorkloadManifest, manifestId, $"{manifestVersion}/{manifestFeatureBand}"));
var manifestPath = Path.Combine(GetManifestInstallDirForFeatureBand(manifestFeatureBand.ToString()), manifestId.ToString(), manifestVersion.ToString());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we have to delete the install records anymore? Wouldn't leaving old ones behind mean we wouldn't be able to delete it if it's reinstalled later?

{
manifestsToUpdate = InstallWorkloadSet(context);
var installStateFilePath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetPath), "default.json");
if (string.IsNullOrWhiteSpace(_fromRollbackDefinition) && File.Exists(installStateFilePath) && InstallStateContents.FromString(File.ReadAllText(installStateFilePath)).Manifests is not null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (string.IsNullOrWhiteSpace(_fromRollbackDefinition) && File.Exists(installStateFilePath) && InstallStateContents.FromString(File.ReadAllText(installStateFilePath)).Manifests is not null)
if (string.IsNullOrWhiteSpace(_fromRollbackDefinition) && InstallStateContents.FromPath(installStateFilePath)?.Manifests is not null)

This is what FromPath is for 🙂

}
if (_skipManifestUpdate)
{
_workloadInstaller.InstallWorkloads(workloadIds, _sdkFeatureBand, context, offlineCache);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this raises an interesting question:
If you ask for a workload set (via global.json, say) and you also specify to skip manifest updates, what should our response be? I don't think "install the latest version" makes sense because that would violate our workload set, but we can't update other manifests, so there's no point in aligning with the workload set. Perhaps that should be an error?

}

// Write workload installation records
var recordRepo = _workloadInstaller.GetWorkloadInstallationRecordRepository();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to do this part before we install more workloads? Wouldn't this just be empty?

}
});

TryRunGarbageCollection(_workloadInstaller, Reporter, Verbosity, workloadSetVersion => _workloadResolverFactory.CreateForWorkloadSet(_dotnetPath, _sdkVersion.ToString(), _userProfileDir, workloadSetVersion), offlineCache);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to refresh workload manifests before we run GC.

string[] sections = setVersion.Split(new char[] { '-', '+' }, 2);
string versionCore = sections[0];
string preReleaseOrBuild = sections.Length > 1 ? sections[1] : null;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you doing all this manually instead of using NugetVersion's parsing? I'm having a little trouble figuring out what exactly you're doing here.

/cc: @joeloff

@@ -194,6 +214,17 @@ public static void AdvertiseWorkloadUpdates()
}
}

public string GetAdvertisedWorkloadSetVersion()
{
var advertisedPath = GetAdvertisingManifestPath(_sdkFeatureBand, new ManifestId(WorkloadSetManifestId));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's only ever one advertising manifest for workload sets? I don't remember for sure.

@@ -3,6 +3,7 @@

namespace Microsoft.NET.Sdk.WorkloadManifestReader
{
// TODO: Do we need this class, or the existing version information anymore now that workload manifest are side by side?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only time I can think of that it would be helpful is if we're rolling back to a pinned version of something, but I don't think we handle that 100% correctly now, so we can probably dispense with it.

{
manifestsToUpdate = InstallWorkloadSet(context, resolvedWorkloadSetVersion);
}
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could probably make this whole thing a ternary; I only didn't because the return value was different for workload sets.

_workloadInstaller.RemoveManifestsFromInstallState(_sdkFeatureBand);
}

_workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, string.IsNullOrWhiteSpace(_workloadSetVersion) ? null : _workloadSetVersion);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolvedWorkloadSetVersion wasn't what I'd thought it was, so ignore that part.

_workloadInstaller.RemoveManifestsFromInstallState(_sdkFeatureBand);
}

_workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, string.IsNullOrWhiteSpace(_workloadSetVersion) ? null : _workloadSetVersion);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also be sure we handle global.json properly.

workloadSet.IsBaselineWorkloadSet = true;
}

workloadSet.Version = workloadSetVersion;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a little weird to assert that this random workload set we found is the version specified in the argument to this method...maybe move this to be next to where we actually figured out what workload set version we wanted? Note that I think if we request version x from NuGet, we might get something else that's close.

@dsplaisted dsplaisted force-pushed the refactor-workload-sets branch from 58bf7f6 to 7099ad0 Compare May 14, 2024 16:40
@dsplaisted dsplaisted requested a review from a team as a code owner May 14, 2024 16:40
@dsplaisted dsplaisted changed the base branch from release/8.0.3xx to release/8.0.4xx May 14, 2024 17:10
@dsplaisted dsplaisted force-pushed the refactor-workload-sets branch from 599a15a to 2438d99 Compare May 21, 2024 13:44
@dsplaisted dsplaisted force-pushed the refactor-workload-sets branch from 8a4f1db to d6f4293 Compare May 28, 2024 22:38
@dsplaisted dsplaisted force-pushed the refactor-workload-sets branch from d5658cd to b5a26b2 Compare June 11, 2024 21:07
@dsplaisted dsplaisted force-pushed the refactor-workload-sets branch from c30f7b4 to ce07ca3 Compare June 25, 2024 17:31
@dsplaisted dsplaisted force-pushed the refactor-workload-sets branch from ce07ca3 to 4bb9d50 Compare June 25, 2024 17:57
Copy link
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't reviewed tests yet

{
RunInNewTransaction(context =>
var installStateFilePath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetPath), "default.json");
if (string.IsNullOrWhiteSpace(_fromRollbackDefinition) &&
Copy link
Contributor

@Forgind Forgind Jun 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't there be more conditions here for other reasons to require a manifest update?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clarification: consider adding string.isnull checks for --version inputs and global.json version inputs to be complete

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping on this one

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying that you think we should skip manifest update if --sdk-version is specified or if there's a workload version in global.json? In those cases we do want to call the UpdateWorkloadManifests method. That method is responsible for respecting the version from --sdk-version or global.json. If a version was specified in either of those ways, then it will install those versions.

So I think the logic is correct, let me know if there's a case I'm missing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should skip manifest update if --sdk-version is specified or if there's a workload version in global.json

The opposite: I think we should never skip manifest updates if either of those is specified. This if statement currently says that if rollback is not specified, and there is something pinned in the install state file, then we do skip manifest updates, which is wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (string.IsNullOrWhiteSpace(_fromRollbackDefinition) &&
if (string.IsNullOrWhiteSpace(_fromRollbackDefinition) &&
!SpecifiedWorkloadSetVersionOnCommandLine &&
!SpecifiedWorkloadSetVersionInGlobalJson &&

Copy link
Member

@marcpopMSFT marcpopMSFT Jun 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explanation as I understand it is that forgind is saying that if we already have an install state, this logic will skip workload updates even if you have a global.json or --version specified (which could be different than what is in the install state). So I think you are agreeing with each other. Daniel, I think you're implying that UpdateWorkloadManifests would end up getting called for those cases which I think is true the first time a customer does it but any subsequent times after having an install state, I think forgind may be right unless I'm missing a code path that calls it elsewhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think I get it now. I will try to add a test to validate the behavior and then update the logic. I'm not sure how quickly I'll be able to do that though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed now


RunInNewTransaction(context =>
{
if (!_skipManifestUpdate)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't figured out why yet, but skipping UpdateWorkloadManifests here makes me uncomfortable.

Copy link
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still haven't gotten to tests yet, but I think the code looks good other than these 6. (I mostly just care about the fourth and sixth.)

{
RunInNewTransaction(context =>
var installStateFilePath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetPath), "default.json");
if (string.IsNullOrWhiteSpace(_fromRollbackDefinition) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping on this one

throw new NotImplementedException();
}

[Fact]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a test that tries to install from another feature band?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up testing this incidentally, as I switched to using 8.0.302 instead of an 8.0.300-preview.0 build. That's how I found the issue with cross-band workload set GC.

Some of the VM tests are now failing though because of the band mismatch (because when you do a workload set update on an 8.0.300 band SDK, it won't install 8.0.300-preview.0 workload sets, which is what the tests are expecting).

I will probably try to sort through that and get all the tests working separately from this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just thinking perhaps we could have another test that just throws a NotImplementedException (that we'll later make real) to explicitly do cross band install/update, but if it's covered decently well by the 8.0.300-preview tests, that works for me.

Copy link
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Looks good to me! 🚢

@@ -294,6 +293,52 @@ public void InstallWithVersionAndSkipManifestUpdate()
.And.HaveStdErrContaining("--sdk-version");
}

[Fact]
public void InstallWithVersionWhenPinned()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the perfect test for testing this change. Simple, elegant, and it works. It'd be nice to make it run in CI, but this should be fine.

@marcpopMSFT
Copy link
Member

Merging per offline discussions as this is now signed off. We'll get it merged into VS main for testing.

@marcpopMSFT marcpopMSFT merged commit 4f90ed8 into dotnet:release/8.0.4xx Jul 1, 2024
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Workloads untriaged Request triage from a team member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants