mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore: resource limits service (#10709)
https://linear.app/unleash/issue/2-3927/implement-resource-limits-service Implements a resource limits service. The implementation looks trivial (or even redundant) in OSS, but by implementing a resource limits service we can make this potentially dynamic and overridable.
This commit is contained in:
		
							parent
							
								
									9d996f14d9
								
							
						
					
					
						commit
						7462465a0b
					
				| @ -9,6 +9,7 @@ import { | |||||||
| } from '../events/createEventsService.js'; | } from '../events/createEventsService.js'; | ||||||
| import FakeApiTokenStore from '../../../test/fixtures/fake-api-token-store.js'; | import FakeApiTokenStore from '../../../test/fixtures/fake-api-token-store.js'; | ||||||
| import { ApiTokenStore } from '../../db/api-token-store.js'; | import { ApiTokenStore } from '../../db/api-token-store.js'; | ||||||
|  | import { ResourceLimitsService } from '../resource-limits/resource-limits-service.js'; | ||||||
| 
 | 
 | ||||||
| export const createApiTokenService = ( | export const createApiTokenService = ( | ||||||
|     db: Db, |     db: Db, | ||||||
| @ -23,11 +24,13 @@ export const createApiTokenService = ( | |||||||
|     ); |     ); | ||||||
|     const environmentStore = new EnvironmentStore(db, eventBus, config); |     const environmentStore = new EnvironmentStore(db, eventBus, config); | ||||||
|     const eventService = createEventsService(db, config); |     const eventService = createEventsService(db, config); | ||||||
|  |     const resourceLimitsService = new ResourceLimitsService(config); | ||||||
| 
 | 
 | ||||||
|     return new ApiTokenService( |     return new ApiTokenService( | ||||||
|         { apiTokenStore, environmentStore }, |         { apiTokenStore, environmentStore }, | ||||||
|         config, |         config, | ||||||
|         eventService, |         eventService, | ||||||
|  |         resourceLimitsService, | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -36,23 +39,27 @@ export const createFakeApiTokenService = ( | |||||||
| ): { | ): { | ||||||
|     apiTokenService: ApiTokenService; |     apiTokenService: ApiTokenService; | ||||||
|     eventService: EventService; |     eventService: EventService; | ||||||
|  |     resourceLimitsService: ResourceLimitsService; | ||||||
|     apiTokenStore: FakeApiTokenStore; |     apiTokenStore: FakeApiTokenStore; | ||||||
|     environmentStore: IEnvironmentStore; |     environmentStore: IEnvironmentStore; | ||||||
| } => { | } => { | ||||||
|     const apiTokenStore = new FakeApiTokenStore(); |     const apiTokenStore = new FakeApiTokenStore(); | ||||||
|     const environmentStore = new FakeEnvironmentStore(); |     const environmentStore = new FakeEnvironmentStore(); | ||||||
|     const eventService = createFakeEventsService(config); |     const eventService = createFakeEventsService(config); | ||||||
|  |     const resourceLimitsService = new ResourceLimitsService(config); | ||||||
| 
 | 
 | ||||||
|     const apiTokenService = new ApiTokenService( |     const apiTokenService = new ApiTokenService( | ||||||
|         { apiTokenStore, environmentStore }, |         { apiTokenStore, environmentStore }, | ||||||
|         config, |         config, | ||||||
|         eventService, |         eventService, | ||||||
|  |         resourceLimitsService, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|         apiTokenService, |         apiTokenService, | ||||||
|         apiTokenStore, |         apiTokenStore, | ||||||
|         eventService, |         eventService, | ||||||
|  |         resourceLimitsService, | ||||||
|         environmentStore, |         environmentStore, | ||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -65,12 +65,13 @@ import { | |||||||
|     createFakeFeatureLinkService, |     createFakeFeatureLinkService, | ||||||
|     createFeatureLinkService, |     createFeatureLinkService, | ||||||
| } from '../feature-links/createFeatureLinkService.js'; | } from '../feature-links/createFeatureLinkService.js'; | ||||||
|  | import { ResourceLimitsService } from '../resource-limits/resource-limits-service.js'; | ||||||
| 
 | 
 | ||||||
| export const createFeatureToggleService = ( | export const createFeatureToggleService = ( | ||||||
|     db: Db, |     db: Db, | ||||||
|     config: IUnleashConfig, |     config: IUnleashConfig, | ||||||
| ): FeatureToggleService => { | ): FeatureToggleService => { | ||||||
|     const { getLogger, eventBus, flagResolver, resourceLimits } = config; |     const { getLogger, eventBus, flagResolver } = config; | ||||||
|     const featureStrategiesStore = new FeatureStrategiesStore( |     const featureStrategiesStore = new FeatureStrategiesStore( | ||||||
|         db, |         db, | ||||||
|         eventBus, |         eventBus, | ||||||
| @ -138,6 +139,8 @@ export const createFeatureToggleService = ( | |||||||
| 
 | 
 | ||||||
|     const featureLinkService = createFeatureLinkService(config)(db); |     const featureLinkService = createFeatureLinkService(config)(db); | ||||||
| 
 | 
 | ||||||
|  |     const resourceLimitsService = new ResourceLimitsService(config); | ||||||
|  | 
 | ||||||
|     const featureToggleService = new FeatureToggleService( |     const featureToggleService = new FeatureToggleService( | ||||||
|         { |         { | ||||||
|             featureStrategiesStore, |             featureStrategiesStore, | ||||||
| @ -149,7 +152,7 @@ export const createFeatureToggleService = ( | |||||||
|             contextFieldStore, |             contextFieldStore, | ||||||
|             strategyStore, |             strategyStore, | ||||||
|         }, |         }, | ||||||
|         { getLogger, flagResolver, eventBus, resourceLimits }, |         { getLogger, flagResolver, eventBus }, | ||||||
|         { |         { | ||||||
|             segmentService, |             segmentService, | ||||||
|             accessService, |             accessService, | ||||||
| @ -162,13 +165,14 @@ export const createFeatureToggleService = ( | |||||||
|             featureCollaboratorsReadModel, |             featureCollaboratorsReadModel, | ||||||
|             featureLinksReadModel, |             featureLinksReadModel, | ||||||
|             featureLinkService, |             featureLinkService, | ||||||
|  |             resourceLimitsService, | ||||||
|         }, |         }, | ||||||
|     ); |     ); | ||||||
|     return featureToggleService; |     return featureToggleService; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const createFakeFeatureToggleService = (config: IUnleashConfig) => { | export const createFakeFeatureToggleService = (config: IUnleashConfig) => { | ||||||
|     const { getLogger, flagResolver, resourceLimits } = config; |     const { getLogger, flagResolver } = config; | ||||||
|     const eventStore = new FakeEventStore(); |     const eventStore = new FakeEventStore(); | ||||||
|     const strategyStore = new FakeStrategiesStore(); |     const strategyStore = new FakeStrategiesStore(); | ||||||
|     const featureStrategiesStore = new FakeFeatureStrategiesStore(); |     const featureStrategiesStore = new FakeFeatureStrategiesStore(); | ||||||
| @ -206,6 +210,8 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => { | |||||||
|     const featureLinksReadModel = new FakeFeatureLinksReadModel(); |     const featureLinksReadModel = new FakeFeatureLinksReadModel(); | ||||||
|     const { featureLinkService } = createFakeFeatureLinkService(config); |     const { featureLinkService } = createFakeFeatureLinkService(config); | ||||||
| 
 | 
 | ||||||
|  |     const resourceLimitsService = new ResourceLimitsService(config); | ||||||
|  | 
 | ||||||
|     const featureToggleService = new FeatureToggleService( |     const featureToggleService = new FeatureToggleService( | ||||||
|         { |         { | ||||||
|             featureStrategiesStore, |             featureStrategiesStore, | ||||||
| @ -221,7 +227,6 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => { | |||||||
|             getLogger, |             getLogger, | ||||||
|             flagResolver, |             flagResolver, | ||||||
|             eventBus: new EventEmitter(), |             eventBus: new EventEmitter(), | ||||||
|             resourceLimits, |  | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             segmentService, |             segmentService, | ||||||
| @ -235,6 +240,7 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => { | |||||||
|             featureCollaboratorsReadModel, |             featureCollaboratorsReadModel, | ||||||
|             featureLinksReadModel, |             featureLinksReadModel, | ||||||
|             featureLinkService, |             featureLinkService, | ||||||
|  |             resourceLimitsService, | ||||||
|         }, |         }, | ||||||
|     ); |     ); | ||||||
|     return { |     return { | ||||||
|  | |||||||
| @ -114,9 +114,9 @@ import type { IFeatureLifecycleReadModel } from '../feature-lifecycle/feature-li | |||||||
| import { throwExceedsLimitError } from '../../error/exceeds-limit-error.js'; | import { throwExceedsLimitError } from '../../error/exceeds-limit-error.js'; | ||||||
| import type { Collaborator } from './types/feature-collaborators-read-model-type.js'; | import type { Collaborator } from './types/feature-collaborators-read-model-type.js'; | ||||||
| import { sortStrategies } from '../../util/sortStrategies.js'; | import { sortStrategies } from '../../util/sortStrategies.js'; | ||||||
| import type { ResourceLimitsSchema } from '../../openapi/index.js'; |  | ||||||
| import type FeatureLinkService from '../feature-links/feature-link-service.js'; | import type FeatureLinkService from '../feature-links/feature-link-service.js'; | ||||||
| import type { IFeatureLink } from '../feature-links/feature-links-read-model-type.js'; | import type { IFeatureLink } from '../feature-links/feature-links-read-model-type.js'; | ||||||
|  | import type { ResourceLimitsService } from '../resource-limits/resource-limits-service.js'; | ||||||
| interface IFeatureContext { | interface IFeatureContext { | ||||||
|     featureName: string; |     featureName: string; | ||||||
|     projectId: string; |     projectId: string; | ||||||
| @ -161,7 +161,7 @@ export type Stores = Pick< | |||||||
| 
 | 
 | ||||||
| export type Config = Pick< | export type Config = Pick< | ||||||
|     IUnleashConfig, |     IUnleashConfig, | ||||||
|     'getLogger' | 'flagResolver' | 'eventBus' | 'resourceLimits' |     'getLogger' | 'flagResolver' | 'eventBus' | ||||||
| >; | >; | ||||||
| 
 | 
 | ||||||
| export type ServicesAndReadModels = { | export type ServicesAndReadModels = { | ||||||
| @ -176,6 +176,7 @@ export type ServicesAndReadModels = { | |||||||
|     featureCollaboratorsReadModel: IFeatureCollaboratorsReadModel; |     featureCollaboratorsReadModel: IFeatureCollaboratorsReadModel; | ||||||
|     featureLinkService: FeatureLinkService; |     featureLinkService: FeatureLinkService; | ||||||
|     featureLinksReadModel: IFeatureLinksReadModel; |     featureLinksReadModel: IFeatureLinksReadModel; | ||||||
|  |     resourceLimitsService: ResourceLimitsService; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class FeatureToggleService { | export class FeatureToggleService { | ||||||
| @ -223,7 +224,7 @@ export class FeatureToggleService { | |||||||
| 
 | 
 | ||||||
|     private eventBus: EventEmitter; |     private eventBus: EventEmitter; | ||||||
| 
 | 
 | ||||||
|     private resourceLimits: ResourceLimitsSchema; |     private resourceLimitsService: ResourceLimitsService; | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         { |         { | ||||||
| @ -236,7 +237,7 @@ export class FeatureToggleService { | |||||||
|             contextFieldStore, |             contextFieldStore, | ||||||
|             strategyStore, |             strategyStore, | ||||||
|         }: Stores, |         }: Stores, | ||||||
|         { getLogger, flagResolver, eventBus, resourceLimits }: Config, |         { getLogger, flagResolver, eventBus }: Config, | ||||||
|         { |         { | ||||||
|             segmentService, |             segmentService, | ||||||
|             accessService, |             accessService, | ||||||
| @ -249,6 +250,7 @@ export class FeatureToggleService { | |||||||
|             featureCollaboratorsReadModel, |             featureCollaboratorsReadModel, | ||||||
|             featureLinksReadModel, |             featureLinksReadModel, | ||||||
|             featureLinkService, |             featureLinkService, | ||||||
|  |             resourceLimitsService, | ||||||
|         }: ServicesAndReadModels, |         }: ServicesAndReadModels, | ||||||
|     ) { |     ) { | ||||||
|         this.logger = getLogger('services/feature-toggle-service.ts'); |         this.logger = getLogger('services/feature-toggle-service.ts'); | ||||||
| @ -273,7 +275,7 @@ export class FeatureToggleService { | |||||||
|         this.featureLinksReadModel = featureLinksReadModel; |         this.featureLinksReadModel = featureLinksReadModel; | ||||||
|         this.featureLinkService = featureLinkService; |         this.featureLinkService = featureLinkService; | ||||||
|         this.eventBus = eventBus; |         this.eventBus = eventBus; | ||||||
|         this.resourceLimits = resourceLimits; |         this.resourceLimitsService = resourceLimitsService; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async validateFeaturesContext( |     async validateFeaturesContext( | ||||||
| @ -396,7 +398,8 @@ export class FeatureToggleService { | |||||||
|         environment: string; |         environment: string; | ||||||
|         featureName: string; |         featureName: string; | ||||||
|     }) { |     }) { | ||||||
|         const limit = this.resourceLimits.featureEnvironmentStrategies; |         const { featureEnvironmentStrategies: limit } = | ||||||
|  |             await this.resourceLimitsService.getResourceLimits(); | ||||||
|         const existingCount = ( |         const existingCount = ( | ||||||
|             await this.featureStrategiesStore.getStrategiesForFeatureEnv( |             await this.featureStrategiesStore.getStrategiesForFeatureEnv( | ||||||
|                 featureEnv.projectId, |                 featureEnv.projectId, | ||||||
| @ -412,14 +415,14 @@ export class FeatureToggleService { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private validateConstraintsLimit(constraints: { |     private async validateConstraintsLimit(constraints: { | ||||||
|         updated: IConstraint[]; |         updated: IConstraint[]; | ||||||
|         existing: IConstraint[]; |         existing: IConstraint[]; | ||||||
|     }) { |     }) { | ||||||
|         const { |         const { | ||||||
|             constraints: constraintsLimit, |             constraints: constraintsLimit, | ||||||
|             constraintValues: constraintValuesLimit, |             constraintValues: constraintValuesLimit, | ||||||
|         } = this.resourceLimits; |         } = await this.resourceLimitsService.getResourceLimits(); | ||||||
| 
 | 
 | ||||||
|         if ( |         if ( | ||||||
|             constraints.updated.length > constraintsLimit && |             constraints.updated.length > constraintsLimit && | ||||||
| @ -711,7 +714,7 @@ export class FeatureToggleService { | |||||||
|         const { name, title, disabled, sortOrder } = strategyConfig; |         const { name, title, disabled, sortOrder } = strategyConfig; | ||||||
|         let { constraints, parameters, variants } = strategyConfig; |         let { constraints, parameters, variants } = strategyConfig; | ||||||
|         if (constraints && constraints.length > 0) { |         if (constraints && constraints.length > 0) { | ||||||
|             this.validateConstraintsLimit({ |             await this.validateConstraintsLimit({ | ||||||
|                 updated: constraints, |                 updated: constraints, | ||||||
|                 existing: existing?.constraints ?? [], |                 existing: existing?.constraints ?? [], | ||||||
|             }); |             }); | ||||||
| @ -1264,7 +1267,8 @@ export class FeatureToggleService { | |||||||
| 
 | 
 | ||||||
|     private async validateFeatureFlagLimit() { |     private async validateFeatureFlagLimit() { | ||||||
|         const currentFlagCount = await this.featureToggleStore.count(); |         const currentFlagCount = await this.featureToggleStore.count(); | ||||||
|         const limit = this.resourceLimits.featureFlags; |         const { featureFlags: limit } = | ||||||
|  |             await this.resourceLimitsService.getResourceLimits(); | ||||||
|         if (currentFlagCount >= limit) { |         if (currentFlagCount >= limit) { | ||||||
|             throwExceedsLimitError(this.eventBus, { |             throwExceedsLimitError(this.eventBus, { | ||||||
|                 resource: 'feature flag', |                 resource: 'feature flag', | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import { | |||||||
|     FavoritesService, |     FavoritesService, | ||||||
|     GroupService, |     GroupService, | ||||||
|     ProjectService, |     ProjectService, | ||||||
|  |     ResourceLimitsService, | ||||||
| } from '../../services/index.js'; | } from '../../services/index.js'; | ||||||
| import FakeGroupStore from '../../../test/fixtures/fake-group-store.js'; | import FakeGroupStore from '../../../test/fixtures/fake-group-store.js'; | ||||||
| import FakeEventStore from '../../../test/fixtures/fake-event-store.js'; | import FakeEventStore from '../../../test/fixtures/fake-event-store.js'; | ||||||
| @ -117,10 +118,13 @@ export const createProjectService = ( | |||||||
| 
 | 
 | ||||||
|     const privateProjectChecker = createPrivateProjectChecker(db, config); |     const privateProjectChecker = createPrivateProjectChecker(db, config); | ||||||
| 
 | 
 | ||||||
|  |     const resourceLimitsService = new ResourceLimitsService(config); | ||||||
|  | 
 | ||||||
|     const apiTokenService = new ApiTokenService( |     const apiTokenService = new ApiTokenService( | ||||||
|         { apiTokenStore, environmentStore }, |         { apiTokenStore, environmentStore }, | ||||||
|         config, |         config, | ||||||
|         eventService, |         eventService, | ||||||
|  |         resourceLimitsService, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     const projectReadModel = createProjectReadModel( |     const projectReadModel = createProjectReadModel( | ||||||
| @ -153,6 +157,7 @@ export const createProjectService = ( | |||||||
|         eventService, |         eventService, | ||||||
|         privateProjectChecker, |         privateProjectChecker, | ||||||
|         apiTokenService, |         apiTokenService, | ||||||
|  |         resourceLimitsService, | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -189,10 +194,13 @@ export const createFakeProjectService = (config: IUnleashConfig) => { | |||||||
|         eventService, |         eventService, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     const resourceLimitsService = new ResourceLimitsService(config); | ||||||
|  | 
 | ||||||
|     const apiTokenService = new ApiTokenService( |     const apiTokenService = new ApiTokenService( | ||||||
|         { apiTokenStore, environmentStore }, |         { apiTokenStore, environmentStore }, | ||||||
|         config, |         config, | ||||||
|         eventService, |         eventService, | ||||||
|  |         resourceLimitsService, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     const projectReadModel = createFakeProjectReadModel(); |     const projectReadModel = createFakeProjectReadModel(); | ||||||
| @ -221,6 +229,7 @@ export const createFakeProjectService = (config: IUnleashConfig) => { | |||||||
|         eventService, |         eventService, | ||||||
|         privateProjectChecker, |         privateProjectChecker, | ||||||
|         apiTokenService, |         apiTokenService, | ||||||
|  |         resourceLimitsService, | ||||||
|     ); |     ); | ||||||
|     return { |     return { | ||||||
|         projectService, |         projectService, | ||||||
|  | |||||||
| @ -67,10 +67,7 @@ import { calculateAverageTimeToProd } from '../feature-toggle/time-to-production | |||||||
| import type { IProjectStatsStore } from '../../types/stores/project-stats-store-type.js'; | import type { IProjectStatsStore } from '../../types/stores/project-stats-store-type.js'; | ||||||
| import { uniqueByKey } from '../../util/unique.js'; | import { uniqueByKey } from '../../util/unique.js'; | ||||||
| import { BadDataError, PermissionError } from '../../error/index.js'; | import { BadDataError, PermissionError } from '../../error/index.js'; | ||||||
| import type { | import type { ProjectDoraMetricsSchema } from '../../openapi/index.js'; | ||||||
|     ProjectDoraMetricsSchema, |  | ||||||
|     ResourceLimitsSchema, |  | ||||||
| } from '../../openapi/index.js'; |  | ||||||
| import { checkFeatureNamingData } from '../feature-naming-pattern/feature-naming-validation.js'; | import { checkFeatureNamingData } from '../feature-naming-pattern/feature-naming-validation.js'; | ||||||
| import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType.js'; | import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType.js'; | ||||||
| import type EventService from '../events/event-service.js'; | import type EventService from '../events/event-service.js'; | ||||||
| @ -89,6 +86,7 @@ import { canGrantProjectRole } from './can-grant-project-role.js'; | |||||||
| import { batchExecute } from '../../util/index.js'; | import { batchExecute } from '../../util/index.js'; | ||||||
| import metricsHelper from '../../util/metrics-helper.js'; | import metricsHelper from '../../util/metrics-helper.js'; | ||||||
| import { FUNCTION_TIME } from '../../metric-events.js'; | import { FUNCTION_TIME } from '../../metric-events.js'; | ||||||
|  | import type { ResourceLimitsService } from '../resource-limits/resource-limits-service.js'; | ||||||
| 
 | 
 | ||||||
| type Days = number; | type Days = number; | ||||||
| type Count = number; | type Count = number; | ||||||
| @ -148,7 +146,7 @@ export default class ProjectService { | |||||||
| 
 | 
 | ||||||
|     private isEnterprise: boolean; |     private isEnterprise: boolean; | ||||||
| 
 | 
 | ||||||
|     private resourceLimits: ResourceLimitsSchema; |     private resourceLimitsService: ResourceLimitsService; | ||||||
| 
 | 
 | ||||||
|     private eventBus: EventEmitter; |     private eventBus: EventEmitter; | ||||||
| 
 | 
 | ||||||
| @ -193,6 +191,7 @@ export default class ProjectService { | |||||||
|         eventService: EventService, |         eventService: EventService, | ||||||
|         privateProjectChecker: IPrivateProjectChecker, |         privateProjectChecker: IPrivateProjectChecker, | ||||||
|         apiTokenService: ApiTokenService, |         apiTokenService: ApiTokenService, | ||||||
|  |         resourceLimitsService: ResourceLimitsService, | ||||||
|     ) { |     ) { | ||||||
|         this.projectStore = projectStore; |         this.projectStore = projectStore; | ||||||
|         this.projectOwnersReadModel = projectOwnersReadModel; |         this.projectOwnersReadModel = projectOwnersReadModel; | ||||||
| @ -213,7 +212,7 @@ export default class ProjectService { | |||||||
|         this.logger = config.getLogger('services/project-service.js'); |         this.logger = config.getLogger('services/project-service.js'); | ||||||
|         this.flagResolver = config.flagResolver; |         this.flagResolver = config.flagResolver; | ||||||
|         this.isEnterprise = config.isEnterprise; |         this.isEnterprise = config.isEnterprise; | ||||||
|         this.resourceLimits = config.resourceLimits; |         this.resourceLimitsService = resourceLimitsService; | ||||||
|         this.eventBus = config.eventBus; |         this.eventBus = config.eventBus; | ||||||
|         this.projectReadModel = projectReadModel; |         this.projectReadModel = projectReadModel; | ||||||
|         this.onboardingReadModel = onboardingReadModel; |         this.onboardingReadModel = onboardingReadModel; | ||||||
| @ -318,7 +317,9 @@ export default class ProjectService { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async validateProjectLimit() { |     async validateProjectLimit() { | ||||||
|         const limit = Math.max(this.resourceLimits.projects, 1); |         const { projects } = | ||||||
|  |             await this.resourceLimitsService.getResourceLimits(); | ||||||
|  |         const limit = Math.max(projects, 1); | ||||||
|         const projectCount = await this.projectStore.count(); |         const projectCount = await this.projectStore.count(); | ||||||
| 
 | 
 | ||||||
|         if (projectCount >= limit) { |         if (projectCount >= limit) { | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								src/lib/features/resource-limits/resource-limits-service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/lib/features/resource-limits/resource-limits-service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | import type { IUnleashConfig } from '../../types/option.js'; | ||||||
|  | import type { ResourceLimitsSchema } from '../../openapi/index.js'; | ||||||
|  | 
 | ||||||
|  | export class ResourceLimitsService { | ||||||
|  |     private config: IUnleashConfig; | ||||||
|  | 
 | ||||||
|  |     constructor(config: IUnleashConfig) { | ||||||
|  |         this.config = config; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getResourceLimits(): Promise<ResourceLimitsSchema> { | ||||||
|  |         return this.config.resourceLimits; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,5 +1,5 @@ | |||||||
| import type { Db, IUnleashConfig } from '../../types/index.js'; | import type { Db, IUnleashConfig } from '../../types/index.js'; | ||||||
| import { SegmentService } from '../../services/index.js'; | import { ResourceLimitsService, SegmentService } from '../../services/index.js'; | ||||||
| import type { ISegmentService } from './segment-service-interface.js'; | import type { ISegmentService } from './segment-service-interface.js'; | ||||||
| import FeatureStrategiesStore from '../feature-toggle/feature-toggle-strategies-store.js'; | import FeatureStrategiesStore from '../feature-toggle/feature-toggle-strategies-store.js'; | ||||||
| import SegmentStore from './segment-store.js'; | import SegmentStore from './segment-store.js'; | ||||||
| @ -51,6 +51,8 @@ export const createSegmentService = ( | |||||||
| 
 | 
 | ||||||
|     const eventService = createEventsService(db, config); |     const eventService = createEventsService(db, config); | ||||||
| 
 | 
 | ||||||
|  |     const resourceLimitsService = new ResourceLimitsService(config); | ||||||
|  | 
 | ||||||
|     return new SegmentService( |     return new SegmentService( | ||||||
|         { segmentStore, featureStrategiesStore }, |         { segmentStore, featureStrategiesStore }, | ||||||
|         changeRequestAccessReadModel, |         changeRequestAccessReadModel, | ||||||
| @ -58,6 +60,7 @@ export const createSegmentService = ( | |||||||
|         config, |         config, | ||||||
|         eventService, |         eventService, | ||||||
|         privateProjectChecker, |         privateProjectChecker, | ||||||
|  |         resourceLimitsService, | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -74,6 +77,8 @@ export const createFakeSegmentService = ( | |||||||
| 
 | 
 | ||||||
|     const eventService = createFakeEventsService(config); |     const eventService = createFakeEventsService(config); | ||||||
| 
 | 
 | ||||||
|  |     const resourceLimitsService = new ResourceLimitsService(config); | ||||||
|  | 
 | ||||||
|     return new SegmentService( |     return new SegmentService( | ||||||
|         { segmentStore, featureStrategiesStore }, |         { segmentStore, featureStrategiesStore }, | ||||||
|         changeRequestAccessReadModel, |         changeRequestAccessReadModel, | ||||||
| @ -81,5 +86,6 @@ export const createFakeSegmentService = ( | |||||||
|         config, |         config, | ||||||
|         eventService, |         eventService, | ||||||
|         privateProjectChecker, |         privateProjectChecker, | ||||||
|  |         resourceLimitsService, | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -25,11 +25,9 @@ import type { IChangeRequestAccessReadModel } from '../change-request-access-ser | |||||||
| import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType.js'; | import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType.js'; | ||||||
| import type EventService from '../events/event-service.js'; | import type EventService from '../events/event-service.js'; | ||||||
| import type { IChangeRequestSegmentUsageReadModel } from '../change-request-segment-usage-service/change-request-segment-usage-read-model.js'; | import type { IChangeRequestSegmentUsageReadModel } from '../change-request-segment-usage-service/change-request-segment-usage-read-model.js'; | ||||||
| import type { | import type { UpsertSegmentSchema } from '../../openapi/index.js'; | ||||||
|     ResourceLimitsSchema, |  | ||||||
|     UpsertSegmentSchema, |  | ||||||
| } from '../../openapi/index.js'; |  | ||||||
| import { throwExceedsLimitError } from '../../error/exceeds-limit-error.js'; | import { throwExceedsLimitError } from '../../error/exceeds-limit-error.js'; | ||||||
|  | import type { ResourceLimitsService } from '../resource-limits/resource-limits-service.js'; | ||||||
| 
 | 
 | ||||||
| export class SegmentService implements ISegmentService { | export class SegmentService implements ISegmentService { | ||||||
|     private logger: Logger; |     private logger: Logger; | ||||||
| @ -50,7 +48,7 @@ export class SegmentService implements ISegmentService { | |||||||
| 
 | 
 | ||||||
|     private privateProjectChecker: IPrivateProjectChecker; |     private privateProjectChecker: IPrivateProjectChecker; | ||||||
| 
 | 
 | ||||||
|     private resourceLimits: ResourceLimitsSchema; |     private resourceLimitsService: ResourceLimitsService; | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         { |         { | ||||||
| @ -62,6 +60,7 @@ export class SegmentService implements ISegmentService { | |||||||
|         config: IUnleashConfig, |         config: IUnleashConfig, | ||||||
|         eventService: EventService, |         eventService: EventService, | ||||||
|         privateProjectChecker: IPrivateProjectChecker, |         privateProjectChecker: IPrivateProjectChecker, | ||||||
|  |         resourceLimitsService: ResourceLimitsService, | ||||||
|     ) { |     ) { | ||||||
|         this.segmentStore = segmentStore; |         this.segmentStore = segmentStore; | ||||||
|         this.featureStrategiesStore = featureStrategiesStore; |         this.featureStrategiesStore = featureStrategiesStore; | ||||||
| @ -72,7 +71,7 @@ export class SegmentService implements ISegmentService { | |||||||
|         this.privateProjectChecker = privateProjectChecker; |         this.privateProjectChecker = privateProjectChecker; | ||||||
|         this.logger = config.getLogger('services/segment-service.ts'); |         this.logger = config.getLogger('services/segment-service.ts'); | ||||||
|         this.flagResolver = config.flagResolver; |         this.flagResolver = config.flagResolver; | ||||||
|         this.resourceLimits = config.resourceLimits; |         this.resourceLimitsService = resourceLimitsService; | ||||||
|         this.config = config; |         this.config = config; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -136,7 +135,8 @@ export class SegmentService implements ISegmentService { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async validateSegmentLimit() { |     async validateSegmentLimit() { | ||||||
|         const limit = this.resourceLimits.segments; |         const { segments: limit } = | ||||||
|  |             await this.resourceLimitsService.getResourceLimits(); | ||||||
| 
 | 
 | ||||||
|         const segmentCount = await this.segmentStore.count(); |         const segmentCount = await this.segmentStore.count(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -31,9 +31,9 @@ import type EventService from '../features/events/event-service.js'; | |||||||
| import { addMinutes, isPast } from 'date-fns'; | import { addMinutes, isPast } from 'date-fns'; | ||||||
| import metricsHelper from '../util/metrics-helper.js'; | import metricsHelper from '../util/metrics-helper.js'; | ||||||
| import { FUNCTION_TIME } from '../metric-events.js'; | import { FUNCTION_TIME } from '../metric-events.js'; | ||||||
| import type { ResourceLimitsSchema } from '../openapi/index.js'; |  | ||||||
| import { throwExceedsLimitError } from '../error/exceeds-limit-error.js'; | import { throwExceedsLimitError } from '../error/exceeds-limit-error.js'; | ||||||
| import type EventEmitter from 'events'; | import type EventEmitter from 'events'; | ||||||
|  | import type { ResourceLimitsService } from '../features/resource-limits/resource-limits-service.js'; | ||||||
| 
 | 
 | ||||||
| const resolveTokenPermissions = (tokenType: string) => { | const resolveTokenPermissions = (tokenType: string) => { | ||||||
|     if (tokenType === ApiTokenType.ADMIN) { |     if (tokenType === ApiTokenType.ADMIN) { | ||||||
| @ -73,7 +73,7 @@ export class ApiTokenService { | |||||||
| 
 | 
 | ||||||
|     private timer: Function; |     private timer: Function; | ||||||
| 
 | 
 | ||||||
|     private resourceLimits: ResourceLimitsSchema; |     private resourceLimitsService: ResourceLimitsService; | ||||||
| 
 | 
 | ||||||
|     private eventBus: EventEmitter; |     private eventBus: EventEmitter; | ||||||
| 
 | 
 | ||||||
| @ -84,20 +84,17 @@ export class ApiTokenService { | |||||||
|         }: Pick<IUnleashStores, 'apiTokenStore' | 'environmentStore'>, |         }: Pick<IUnleashStores, 'apiTokenStore' | 'environmentStore'>, | ||||||
|         config: Pick< |         config: Pick< | ||||||
|             IUnleashConfig, |             IUnleashConfig, | ||||||
|             | 'getLogger' |             'getLogger' | 'authentication' | 'flagResolver' | 'eventBus' | ||||||
|             | 'authentication' |  | ||||||
|             | 'flagResolver' |  | ||||||
|             | 'eventBus' |  | ||||||
|             | 'resourceLimits' |  | ||||||
|         >, |         >, | ||||||
|         eventService: EventService, |         eventService: EventService, | ||||||
|  |         resourceLimitsService: ResourceLimitsService, | ||||||
|     ) { |     ) { | ||||||
|         this.store = apiTokenStore; |         this.store = apiTokenStore; | ||||||
|         this.eventService = eventService; |         this.eventService = eventService; | ||||||
|  |         this.resourceLimitsService = resourceLimitsService; | ||||||
|         this.environmentStore = environmentStore; |         this.environmentStore = environmentStore; | ||||||
|         this.flagResolver = config.flagResolver; |         this.flagResolver = config.flagResolver; | ||||||
|         this.logger = config.getLogger('/services/api-token-service.ts'); |         this.logger = config.getLogger('/services/api-token-service.ts'); | ||||||
|         this.resourceLimits = config.resourceLimits; |  | ||||||
|         if (!this.flagResolver.isEnabled('useMemoizedActiveTokens')) { |         if (!this.flagResolver.isEnabled('useMemoizedActiveTokens')) { | ||||||
|             // This is probably not needed because the scheduler will run it
 |             // This is probably not needed because the scheduler will run it
 | ||||||
|             this.fetchActiveTokens(); |             this.fetchActiveTokens(); | ||||||
| @ -321,7 +318,8 @@ export class ApiTokenService { | |||||||
| 
 | 
 | ||||||
|     private async validateApiTokenLimit() { |     private async validateApiTokenLimit() { | ||||||
|         const currentTokenCount = await this.store.count(); |         const currentTokenCount = await this.store.count(); | ||||||
|         const limit = this.resourceLimits.apiTokens; |         const { apiTokens: limit } = | ||||||
|  |             await this.resourceLimitsService.getResourceLimits(); | ||||||
|         if (currentTokenCount >= limit) { |         if (currentTokenCount >= limit) { | ||||||
|             throwExceedsLimitError(this.eventBus, { |             throwExceedsLimitError(this.eventBus, { | ||||||
|                 resource: 'api token', |                 resource: 'api token', | ||||||
|  | |||||||
| @ -171,6 +171,7 @@ import { UnknownFlagsService } from '../features/metrics/unknown-flags/unknown-f | |||||||
| import type FeatureLinkService from '../features/feature-links/feature-link-service.js'; | import type FeatureLinkService from '../features/feature-links/feature-link-service.js'; | ||||||
| import { createUserService } from '../features/users/createUserService.js'; | import { createUserService } from '../features/users/createUserService.js'; | ||||||
| import { UiConfigService } from '../ui-config/ui-config-service.js'; | import { UiConfigService } from '../ui-config/ui-config-service.js'; | ||||||
|  | import { ResourceLimitsService } from '../features/resource-limits/resource-limits-service.js'; | ||||||
| 
 | 
 | ||||||
| export const createServices = ( | export const createServices = ( | ||||||
|     stores: IUnleashStores, |     stores: IUnleashStores, | ||||||
| @ -205,6 +206,8 @@ export const createServices = ( | |||||||
| 
 | 
 | ||||||
|     const unknownFlagsService = new UnknownFlagsService(stores, config); |     const unknownFlagsService = new UnknownFlagsService(stores, config); | ||||||
| 
 | 
 | ||||||
|  |     const resourceLimitsService = new ResourceLimitsService(config); | ||||||
|  | 
 | ||||||
|     // Initialize custom metrics service
 |     // Initialize custom metrics service
 | ||||||
|     const customMetricsService = new CustomMetricsService(config); |     const customMetricsService = new CustomMetricsService(config); | ||||||
| 
 | 
 | ||||||
| @ -283,6 +286,7 @@ export const createServices = ( | |||||||
|         config, |         config, | ||||||
|         eventService, |         eventService, | ||||||
|         privateProjectChecker, |         privateProjectChecker, | ||||||
|  |         resourceLimitsService, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     const clientInstanceService = new ClientInstanceService( |     const clientInstanceService = new ClientInstanceService( | ||||||
| @ -449,6 +453,7 @@ export const createServices = ( | |||||||
|         frontendApiService, |         frontendApiService, | ||||||
|         maintenanceService, |         maintenanceService, | ||||||
|         sessionService, |         sessionService, | ||||||
|  |         resourceLimitsService, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
| @ -525,6 +530,7 @@ export const createServices = ( | |||||||
|         featureLinkService, |         featureLinkService, | ||||||
|         unknownFlagsService, |         unknownFlagsService, | ||||||
|         uiConfigService, |         uiConfigService, | ||||||
|  |         resourceLimitsService, | ||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -581,6 +587,8 @@ export { | |||||||
|     UniqueConnectionService, |     UniqueConnectionService, | ||||||
|     FeatureLifecycleReadModel, |     FeatureLifecycleReadModel, | ||||||
|     UnknownFlagsService, |     UnknownFlagsService, | ||||||
|  |     UiConfigService, | ||||||
|  |     ResourceLimitsService, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export interface IUnleashServices { | export interface IUnleashServices { | ||||||
| @ -657,4 +665,5 @@ export interface IUnleashServices { | |||||||
|     featureLinkService: FeatureLinkService; |     featureLinkService: FeatureLinkService; | ||||||
|     unknownFlagsService: UnknownFlagsService; |     unknownFlagsService: UnknownFlagsService; | ||||||
|     uiConfigService: UiConfigService; |     uiConfigService: UiConfigService; | ||||||
|  |     resourceLimitsService: ResourceLimitsService; | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ import { | |||||||
|     simpleAuthSettingsKey, |     simpleAuthSettingsKey, | ||||||
| } from '../types/settings/simple-auth-settings.js'; | } from '../types/settings/simple-auth-settings.js'; | ||||||
| import version from '../util/version.js'; | import version from '../util/version.js'; | ||||||
|  | import type { ResourceLimitsService } from '../features/resource-limits/resource-limits-service.js'; | ||||||
| 
 | 
 | ||||||
| export class UiConfigService { | export class UiConfigService { | ||||||
|     private config: IUnleashConfig; |     private config: IUnleashConfig; | ||||||
| @ -33,6 +34,8 @@ export class UiConfigService { | |||||||
| 
 | 
 | ||||||
|     private maintenanceService: MaintenanceService; |     private maintenanceService: MaintenanceService; | ||||||
| 
 | 
 | ||||||
|  |     private resourceLimitsService: ResourceLimitsService; | ||||||
|  | 
 | ||||||
|     private flagResolver: IFlagResolver; |     private flagResolver: IFlagResolver; | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
| @ -44,6 +47,7 @@ export class UiConfigService { | |||||||
|             frontendApiService, |             frontendApiService, | ||||||
|             maintenanceService, |             maintenanceService, | ||||||
|             sessionService, |             sessionService, | ||||||
|  |             resourceLimitsService, | ||||||
|         }: Pick< |         }: Pick< | ||||||
|             IUnleashServices, |             IUnleashServices, | ||||||
|             | 'versionService' |             | 'versionService' | ||||||
| @ -52,6 +56,7 @@ export class UiConfigService { | |||||||
|             | 'frontendApiService' |             | 'frontendApiService' | ||||||
|             | 'maintenanceService' |             | 'maintenanceService' | ||||||
|             | 'sessionService' |             | 'sessionService' | ||||||
|  |             | 'resourceLimitsService' | ||||||
|         >, |         >, | ||||||
|     ) { |     ) { | ||||||
|         this.config = config; |         this.config = config; | ||||||
| @ -62,6 +67,7 @@ export class UiConfigService { | |||||||
|         this.frontendApiService = frontendApiService; |         this.frontendApiService = frontendApiService; | ||||||
|         this.maintenanceService = maintenanceService; |         this.maintenanceService = maintenanceService; | ||||||
|         this.sessionService = sessionService; |         this.sessionService = sessionService; | ||||||
|  |         this.resourceLimitsService = resourceLimitsService; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getMaxSessionsCount(): Promise<number> { |     async getMaxSessionsCount(): Promise<number> { | ||||||
| @ -114,7 +120,8 @@ export class UiConfigService { | |||||||
|             frontendApiOrigins: frontendSettings.frontendApiOrigins, |             frontendApiOrigins: frontendSettings.frontendApiOrigins, | ||||||
|             versionInfo: await this.versionService.getVersionInfo(), |             versionInfo: await this.versionService.getVersionInfo(), | ||||||
|             prometheusAPIAvailable: this.config.prometheusApi !== undefined, |             prometheusAPIAvailable: this.config.prometheusApi !== undefined, | ||||||
|             resourceLimits: this.config.resourceLimits, |             resourceLimits: | ||||||
|  |                 await this.resourceLimitsService.getResourceLimits(), | ||||||
|             disablePasswordAuth, |             disablePasswordAuth, | ||||||
|             maintenanceMode, |             maintenanceMode, | ||||||
|             feedbackUriPath: this.config.feedbackUriPath, |             feedbackUriPath: this.config.feedbackUriPath, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user