UNPKG

@azure/event-hubs

Version:
270 lines • 14.4 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { Constants, RequestResponseLink, RetryOperationType, defaultCancellableLock, isSasTokenProvider, retry, translate, } from "@azure/core-amqp"; import { ReceiverEvents, SenderEvents, } from "rhea-promise"; import { logErrorStackTrace, createSimpleLogger, logger, createManagementLogPrefix, } from "./logger.js"; import { throwErrorIfConnectionClosed, throwTypeErrorIfParameterMissing } from "./util/error.js"; import { toSpanOptions, tracingClient } from "./diagnostics/tracing.js"; import { getRetryAttemptTimeoutInMs } from "./util/retries.js"; import { withAuth } from "./withAuth.js"; import { getRandomName } from "./util/utils.js"; /** * @internal * Describes the EventHubs Management Client that talks * to the $management endpoint over AMQP connection. */ export class ManagementClient { /** * Instantiates the management client. * @param context - The connection context. * @param address - The address for the management endpoint. For IotHub it will be * `/messages/events/$management`. */ constructor(context, { address, audience } = {}) { this.managementLock = getRandomName(Constants.managementRequestKey); /** * The reply to Guid for the management client. */ this.replyTo = getRandomName(); this.address = address !== null && address !== void 0 ? address : Constants.management; this.audience = audience !== null && audience !== void 0 ? audience : context.config.getManagementAudience(); this._context = context; const logPrefix = createManagementLogPrefix(this._context.connectionId); this.logger = createSimpleLogger(logger, logPrefix); this.entityPath = context.config.entityPath; } /** * Gets the security token for the management application properties. * @internal */ async getSecurityToken() { if (isSasTokenProvider(this._context.tokenCredential)) { // the security_token has the $management address removed from the end of the audience // expected audience: sb://fully.qualified.namespace/event-hub-name/$management const audienceParts = this.audience.split("/"); // for management links, address should be '$management' if (audienceParts[audienceParts.length - 1] === this.address) { audienceParts.pop(); } const audience = audienceParts.join("/"); return this._context.tokenCredential.getToken(audience); } // aad credentials use the aad scope return this._context.tokenCredential.getToken(Constants.aadEventHubsScope); } /** * Provides the eventhub runtime information. */ async getEventHubProperties(options = {}) { throwErrorIfConnectionClosed(this._context); return tracingClient.withSpan("ManagementClient.getEventHubProperties", options, async (updatedOptions) => { try { const securityToken = await this.getSecurityToken(); const request = { body: Buffer.from(JSON.stringify([])), message_id: getRandomName(), reply_to: this.replyTo, application_properties: { operation: Constants.readOperation, name: this.entityPath, type: `${Constants.vendorString}:${Constants.eventHub}`, security_token: securityToken === null || securityToken === void 0 ? void 0 : securityToken.token, }, }; const info = await this._makeManagementRequest(request, Object.assign(Object.assign({}, updatedOptions), { requestName: "getHubRuntimeInformation" })); const runtimeInfo = { name: info.name, createdOn: new Date(info.created_at), partitionIds: info.partition_ids, }; logger.verbose("the hub runtime info is: %O", runtimeInfo); return runtimeInfo; } catch (error) { logger.warning(`an error occurred while getting the hub runtime information: ${error === null || error === void 0 ? void 0 : error.name}: ${error === null || error === void 0 ? void 0 : error.message}`); logErrorStackTrace(error); throw error; } }, toSpanOptions(this._context.config)); } /** * Provides information about the specified partition. * @param partitionId - Partition ID for which partition information is required. */ async getPartitionProperties(partitionId, options = {}) { throwErrorIfConnectionClosed(this._context); throwTypeErrorIfParameterMissing(this._context.connectionId, "getPartitionProperties", "partitionId", partitionId); partitionId = String(partitionId); return tracingClient.withSpan("ManagementClient.getPartitionProperties", options, async (updatedOptions) => { try { const securityToken = await this.getSecurityToken(); const request = { body: Buffer.from(JSON.stringify([])), message_id: getRandomName(), reply_to: this.replyTo, application_properties: { operation: Constants.readOperation, name: this.entityPath, type: `${Constants.vendorString}:${Constants.partition}`, partition: `${partitionId}`, security_token: securityToken === null || securityToken === void 0 ? void 0 : securityToken.token, }, }; const info = await this._makeManagementRequest(request, Object.assign(Object.assign({}, updatedOptions), { requestName: "getPartitionInformation" })); const partitionInfo = { beginningSequenceNumber: info.begin_sequence_number, eventHubName: info.name, lastEnqueuedOffset: info.last_enqueued_offset, lastEnqueuedOnUtc: new Date(info.last_enqueued_time_utc), lastEnqueuedSequenceNumber: info.last_enqueued_sequence_number, partitionId: info.partition, isEmpty: info.is_partition_empty, }; logger.verbose("the partition info is: %O.", partitionInfo); return partitionInfo; } catch (error) { logger.warning(`an error occurred while getting the partition information: ${error === null || error === void 0 ? void 0 : error.name}: ${error === null || error === void 0 ? void 0 : error.message}`); logErrorStackTrace(error); throw error; } }, toSpanOptions(this._context.config)); } /** * Closes the AMQP management session to the Event Hub for this client, * returning a promise that will be resolved when disconnection is completed. */ async close() { var _a; try { // Always stop the auth loop when closing the management link. (_a = this.authLoop) === null || _a === void 0 ? void 0 : _a.stop(); if (this._isMgmtRequestResponseLinkOpen()) { const mgmtLink = this._mgmtReqResLink; this._mgmtReqResLink = undefined; await mgmtLink.close(); logger.info("successfully closed the management session."); } } catch (err) { const msg = `an error occurred while closing the management session: ${err === null || err === void 0 ? void 0 : err.name}: ${err === null || err === void 0 ? void 0 : err.message}`; logger.warning(msg); logErrorStackTrace(err); throw new Error(msg); } } async _init({ abortSignal, timeoutInMs, }) { const createLink = async () => { const rxopt = { source: { address: this.address }, name: this.replyTo, target: { address: this.replyTo }, onSessionError: (context) => { const ehError = translate(context.session.error); logger.verbose("an error occurred on the session for request/response links for " + "$management: %O", ehError); }, }; const sropt = { target: { address: this.address }, }; logger.verbose("creating sender/receiver links with " + "srOpts: %o, receiverOpts: %O.", sropt, rxopt); this._mgmtReqResLink = await RequestResponseLink.create(this._context.connection, sropt, rxopt, { abortSignal }); this._mgmtReqResLink.sender.on(SenderEvents.senderError, (context) => { const ehError = translate(context.sender.error); logger.verbose("an error occurred on the $management sender link.. %O", ehError); }); this._mgmtReqResLink.receiver.on(ReceiverEvents.receiverError, (context) => { const ehError = translate(context.receiver.error); logger.verbose("an error occurred on the $management receiver link.. %O", ehError); }); logger.verbose("created sender '%s' and receiver '%s' links", this._mgmtReqResLink.sender.name, this._mgmtReqResLink.receiver.name); }; try { if (!this._isMgmtRequestResponseLinkOpen()) { // Wait for the connectionContext to be ready to open the link. await this._context.readyToOpenLink(); this.authLoop = await withAuth(createLink, this._context, this.audience, timeoutInMs, this.logger, { abortSignal }); } } catch (err) { const translatedError = translate(err); logger.warning(`an error occurred while establishing the links: ${translatedError === null || translatedError === void 0 ? void 0 : translatedError.name}: ${translatedError === null || translatedError === void 0 ? void 0 : translatedError.message}`); logErrorStackTrace(translatedError); throw translatedError; } } /** * Helper method to make the management request * @param request - The AMQP message to send * @param options - The options to use when sending a request over a $management link */ async _makeManagementRequest(request, options = {}) { const retryOptions = options.retryOptions || {}; try { const abortSignal = options && options.abortSignal; const sendOperationPromise = async () => { let count = 0; const retryTimeoutInMs = getRetryAttemptTimeoutInMs(options.retryOptions); let timeTakenByInit = 0; if (!this._isMgmtRequestResponseLinkOpen()) { logger.verbose("acquiring lock to get the management req res link."); const initOperationStartTime = Date.now(); try { await defaultCancellableLock.acquire(this.managementLock, () => { const acquireLockEndTime = Date.now(); const timeoutInMs = retryTimeoutInMs - (acquireLockEndTime - initOperationStartTime); return this._init({ abortSignal, timeoutInMs }); }, { abortSignal, timeoutInMs: retryTimeoutInMs }); } catch (err) { const translatedError = translate(err); logger.warning("an error occurred while creating the link: %s", `${translatedError === null || translatedError === void 0 ? void 0 : translatedError.name}: ${translatedError === null || translatedError === void 0 ? void 0 : translatedError.message}`); logErrorStackTrace(translatedError); throw translatedError; } timeTakenByInit = Date.now() - initOperationStartTime; } const remainingOperationTimeoutInMs = retryTimeoutInMs - timeTakenByInit; const sendRequestOptions = { abortSignal: options.abortSignal, requestName: options.requestName, timeoutInMs: remainingOperationTimeoutInMs, }; count++; if (count !== 1) { // Generate a new message_id every time after the first attempt request.message_id = getRandomName(); } else if (!request.message_id) { // Set the message_id in the first attempt only if it is not set request.message_id = getRandomName(); } return this._mgmtReqResLink.sendRequest(request, sendRequestOptions); }; const config = Object.defineProperties({ operation: sendOperationPromise, operationType: RetryOperationType.management, abortSignal: abortSignal, retryOptions: retryOptions, }, { connectionId: { enumerable: true, get: () => { return this._context.connectionId; }, }, }); return (await retry(config)).body; } catch (err) { const translatedError = translate(err); logger.warning("an error occurred during send on management request-response link with address: %s", `${translatedError === null || translatedError === void 0 ? void 0 : translatedError.name}: ${translatedError === null || translatedError === void 0 ? void 0 : translatedError.message}`); logErrorStackTrace(translatedError); throw translatedError; } } _isMgmtRequestResponseLinkOpen() { return this._mgmtReqResLink && this._mgmtReqResLink.isOpen(); } } //# sourceMappingURL=managementClient.js.map