-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[SLO] Check for unique SLO ids across spaces #214496
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
Changes from all commits
da55fe0
8400f3f
bb9a1c3
48dfbf4
0c8df52
61745bb
7d865cb
03f2be8
ccf84c2
b445717
fde79d6
a05554f
34d37f0
1446124
b665609
0e08115
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 |
|---|---|---|
|
|
@@ -6,11 +6,18 @@ | |
| */ | ||
| import { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/types'; | ||
| import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; | ||
| import { ElasticsearchClient, IBasePath, IScopedClusterClient, Logger } from '@kbn/core/server'; | ||
| import { | ||
| ElasticsearchClient, | ||
| IBasePath, | ||
| IScopedClusterClient, | ||
| Logger, | ||
| SavedObjectsClientContract, | ||
| } from '@kbn/core/server'; | ||
| import { ALL_VALUE, CreateSLOParams, CreateSLOResponse } from '@kbn/slo-schema'; | ||
| import { asyncForEach } from '@kbn/std'; | ||
| import { merge } from 'lodash'; | ||
| import { v4 as uuidv4 } from 'uuid'; | ||
| import { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants'; | ||
| import { | ||
| SLO_MODEL_VERSION, | ||
| SUMMARY_TEMP_INDEX_NAME, | ||
|
|
@@ -21,7 +28,7 @@ import { | |
| } from '../../common/constants'; | ||
| import { getSLIPipelineTemplate } from '../assets/ingest_templates/sli_pipeline_template'; | ||
| import { getSummaryPipelineTemplate } from '../assets/ingest_templates/summary_pipeline_template'; | ||
| import { Duration, DurationUnit, SLODefinition } from '../domain/models'; | ||
| import { Duration, DurationUnit, SLODefinition, StoredSLODefinition } from '../domain/models'; | ||
| import { validateSLO } from '../domain/services'; | ||
| import { SLOIdConflict, SecurityException } from '../errors'; | ||
| import { retryTransientEsErrors } from '../utils/retry'; | ||
|
|
@@ -30,12 +37,14 @@ import { createTempSummaryDocument } from './summary_transform_generator/helpers | |
| import { TransformManager } from './transform_manager'; | ||
| import { assertExpectedIndicatorSourceIndexPrivileges } from './utils/assert_expected_indicator_source_index_privileges'; | ||
| import { getTransformQueryComposite } from './utils/get_transform_compite_query'; | ||
| import { SO_SLO_TYPE } from '../saved_objects'; | ||
|
|
||
| export class CreateSLO { | ||
| constructor( | ||
| private esClient: ElasticsearchClient, | ||
| private scopedClusterClient: IScopedClusterClient, | ||
| private repository: SLORepository, | ||
| private internalSOClient: SavedObjectsClientContract, | ||
| private transformManager: TransformManager, | ||
| private summaryTransformManager: TransformManager, | ||
| private logger: Logger, | ||
|
|
@@ -123,7 +132,15 @@ export class CreateSLO { | |
| } | ||
|
|
||
| private async assertSLOInexistant(slo: SLODefinition) { | ||
| const exists = await this.repository.exists(slo.id); | ||
|
Contributor
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 know I'm the one who suggested a service for that but thinking it more thoroughly we could avoid the extra wiring, and just provide the internalSOClient to the CreateSLO application service, and this
Contributor
Author
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. The service was actually easier to implement because passing in the new internal client directly would have required updating the repository instances in all routes. Adding a new service was less of a breaking change. |
||
| const findResponse = await this.internalSOClient.find<StoredSLODefinition>({ | ||
| type: SO_SLO_TYPE, | ||
| perPage: 0, | ||
| filter: `slo.attributes.id:(${slo.id})`, | ||
| namespaces: [ALL_SPACES_ID], | ||
| }); | ||
|
|
||
| const exists = findResponse.total > 0; | ||
|
|
||
| if (exists) { | ||
| throw new SLOIdConflict(`SLO [${slo.id}] already exists`); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ import { TransformHelper, createTransformHelper } from './helpers/transform'; | |
|
|
||
| export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { | ||
| const esClient = getService('es'); | ||
| const spaceApi = getService('spaces'); | ||
| const sloApi = getService('sloApi'); | ||
| const logger = getService('log'); | ||
| const retry = getService('retry'); | ||
|
|
@@ -51,6 +52,8 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { | |
| await cleanup({ client: esClient, config: DATA_FORGE_CONFIG, logger }); | ||
| await sloApi.deleteAllSLOs(adminRoleAuthc); | ||
| await samlAuth.invalidateM2mApiKeyWithRoleScope(adminRoleAuthc); | ||
| await spaceApi.delete('space1'); | ||
| await spaceApi.delete('space2'); | ||
| }); | ||
|
|
||
| it('creates a new slo and transforms', async () => { | ||
|
|
@@ -121,6 +124,40 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { | |
| }); | ||
| }); | ||
|
|
||
| it('creates two SLOs with matching ids across different spaces', async () => { | ||
|
Contributor
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. 👍🏻 Nice! |
||
| const spaceApiResponse = await spaceApi.create({ | ||
| name: 'space1', | ||
| id: 'space1', | ||
| initials: '1', | ||
| }); | ||
| expect(spaceApiResponse.space).property('id'); | ||
|
|
||
| const { | ||
| space: { id: spaceId1 }, | ||
| } = spaceApiResponse; | ||
| const sloApiResponse = await sloApi.createWithSpace( | ||
| DEFAULT_SLO, | ||
| spaceId1, | ||
| adminRoleAuthc, | ||
| 200 | ||
| ); | ||
| expect(sloApiResponse).property('id'); | ||
|
|
||
| const { id } = sloApiResponse; | ||
| const spaceApiResponse2 = await spaceApi.create({ | ||
| name: 'space2', | ||
| id: 'space2', | ||
| initials: '2', | ||
| }); | ||
|
|
||
| const { | ||
| space: { id: spaceId2 }, | ||
| } = spaceApiResponse; | ||
| expect(spaceApiResponse2.space).property('id'); | ||
|
|
||
| await sloApi.createWithSpace({ ...DEFAULT_SLO, id }, spaceId2, adminRoleAuthc, 409); | ||
| }); | ||
|
|
||
| describe('groupBy smoke tests', () => { | ||
| it('creates instanceId for SLOs with multi groupBy', async () => { | ||
| const apiResponse = await sloApi.create( | ||
|
|
||
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.
one nice refactor you could do is move these common declarations like internalSOClinet to https://github.com/elastic/kibana/blob/main/x-pack/solutions/observability/plugins/slo/server/routes/register_routes.ts#L30
this it would become available in
const sloContext = await context.slo;and you could pass that context to each service, that way you won't have to declare stuff like this in each route where required.