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