@azure/event-hubs
Version:
Azure Event Hubs SDK for JS.
270 lines • 14.4 kB
JavaScript
// 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