-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
[Ingest Manager] Make setupIngestManager wait if setup is in progress #70008
Changes from 3 commits
6134a54
431acdd
a8cbc6a
417f45f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,70 +30,93 @@ import { appContextService } from './app_context'; | |
const FLEET_ENROLL_USERNAME = 'fleet_enroll'; | ||
const FLEET_ENROLL_ROLE = 'fleet_enroll'; | ||
|
||
// the promise which tracks the setup | ||
let setupIngestStatus: Promise<void> | undefined; | ||
// default resolve & reject to guard against "undefined is not a function" errors | ||
let onSetupResolve = () => {}; | ||
let onSetupReject = (error: Error) => {}; | ||
|
||
export async function setupIngestManager( | ||
soClient: SavedObjectsClientContract, | ||
callCluster: CallESAsCurrentUser | ||
) { | ||
const [installedPackages, defaultOutput, config] = await Promise.all([ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is from moving the body inside a |
||
// packages installed by default | ||
ensureInstalledDefaultPackages(soClient, callCluster), | ||
outputService.ensureDefaultOutput(soClient), | ||
agentConfigService.ensureDefaultAgentConfig(soClient), | ||
ensureDefaultIndices(callCluster), | ||
settingsService.getSettings(soClient).catch((e: any) => { | ||
if (e.isBoom && e.output.statusCode === 404) { | ||
const http = appContextService.getHttpSetup(); | ||
const serverInfo = http.getServerInfo(); | ||
const basePath = http.basePath; | ||
|
||
const cloud = appContextService.getCloud(); | ||
const cloudId = cloud?.isCloudEnabled && cloud.cloudId; | ||
const cloudUrl = cloudId && decodeCloudId(cloudId)?.kibanaUrl; | ||
const flagsUrl = appContextService.getConfig()?.fleet?.kibana?.host; | ||
const defaultUrl = url.format({ | ||
protocol: serverInfo.protocol, | ||
hostname: serverInfo.host, | ||
port: serverInfo.port, | ||
pathname: basePath.serverBasePath, | ||
}); | ||
|
||
return settingsService.saveSettings(soClient, { | ||
agent_auto_upgrade: true, | ||
package_auto_upgrade: true, | ||
kibana_url: cloudUrl || flagsUrl || defaultUrl, | ||
}); | ||
} | ||
|
||
return Promise.reject(e); | ||
}), | ||
]); | ||
|
||
// ensure default packages are added to the default conifg | ||
const configWithDatasource = await agentConfigService.get(soClient, config.id, true); | ||
if (!configWithDatasource) { | ||
throw new Error('Config not found'); | ||
} | ||
if ( | ||
configWithDatasource.datasources.length && | ||
typeof configWithDatasource.datasources[0] === 'string' | ||
) { | ||
throw new Error('Config not found'); | ||
// installation in progress | ||
if (setupIngestStatus) { | ||
await setupIngestStatus; | ||
} else { | ||
// create the initial promise | ||
setupIngestStatus = new Promise((res, rej) => { | ||
onSetupResolve = res; | ||
onSetupReject = rej; | ||
}); | ||
} | ||
for (const installedPackage of installedPackages) { | ||
const packageShouldBeInstalled = DEFAULT_AGENT_CONFIGS_PACKAGES.some( | ||
(packageName) => installedPackage.name === packageName | ||
); | ||
if (!packageShouldBeInstalled) { | ||
continue; | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason we can't put this whole try/catch block into the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would only run the setup one time which isn't the current or previous behavior. I'm trying to avoid any changes to the prior flow / guarantees or only make the smallest incremental changes required. We'll definitely revisit this and other parts of the setup in later PRs but for now I want small conservative changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, but we were only ever running the entire setup twice because we couldn't track the first. Now there is no need. Otherwise this change is going to cause the Ingest Manager setup to take longer because we have to await the first call before running the whole of the second and awaiting that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Then maybe we can remove the second call? Either way, I'm starting with stability then we can move on to speed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still need the second call, which functions more like a check now, because its purpose is to not let the user interact with Ingest Manager until setup has completed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should talk about this outside this PR. In person or another issue. As far as I can tell, this PR fixes the known bugs and tests are passing. I'd like to take the win and do more in another effort. |
||
const [installedPackages, defaultOutput, config] = await Promise.all([ | ||
// packages installed by default | ||
ensureInstalledDefaultPackages(soClient, callCluster), | ||
outputService.ensureDefaultOutput(soClient), | ||
agentConfigService.ensureDefaultAgentConfig(soClient), | ||
ensureDefaultIndices(callCluster), | ||
settingsService.getSettings(soClient).catch((e: any) => { | ||
if (e.isBoom && e.output.statusCode === 404) { | ||
const http = appContextService.getHttpSetup(); | ||
const serverInfo = http.getServerInfo(); | ||
const basePath = http.basePath; | ||
|
||
const cloud = appContextService.getCloud(); | ||
const cloudId = cloud?.isCloudEnabled && cloud.cloudId; | ||
const cloudUrl = cloudId && decodeCloudId(cloudId)?.kibanaUrl; | ||
const flagsUrl = appContextService.getConfig()?.fleet?.kibana?.host; | ||
const defaultUrl = url.format({ | ||
protocol: serverInfo.protocol, | ||
hostname: serverInfo.host, | ||
port: serverInfo.port, | ||
pathname: basePath.serverBasePath, | ||
}); | ||
|
||
return settingsService.saveSettings(soClient, { | ||
agent_auto_upgrade: true, | ||
package_auto_upgrade: true, | ||
kibana_url: cloudUrl || flagsUrl || defaultUrl, | ||
}); | ||
} | ||
|
||
return Promise.reject(e); | ||
}), | ||
]); | ||
|
||
// ensure default packages are added to the default conifg | ||
const configWithDatasource = await agentConfigService.get(soClient, config.id, true); | ||
if (!configWithDatasource) { | ||
throw new Error('Config not found'); | ||
} | ||
if ( | ||
configWithDatasource.datasources.length && | ||
typeof configWithDatasource.datasources[0] === 'string' | ||
) { | ||
throw new Error('Config not found'); | ||
} | ||
for (const installedPackage of installedPackages) { | ||
const packageShouldBeInstalled = DEFAULT_AGENT_CONFIGS_PACKAGES.some( | ||
(packageName) => installedPackage.name === packageName | ||
); | ||
if (!packageShouldBeInstalled) { | ||
continue; | ||
} | ||
|
||
const isInstalled = configWithDatasource.datasources.some((d: Datasource | string) => { | ||
return typeof d !== 'string' && d.package?.name === installedPackage.name; | ||
}); | ||
|
||
if (!isInstalled) { | ||
await addPackageToConfig(soClient, installedPackage, configWithDatasource, defaultOutput); | ||
const isInstalled = configWithDatasource.datasources.some((d: Datasource | string) => { | ||
return typeof d !== 'string' && d.package?.name === installedPackage.name; | ||
}); | ||
if (!isInstalled) { | ||
await addPackageToConfig(soClient, installedPackage, configWithDatasource, defaultOutput); | ||
} | ||
} | ||
|
||
// if everything works, resolve/succeed | ||
onSetupResolve(); | ||
} catch (error) { | ||
// if anything errors, reject/fail | ||
onSetupReject(error); | ||
} | ||
} | ||
|
||
|
@@ -135,7 +158,7 @@ export async function setupFleet( | |
}, | ||
}); | ||
|
||
await outputService.invalidateCache(); | ||
jfsiii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
outputService.invalidateCache(); | ||
|
||
// save fleet admin user | ||
const defaultOutputId = await outputService.getDefaultOutputId(soClient); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not going to work if we have multiple instances of Kibana no? Can we use a saved object, to keep the state of the setup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I wondered about the same things. From Slack the other day:
I did this because it's the simplest
thingchange that could possibly work, but I do think we'll want to move it to Saved Objects eventually.Along those lines and https://github.com/elastic/kibana/issues/63123 perhaps we want a GET route to match
/fleet/setup
?Do you think these need to be done in this PR or can they be discussed/implemented in a followup issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am in favor of doing it in this PR as I think this is probably not going to solve our problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the failure in multi-kibana exist in the code before #69505, e.g. in 7.8?
I'll see how quickly I can do a dead-simple boolean but, like I said in the description, I think we'll want more fine-grained permissions and I'd like to avoid designing that during the PR or writing such short-lived code if this works. I know, "if" it works.