1 |
|
2 |
|
3 | import { OperationType, ResourceType, isReadRequest } from "./common";
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | export class GlobalEndpointManager {
|
9 | |
10 |
|
11 |
|
12 | constructor(options, readDatabaseAccount) {
|
13 | this.readDatabaseAccount = readDatabaseAccount;
|
14 | this.writeableLocations = [];
|
15 | this.readableLocations = [];
|
16 | this.options = options;
|
17 | this.defaultEndpoint = options.endpoint;
|
18 | this.enableEndpointDiscovery = options.connectionPolicy.enableEndpointDiscovery;
|
19 | this.isRefreshing = false;
|
20 | this.preferredLocations = this.options.connectionPolicy.preferredLocations;
|
21 | }
|
22 | |
23 |
|
24 |
|
25 | async getReadEndpoint() {
|
26 | return this.resolveServiceEndpoint(ResourceType.item, OperationType.Read);
|
27 | }
|
28 | |
29 |
|
30 |
|
31 | async getWriteEndpoint() {
|
32 | return this.resolveServiceEndpoint(ResourceType.item, OperationType.Replace);
|
33 | }
|
34 | async getReadEndpoints() {
|
35 | return this.readableLocations.map((loc) => loc.databaseAccountEndpoint);
|
36 | }
|
37 | async getWriteEndpoints() {
|
38 | return this.writeableLocations.map((loc) => loc.databaseAccountEndpoint);
|
39 | }
|
40 | async markCurrentLocationUnavailableForRead(endpoint) {
|
41 | await this.refreshEndpointList();
|
42 | const location = this.readableLocations.find((loc) => loc.databaseAccountEndpoint === endpoint);
|
43 | if (location) {
|
44 | location.unavailable = true;
|
45 | }
|
46 | }
|
47 | async markCurrentLocationUnavailableForWrite(endpoint) {
|
48 | await this.refreshEndpointList();
|
49 | const location = this.writeableLocations.find((loc) => loc.databaseAccountEndpoint === endpoint);
|
50 | if (location) {
|
51 | location.unavailable = true;
|
52 | }
|
53 | }
|
54 | canUseMultipleWriteLocations(resourceType, operationType) {
|
55 | let canUse = this.options.connectionPolicy.useMultipleWriteLocations;
|
56 | if (resourceType) {
|
57 | canUse =
|
58 | canUse &&
|
59 | (resourceType === ResourceType.item ||
|
60 | (resourceType === ResourceType.sproc && operationType === OperationType.Execute));
|
61 | }
|
62 | return canUse;
|
63 | }
|
64 | async resolveServiceEndpoint(resourceType, operationType) {
|
65 |
|
66 | if (!this.options.connectionPolicy.enableEndpointDiscovery) {
|
67 | return this.defaultEndpoint;
|
68 | }
|
69 |
|
70 | if (resourceType === ResourceType.none) {
|
71 | return this.defaultEndpoint;
|
72 | }
|
73 | if (this.readableLocations.length === 0 || this.writeableLocations.length === 0) {
|
74 | const { resource: databaseAccount } = await this.readDatabaseAccount({
|
75 | urlConnection: this.defaultEndpoint,
|
76 | });
|
77 | this.writeableLocations = databaseAccount.writableLocations;
|
78 | this.readableLocations = databaseAccount.readableLocations;
|
79 | }
|
80 | const locations = isReadRequest(operationType)
|
81 | ? this.readableLocations
|
82 | : this.writeableLocations;
|
83 | let location;
|
84 |
|
85 | if (this.preferredLocations && this.preferredLocations.length > 0) {
|
86 | for (const preferredLocation of this.preferredLocations) {
|
87 | location = locations.find((loc) => loc.unavailable !== true &&
|
88 | normalizeEndpoint(loc.name) === normalizeEndpoint(preferredLocation));
|
89 | if (location) {
|
90 | break;
|
91 | }
|
92 | }
|
93 | }
|
94 |
|
95 | if (!location) {
|
96 | location = locations.find((loc) => {
|
97 | return loc.unavailable !== true;
|
98 | });
|
99 | }
|
100 | return location ? location.databaseAccountEndpoint : this.defaultEndpoint;
|
101 | }
|
102 | |
103 |
|
104 |
|
105 |
|
106 |
|
107 | async refreshEndpointList() {
|
108 | if (!this.isRefreshing && this.enableEndpointDiscovery) {
|
109 | this.isRefreshing = true;
|
110 | const databaseAccount = await this.getDatabaseAccountFromAnyEndpoint();
|
111 | if (databaseAccount) {
|
112 | this.refreshEndpoints(databaseAccount);
|
113 | }
|
114 | this.isRefreshing = false;
|
115 | }
|
116 | }
|
117 | refreshEndpoints(databaseAccount) {
|
118 | for (const location of databaseAccount.writableLocations) {
|
119 | const existingLocation = this.writeableLocations.find((loc) => loc.name === location.name);
|
120 | if (!existingLocation) {
|
121 | this.writeableLocations.push(location);
|
122 | }
|
123 | }
|
124 | for (const location of databaseAccount.writableLocations) {
|
125 | const existingLocation = this.readableLocations.find((loc) => loc.name === location.name);
|
126 | if (!existingLocation) {
|
127 | this.readableLocations.push(location);
|
128 | }
|
129 | }
|
130 | }
|
131 | |
132 |
|
133 |
|
134 |
|
135 |
|
136 | async getDatabaseAccountFromAnyEndpoint() {
|
137 | try {
|
138 | const options = { urlConnection: this.defaultEndpoint };
|
139 | const { resource: databaseAccount } = await this.readDatabaseAccount(options);
|
140 | return databaseAccount;
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 | }
|
148 | catch (err) {
|
149 |
|
150 | }
|
151 | if (this.preferredLocations) {
|
152 | for (const location of this.preferredLocations) {
|
153 | try {
|
154 | const locationalEndpoint = GlobalEndpointManager.getLocationalEndpoint(this.defaultEndpoint, location);
|
155 | const options = { urlConnection: locationalEndpoint };
|
156 | const { resource: databaseAccount } = await this.readDatabaseAccount(options);
|
157 | if (databaseAccount) {
|
158 | return databaseAccount;
|
159 | }
|
160 | }
|
161 | catch (err) {
|
162 |
|
163 | }
|
164 | }
|
165 | }
|
166 | }
|
167 | |
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 | static getLocationalEndpoint(defaultEndpoint, locationName) {
|
174 |
|
175 |
|
176 |
|
177 | const endpointUrl = new URL(defaultEndpoint);
|
178 |
|
179 | if (endpointUrl.hostname) {
|
180 | const hostnameParts = endpointUrl.hostname.toString().toLowerCase().split(".");
|
181 | if (hostnameParts) {
|
182 |
|
183 | const globalDatabaseAccountName = hostnameParts[0];
|
184 |
|
185 | const locationalDatabaseAccountName = globalDatabaseAccountName + "-" + locationName.replace(" ", "");
|
186 |
|
187 |
|
188 | const locationalEndpoint = defaultEndpoint
|
189 | .toLowerCase()
|
190 | .replace(globalDatabaseAccountName, locationalDatabaseAccountName);
|
191 | return locationalEndpoint;
|
192 | }
|
193 | }
|
194 | return null;
|
195 | }
|
196 | }
|
197 | function normalizeEndpoint(endpoint) {
|
198 | return endpoint.split(" ").join("").toLowerCase();
|
199 | }
|
200 |
|
\ | No newline at end of file |