Skip to content
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

Source resilience #1470

Merged
merged 14 commits into from
Sep 18, 2021
Merged

Source resilience #1470

merged 14 commits into from
Sep 18, 2021

Conversation

JohnMcPMS
Copy link
Member

@JohnMcPMS JohnMcPMS commented Sep 15, 2021

Fixes #1349, #1437 (although #1437 seems to have been re-opened more as a "the store is down" than "the client doesn't handle that well").

Change

Improves handling of errors from one source when multiple are configured by capturing the errors and including them as part of the source open and search results. Some commands will continue to fail if one of the sources has an error to prevent said error from affecting a change to the system. The commands that still fail are install, import, and upgrade --all|<specific> and they now indicate which source failed.

Examples of new behavior:

> wingetdev search terminal
Failed when searching source; results will not be included: msstore
Name                          Id                               Version     Match             Source
---------------------------------------------------------------------------------------------------
Windows Terminal              Microsoft.WindowsTerminal        1.10.2383.0 Moniker: terminal winget
...

> wingetdev install terminal
Failed when searching source: msstore
An unexpected error occurred while executing the command:
WinHttpSendRequest: 12007: The server name or address could not be resolved

The following packages were found among the working sources.
Please specify one of them using the `--source` option to proceed.
Name             Id                        Source
-------------------------------------------------
Windows Terminal Microsoft.WindowsTerminal winget

This change also replaces the experimental msstore source with the more official but still green msstore source (previously called storepreview). This source is now capable of providing basic search functionality against the entire Store catalog. It does not provide any correlation for installed packages however. In addition, the store source is being moved out of experimental.

Validation

Added test source type that can be configured to fail in specific ways to enable easier validation of changes.
Unit tests added to ensure proper error handling.
E2E test added using test source type.

Microsoft Reviewers: Open in CodeFlow

@JohnMcPMS JohnMcPMS requested a review from a team as a code owner September 15, 2021 07:13
@ghost ghost added the Issue-Bug It either shouldn't be doing this or needs an investigation. label Sep 15, 2021
@jedieaston
Copy link
Contributor

jedieaston commented Sep 15, 2021

It seems to me, as a user, winget install should still work without adding a source argument even if one of the sources is down. If I understand the PR description correctly, if I do winget install terminal, and the Microsoft Store is down (as an example), it doesn't matter if there's a great version of Windows Terminal available from the default repo, I just get an error message saying that the msstore is down, which I then use as a hint to start doing winget search so I can find a repo that is up and has it.

I would think the expected behavior (coming from other package managers), would be that winget still installs whatever the closest thing it can find from available sources, since I presumably trust all of the sources I've added equally (or else I wouldn't have them).

If that's too unsafe, then maybe a hint under the error would be nice, like:

> wingetdev install terminal
Failed to open a source; try the 'source reset' command if the problem persists: msstore
An unexpected error occurred while executing the command:
WinHttpSendRequest: 12007: The server name or address could not be resolved

Note: A match was found in the "winget" source. To install it, run winget install terminal --source winget.
(or, if multiple sources have a match, it shows the table)

which would save the trouble of me having to search for someone with the package myself, since presumably winget install already knows everyone who should have it.

@denelon
Copy link
Contributor

denelon commented Sep 15, 2021

@JohnMcPMS what do you think about the suggestion from @jedieaston?

@JohnMcPMS
Copy link
Member Author

If that's too unsafe

I don't think of it as a security issue, but rather a conservative take that we can't know what the user intent was. So if there is a terminal in multiple sources, but one of those is down, how are we to know that the user didn't actually want the unavailable one? Rather than performing a change to the system, I would rather fail "closed" for now. We can consider changes to that based on broad feedback when it is in everyone's hands.

maybe a hint under the error would be nice

This is a good idea and I will add it.

@JohnMcPMS
Copy link
Member Author

This is a good idea and I will add it.

Ok, it is going to require a bit more work than I expected, but I think it will end up being a better experience overall anyway.

@github-actions

This comment has been minimized.

@JohnMcPMS
Copy link
Member Author

@jedieaston , I updated the PR description with the behavior of showing the search results from the not failing sources.

@jedieaston
Copy link
Contributor

That looks awesome! Thanks!

@yao-msft
Copy link
Contributor

    }

nit, unrelated: Dependencies is missing


Refers to: schemas/JSON/settings/settings.schema.0.2.json:121 in c360e5d. [](commit_id = c360e5d, deletion_comment = False)

@yao-msft
Copy link
Contributor

            Workflow::SearchSourceUsingManifest <<

I think this also performs a search against the sources, this is also used in upgrade command

but maybe it's ok we do not perform sophisticated handling for -m cases


Refers to: src/AppInstallerCLICore/Commands/UninstallCommand.cpp:119 in c360e5d. [](commit_id = c360e5d, deletion_comment = False)

// Custom header for Rest sources
std::optional<std::string> CustomHeader;

// Source information containing source agreements, required/unsupported match fields.
SourceInformation Information;

// Prevent correlation against this source if true.
bool DoNotCorrelateAgainst = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

DoNotCorrelateAgainst

SupportsCorrelation?

context <<
Workflow::ReportExecutionStage(Workflow::ExecutionStage::Discovery) <<
Workflow::OpenSource <<
Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) <<
Workflow::SearchSourceForMany <<
Workflow::HandleSearchResultFailures <<
Copy link
Contributor

Choose a reason for hiding this comment

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

HandleSearchResultFailures

This is called after every SearchSourceForMany or SearchSourceForSingle, shall we consolidate them inside?

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 had considered that, but I'm not sure. I think that there is probably a case to be made to have an uber search task (or two) that takes a few well defined behavior flags and roll search + handle failure + ensure* since they are very much linked.

But given our timeframe I'm going to put that on the debt rather than change it here.

error << Resource::String::SearchFailureError << ' ' << failure.Source->GetDetails().Name << std::endl;
HRESULT failureHR = HandleException(context, failure.Exception);

// Just take first failure for now
Copy link
Contributor

Choose a reason for hiding this comment

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

Just take first failure for now

Should we have a new hr for this instead of taking the first 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.

I don't want to create a new failure bucket that is "multiple failures" yet.

Assert.True(result.StdOut.Contains("Failed when searching source: failSearch"));
Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller"));
Assert.False(result.StdOut.Contains("Successfully installed"));
Assert.False(VerifyTestExeInstalled(installDir, "/execustom"));
Copy link
Contributor

Choose a reason for hiding this comment

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

"/execustom"

not needed, we expect the installed file does not exist

@@ -226,6 +226,11 @@
<SubSystem Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(WingetDisableTestHooks)'=='true'">
Copy link
Contributor

Choose a reason for hiding this comment

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

WingetDisableTestHooks

Is this turned on in our official AppInstaller release build?

Copy link
Member Author

Choose a reason for hiding this comment

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

It will be after this is pulled in.

}

// Move failures into the single result
for (SearchResult::Failure& failure : availableResult.Failures)
Copy link
Contributor

@yao-msft yao-msft Sep 17, 2021

Choose a reason for hiding this comment

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

availableResult.Failures

I think this is from a single source(not composite), curious in what scenario will this have Failures entries?

Copy link
Contributor

@yao-msft yao-msft Sep 17, 2021

Choose a reason for hiding this comment

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

Ah, I saw this carries the OpenSource failure. Any reason not adding these failures to OpenSourceResult and handle there?

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 had originally done that, but this pattern made it cleaner to show the suggestions on a failed install command.

}

// Move failures into the single result
std::move(availableResult.Failures.begin(), availableResult.Failures.end(), std::back_inserter(result.Failures));
Copy link
Contributor

Choose a reason for hiding this comment

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

std::move

Should this use AddFailureIfSourceNotPresent to check duplicates?

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably, although I put that in mostly as a guard against unbounded correlation failures.

{
LOG_CAUGHT_EXCEPTION();
AICLI_LOG(Repo, Warning, << "Failed to open source: " << source.get().Name);
openExceptionProxies.emplace_back(std::make_shared<OpenExceptionProxy>(source, std::current_exception()));
Copy link
Contributor

@yao-msft yao-msft Sep 17, 2021

Choose a reason for hiding this comment

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

openExceptionProxies.emplace_back(std::make_shared(source, std::current_exception()));

nit: I think we can just directly add the proxy to the aggregatedSource directly here instead of saving to a list and add all later? #ByDesign

Copy link
Contributor

Choose a reason for hiding this comment

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

never mind, there's a none available check below

[Test]
public void InstallExeWithAlternateSourceFailure()
{
TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"SearchHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\"");
Copy link
Contributor

Choose a reason for hiding this comment

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

""

I may be missing something but why double quotes for json key and value here, I thought it's regular json

Copy link
Member Author

Choose a reason for hiding this comment

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

The double double-quotes are to convince cmd to actually pass in a single double-quote to the process.

}

if (sourceUpdated)
{
sourceList.SaveMetadata();
}

// If all sources failed to open, then throw an exception that is specific to this case.
THROW_HR_IF(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, !aggregatedSource->HasAvailableSource());
Copy link
Contributor

Choose a reason for hiding this comment

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

HasAvailableSource

nit: there is GetAvailableSources().empty()

Copy link
Member Author

Choose a reason for hiding this comment

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

It returns a copy of the vector and I didn't need that.

yao-msft
yao-msft previously approved these changes Sep 17, 2021
Copy link
Contributor

@yao-msft yao-msft left a comment

Choose a reason for hiding this comment

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

:shipit:

@@ -94,7 +93,7 @@ namespace AppInstaller::Repository
details.Arg = s_Source_MSStoreDefault_Arg;
details.Identifier = s_Source_MSStoreDefault_Identifier;
details.TrustLevel = SourceTrustLevel::Trusted;
details.Restricted = true;
details.SupportCorrelation = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

true

should be false here

@yao-msft yao-msft dismissed their stale review September 17, 2021 21:06

revoking review

@JohnMcPMS JohnMcPMS merged commit 2e285f1 into microsoft:master Sep 18, 2021
@JohnMcPMS JohnMcPMS deleted the sourceresilence branch September 18, 2021 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Bug It either shouldn't be doing this or needs an investigation.
Projects
None yet
4 participants