Clean up unused utility functions for creating databases and collections (#181)
This commit is contained in:
parent
efff26dbe7
commit
6bc506b81f
|
@ -266,10 +266,6 @@ src/ResourceProvider/ResourceProviderClientFactory.ts
|
||||||
src/RouteHandlers/RouteHandler.ts
|
src/RouteHandlers/RouteHandler.ts
|
||||||
src/RouteHandlers/TabRouteHandler.test.ts
|
src/RouteHandlers/TabRouteHandler.test.ts
|
||||||
src/RouteHandlers/TabRouteHandler.ts
|
src/RouteHandlers/TabRouteHandler.ts
|
||||||
src/Shared/AddCollectionUtility.test.ts
|
|
||||||
src/Shared/AddCollectionUtility.ts
|
|
||||||
src/Shared/AddDatabaseUtility.test.ts
|
|
||||||
src/Shared/AddDatabaseUtility.ts
|
|
||||||
src/Shared/Constants.ts
|
src/Shared/Constants.ts
|
||||||
src/Shared/DefaultExperienceUtility.test.ts
|
src/Shared/DefaultExperienceUtility.test.ts
|
||||||
src/Shared/DefaultExperienceUtility.ts
|
src/Shared/DefaultExperienceUtility.ts
|
||||||
|
@ -418,6 +414,5 @@ cypress/integration/dataexplorer/SQL/addCollection.spec.ts
|
||||||
cypress/integration/dataexplorer/TABLE/addCollection.spec.ts
|
cypress/integration/dataexplorer/TABLE/addCollection.spec.ts
|
||||||
cypress/integration/notebook/newNotebook.spec.ts
|
cypress/integration/notebook/newNotebook.spec.ts
|
||||||
cypress/integration/notebook/resourceTree.spec.ts
|
cypress/integration/notebook/resourceTree.spec.ts
|
||||||
__mocks__/AddDatabaseUtility.ts
|
|
||||||
__mocks__/monaco-editor.ts
|
__mocks__/monaco-editor.ts
|
||||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
|
@ -1,5 +0,0 @@
|
||||||
export class AddDbUtilities {
|
|
||||||
createGremlinDatabase(params: any) {
|
|
||||||
return Promise.resolve(1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,14 +5,7 @@ import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import {
|
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||||
deleteDocument,
|
|
||||||
getEndpoint,
|
|
||||||
queryDocuments,
|
|
||||||
readDocument,
|
|
||||||
updateDocument,
|
|
||||||
_createMongoCollectionWithARM
|
|
||||||
} from "./MongoProxyClient";
|
|
||||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
|
@ -260,58 +253,4 @@ describe("MongoProxyClient", () => {
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("createMongoCollectionWithARM", () => {
|
|
||||||
it("should create a collection with autopilot when autopilot is selected + shared throughput is false", () => {
|
|
||||||
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
|
||||||
const properties = {
|
|
||||||
pk: "state",
|
|
||||||
coll: "abc-collection",
|
|
||||||
cd: true,
|
|
||||||
db: "a1-db",
|
|
||||||
st: false,
|
|
||||||
sid: "a2",
|
|
||||||
rg: "c1",
|
|
||||||
dba: "main",
|
|
||||||
is: false
|
|
||||||
};
|
|
||||||
_createMongoCollectionWithARM("management.azure.com", properties, { "x-ms-cosmos-offer-autopilot-tier": "1" });
|
|
||||||
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
|
|
||||||
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection",
|
|
||||||
"2020-04-01",
|
|
||||||
{
|
|
||||||
properties: {
|
|
||||||
options: { "x-ms-cosmos-offer-autopilot-tier": "1" },
|
|
||||||
resource: { id: "abc-collection" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("should create a collection with provisioned throughput when provisioned throughput is selected + shared throughput is false", () => {
|
|
||||||
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
|
||||||
const properties = {
|
|
||||||
pk: "state",
|
|
||||||
coll: "abc-collection",
|
|
||||||
cd: true,
|
|
||||||
db: "a1-db",
|
|
||||||
st: false,
|
|
||||||
sid: "a2",
|
|
||||||
rg: "c1",
|
|
||||||
dba: "main",
|
|
||||||
is: false,
|
|
||||||
offerThroughput: 400
|
|
||||||
};
|
|
||||||
_createMongoCollectionWithARM("management.azure.com", properties, undefined);
|
|
||||||
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
|
|
||||||
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection",
|
|
||||||
"2020-04-01",
|
|
||||||
{
|
|
||||||
properties: {
|
|
||||||
options: { throughput: "400" },
|
|
||||||
resource: { id: "abc-collection" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||||
import queryString from "querystring";
|
import queryString from "querystring";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import * as DataExplorerConstants from "../Common/Constants";
|
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
|
||||||
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
|
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
@ -330,48 +327,6 @@ export function createMongoCollectionWithProxy(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMongoCollectionWithARM(
|
|
||||||
armEndpoint: string,
|
|
||||||
databaseId: string,
|
|
||||||
analyticalStorageTtl: number,
|
|
||||||
collectionId: string,
|
|
||||||
offerThroughput: number,
|
|
||||||
shardKey: string,
|
|
||||||
createDatabase: boolean,
|
|
||||||
sharedThroughput: boolean,
|
|
||||||
isSharded: boolean,
|
|
||||||
additionalOptions?: DataModels.RpOptions
|
|
||||||
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
|
||||||
const databaseAccount = userContext.databaseAccount;
|
|
||||||
const params: DataModels.MongoParameters = {
|
|
||||||
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
|
||||||
db: databaseId,
|
|
||||||
coll: collectionId,
|
|
||||||
pk: shardKey,
|
|
||||||
offerThroughput,
|
|
||||||
cd: createDatabase,
|
|
||||||
st: sharedThroughput,
|
|
||||||
is: isSharded,
|
|
||||||
rid: "",
|
|
||||||
rtype: "colls",
|
|
||||||
sid: userContext.subscriptionId,
|
|
||||||
rg: userContext.resourceGroup,
|
|
||||||
dba: databaseAccount.name,
|
|
||||||
analyticalStorageTtl
|
|
||||||
};
|
|
||||||
|
|
||||||
if (createDatabase) {
|
|
||||||
return AddDbUtilities.createMongoDatabaseWithARM(
|
|
||||||
armEndpoint,
|
|
||||||
params,
|
|
||||||
sharedThroughput ? additionalOptions : {}
|
|
||||||
).then(() => {
|
|
||||||
return _createMongoCollectionWithARM(armEndpoint, params, sharedThroughput ? {} : additionalOptions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return _createMongoCollectionWithARM(armEndpoint, params, additionalOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string {
|
export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string {
|
||||||
const serverId = window.dataExplorer.serverId();
|
const serverId = window.dataExplorer.serverId();
|
||||||
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
||||||
|
@ -404,46 +359,3 @@ async function errorHandling(response: Response, action: string, params: unknown
|
||||||
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function _createMongoCollectionWithARM(
|
|
||||||
armEndpoint: string,
|
|
||||||
params: DataModels.MongoParameters,
|
|
||||||
rpOptions: DataModels.RpOptions
|
|
||||||
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
|
||||||
const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.coll
|
|
||||||
},
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params.is) {
|
|
||||||
rpPayloadToCreateCollection.properties.resource["shardKey"] = { [params.pk]: "Hash" };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!params.st) {
|
|
||||||
if (rpOptions) {
|
|
||||||
rpPayloadToCreateCollection.properties.options = rpOptions;
|
|
||||||
} else {
|
|
||||||
rpPayloadToCreateCollection.properties.options["throughput"] =
|
|
||||||
params.offerThroughput && params.offerThroughput.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.analyticalStorageTtl) {
|
|
||||||
rpPayloadToCreateCollection.properties.resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new ResourceProviderClient<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
|
|
||||||
getARMCreateCollectionEndpoint(params),
|
|
||||||
DataExplorerConstants.ArmApiVersions.publicVersion,
|
|
||||||
rpPayloadToCreateCollection
|
|
||||||
);
|
|
||||||
} catch (response) {
|
|
||||||
errorHandling(response, "creating collection", undefined);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -943,7 +943,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||||
const defaultThroughput = this.container.collectionCreationDefaults.throughput;
|
const defaultThroughput = this.container.collectionCreationDefaults.throughput;
|
||||||
this.throughputSinglePartition(defaultThroughput.fixed);
|
this.throughputSinglePartition(defaultThroughput.fixed);
|
||||||
this.throughputMultiPartition(
|
this.throughputMultiPartition(
|
||||||
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.throughputDatabase(defaultThroughput.shared);
|
this.throughputDatabase(defaultThroughput.shared);
|
||||||
|
@ -1167,17 +1167,19 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||||
|
|
||||||
private _updateThroughputLimitByCollectionStorage() {
|
private _updateThroughputLimitByCollectionStorage() {
|
||||||
const storage = this.storage();
|
const storage = this.storage();
|
||||||
const minThroughputRU = AddCollectionUtility.Utilities.getMinRUForStorageOption(
|
const minThroughputRU =
|
||||||
this.container.collectionCreationDefaults,
|
storage === SharedConstants.CollectionCreation.storage10Gb
|
||||||
storage
|
? SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
||||||
);
|
: this.container.collectionCreationDefaults.throughput.unlimitedmin;
|
||||||
|
|
||||||
let maxThroughputRU = AddCollectionUtility.Utilities.getMaxRUForStorageOption(
|
let maxThroughputRU;
|
||||||
this.container.collectionCreationDefaults,
|
|
||||||
storage
|
|
||||||
);
|
|
||||||
if (this.isTryCosmosDBSubscription()) {
|
if (this.isTryCosmosDBSubscription()) {
|
||||||
maxThroughputRU = Constants.TryCosmosExperience.maxRU;
|
maxThroughputRU = Constants.TryCosmosExperience.maxRU;
|
||||||
|
} else {
|
||||||
|
maxThroughputRU =
|
||||||
|
storage === SharedConstants.CollectionCreation.storage10Gb
|
||||||
|
? SharedConstants.CollectionCreation.DefaultCollectionRUs10K
|
||||||
|
: this.container.collectionCreationDefaults.throughput.unlimitedmax;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.minThroughputRU(minThroughputRU);
|
this.minThroughputRU(minThroughputRU);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
|
|
||||||
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
@ -8,15 +7,11 @@ import * as PricingUtils from "../../Utils/PricingUtils";
|
||||||
import * as SharedConstants from "../../Shared/Constants";
|
import * as SharedConstants from "../../Shared/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import editable from "../../Common/EditableUtility";
|
import editable from "../../Common/EditableUtility";
|
||||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { AddDbUtilities } from "../../Shared/AddDatabaseUtility";
|
|
||||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export default class AddDatabasePane extends ContextualPaneBase {
|
export default class AddDatabasePane extends ContextualPaneBase {
|
||||||
public defaultExperience: ko.Computed<string>;
|
public defaultExperience: ko.Computed<string>;
|
||||||
|
|
|
@ -494,9 +494,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||||
this.selectedSharedAutoPilotTier(null);
|
this.selectedSharedAutoPilotTier(null);
|
||||||
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
this.throughput(
|
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
|
||||||
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
|
||||||
);
|
|
||||||
this.keyspaceThroughput(throughputDefaults.shared);
|
this.keyspaceThroughput(throughputDefaults.shared);
|
||||||
this.maxThroughputRU(throughputDefaults.unlimitedmax);
|
this.maxThroughputRU(throughputDefaults.unlimitedmax);
|
||||||
this.minThroughputRU(throughputDefaults.unlimitedmin);
|
this.minThroughputRU(throughputDefaults.unlimitedmin);
|
||||||
|
|
|
@ -1,162 +1,64 @@
|
||||||
import * as SharedConstants from "../Shared/Constants";
|
import * as ko from "knockout";
|
||||||
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
|
import { Collection, Database } from "../Contracts/ViewModels";
|
||||||
import { CreateCollectionUtilities, CreateSqlCollectionUtilities, Utilities } from "./AddCollectionUtility";
|
import { getMaxThroughput } from "./AddCollectionUtility";
|
||||||
jest.mock("AddDatabaseUtility");
|
import Explorer from "../Explorer/Explorer";
|
||||||
|
|
||||||
const armEndpoint = "https://management.azure.com";
|
describe("getMaxThroughput", () => {
|
||||||
|
it("default unlimited throughput setting", () => {
|
||||||
describe("Add Collection Utitlity", () => {
|
const defaults = {
|
||||||
describe("createSqlCollection", () => {
|
storage: "100",
|
||||||
it("should invoke createSqlCollectionWithARM if create database is false", () => {
|
throughput: {
|
||||||
const properties = {
|
fixed: 400,
|
||||||
uniqueKeyPolicy: { uniqueKeys: [{ paths: [""] }] },
|
unlimited: 400,
|
||||||
cd: false,
|
unlimitedmax: 1000000,
|
||||||
coll: "abc-collection",
|
unlimitedmin: 400,
|
||||||
db: "a1-db",
|
shared: 400
|
||||||
dba: "main",
|
}
|
||||||
offerThroughput: 50000,
|
|
||||||
pk: "state",
|
|
||||||
sid: "a1",
|
|
||||||
rg: "b1",
|
|
||||||
st: true,
|
|
||||||
defaultTtl: -1,
|
|
||||||
indexingPolicy: SharedConstants.IndexingPolicies.AllPropertiesIndexed,
|
|
||||||
partitionKeyVersion: 2
|
|
||||||
};
|
};
|
||||||
const additionalOptions = {};
|
|
||||||
const createSqlCollectionWithARMSpy = jest.spyOn(CreateSqlCollectionUtilities, "createSqlCollectionWithARM");
|
expect(getMaxThroughput(defaults, {} as Explorer)).toEqual(defaults.throughput.unlimited);
|
||||||
CreateSqlCollectionUtilities.createSqlCollection(
|
|
||||||
armEndpoint,
|
|
||||||
properties.db,
|
|
||||||
properties.defaultTtl,
|
|
||||||
properties.coll,
|
|
||||||
properties.indexingPolicy,
|
|
||||||
properties.offerThroughput,
|
|
||||||
properties.pk,
|
|
||||||
properties.partitionKeyVersion,
|
|
||||||
properties.cd,
|
|
||||||
properties.st,
|
|
||||||
properties.sid,
|
|
||||||
properties.rg,
|
|
||||||
properties.dba,
|
|
||||||
properties.uniqueKeyPolicy,
|
|
||||||
additionalOptions
|
|
||||||
);
|
|
||||||
expect(createSqlCollectionWithARMSpy).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should invoke createSqlDatabase + createSqlCollectionWithARM if create database is true", () => {
|
describe("no unlimited throughput setting", () => {
|
||||||
const properties = {
|
const defaults = {
|
||||||
uniqueKeyPolicy: { uniqueKeys: [{ paths: [""] }] },
|
storage: "100",
|
||||||
cd: true,
|
throughput: {
|
||||||
coll: "abc-collection",
|
fixed: 400,
|
||||||
db: "a1-db",
|
unlimited: {
|
||||||
dba: "main",
|
collectionThreshold: 3,
|
||||||
offerThroughput: 50000,
|
lessThanOrEqualToThreshold: 400,
|
||||||
pk: "state",
|
greatThanThreshold: 500
|
||||||
sid: "a1",
|
},
|
||||||
rg: "b1",
|
unlimitedmax: 1000000,
|
||||||
st: true,
|
unlimitedmin: 400,
|
||||||
analyticalStorageTtl: -1,
|
shared: 400
|
||||||
indexingPolicy: SharedConstants.IndexingPolicies.AllPropertiesIndexed,
|
}
|
||||||
partitionKeyVersion: 2
|
|
||||||
};
|
};
|
||||||
const additionalOptions = {};
|
|
||||||
const createSqlCollectionWithARMSpy = jest.spyOn(CreateSqlCollectionUtilities, "createSqlCollectionWithARM");
|
const mockCollection1 = { id: ko.observable("collection1") } as Collection;
|
||||||
const createSqlDatabaseSpy = jest.spyOn(AddDbUtilities, "createSqlDatabase");
|
const mockCollection2 = { id: ko.observable("collection2") } as Collection;
|
||||||
CreateSqlCollectionUtilities.createSqlCollection(
|
const mockCollection3 = { id: ko.observable("collection3") } as Collection;
|
||||||
armEndpoint,
|
const mockCollection4 = { id: ko.observable("collection4") } as Collection;
|
||||||
properties.db,
|
const mockDatabase = {} as Database;
|
||||||
properties.analyticalStorageTtl,
|
const mockContainer = {
|
||||||
properties.coll,
|
databases: ko.observableArray([mockDatabase])
|
||||||
properties.indexingPolicy,
|
} as Explorer;
|
||||||
properties.offerThroughput,
|
|
||||||
properties.pk,
|
it("less than or equal to collection threshold", () => {
|
||||||
properties.partitionKeyVersion,
|
mockDatabase.collections = ko.observableArray([mockCollection1, mockCollection2]);
|
||||||
properties.cd,
|
expect(getMaxThroughput(defaults, mockContainer)).toEqual(
|
||||||
properties.st,
|
defaults.throughput.unlimited.lessThanOrEqualToThreshold
|
||||||
properties.sid,
|
|
||||||
properties.rg,
|
|
||||||
properties.dba,
|
|
||||||
properties.uniqueKeyPolicy,
|
|
||||||
additionalOptions
|
|
||||||
);
|
);
|
||||||
expect(createSqlCollectionWithARMSpy).toHaveBeenCalled();
|
|
||||||
expect(createSqlDatabaseSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Add Collection Utitlity", () => {
|
it("exceeds collection threshold", () => {
|
||||||
describe("createGremlinGraph", () => {
|
mockDatabase.collections = ko.observableArray([
|
||||||
it("should invoke createGremlinGraphWithARM if create database is false", () => {
|
mockCollection1,
|
||||||
const properties = {
|
mockCollection2,
|
||||||
cd: false,
|
mockCollection3,
|
||||||
coll: "abc-collection",
|
mockCollection4
|
||||||
db: "a1-db",
|
]);
|
||||||
dba: "main",
|
expect(getMaxThroughput(defaults, mockContainer)).toEqual(defaults.throughput.unlimited.greatThanThreshold);
|
||||||
offerThroughput: 50000,
|
|
||||||
pk: "state",
|
|
||||||
sid: "a1",
|
|
||||||
rg: "b1",
|
|
||||||
st: true,
|
|
||||||
indexingPolicy: SharedConstants.IndexingPolicies.AllPropertiesIndexed,
|
|
||||||
partitionKeyVersion: 2
|
|
||||||
};
|
|
||||||
const additionalOptions = {};
|
|
||||||
const createGremlinGraphWithARMSpy = jest.spyOn(CreateCollectionUtilities, "createGremlinGraphWithARM");
|
|
||||||
CreateCollectionUtilities.createGremlinGraph(
|
|
||||||
armEndpoint,
|
|
||||||
properties.db,
|
|
||||||
properties.coll,
|
|
||||||
properties.indexingPolicy,
|
|
||||||
properties.offerThroughput,
|
|
||||||
properties.pk,
|
|
||||||
properties.partitionKeyVersion,
|
|
||||||
properties.cd,
|
|
||||||
properties.st,
|
|
||||||
properties.sid,
|
|
||||||
properties.rg,
|
|
||||||
properties.dba,
|
|
||||||
additionalOptions
|
|
||||||
);
|
|
||||||
expect(createGremlinGraphWithARMSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke createGremlinDatabase + createGremlinGraphWithARM if create database is true", () => {
|
|
||||||
const properties = {
|
|
||||||
cd: true,
|
|
||||||
coll: "abc-collection",
|
|
||||||
db: "a1-db",
|
|
||||||
dba: "main",
|
|
||||||
offerThroughput: 50000,
|
|
||||||
pk: "state",
|
|
||||||
sid: "a1",
|
|
||||||
rg: "b1",
|
|
||||||
st: true,
|
|
||||||
indexingPolicy: SharedConstants.IndexingPolicies.AllPropertiesIndexed,
|
|
||||||
partitionKeyVersion: 2
|
|
||||||
};
|
|
||||||
const additionalOptions = {};
|
|
||||||
const createGremlinGraphWithARMSpy = jest.spyOn(CreateCollectionUtilities, "createGremlinGraphWithARM");
|
|
||||||
const createGremlinDatabaseSpy = jest.spyOn(AddDbUtilities, "createGremlinDatabase");
|
|
||||||
CreateCollectionUtilities.createGremlinGraph(
|
|
||||||
armEndpoint,
|
|
||||||
properties.db,
|
|
||||||
properties.coll,
|
|
||||||
properties.indexingPolicy,
|
|
||||||
properties.offerThroughput,
|
|
||||||
properties.pk,
|
|
||||||
properties.partitionKeyVersion,
|
|
||||||
properties.cd,
|
|
||||||
properties.st,
|
|
||||||
properties.sid,
|
|
||||||
properties.rg,
|
|
||||||
properties.dba,
|
|
||||||
additionalOptions
|
|
||||||
);
|
|
||||||
expect(createGremlinDatabaseSpy).toHaveBeenCalled();
|
|
||||||
expect(createGremlinGraphWithARMSpy).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,303 +1,23 @@
|
||||||
import * as _ from "underscore";
|
import { any } from "underscore";
|
||||||
import * as DataExplorerConstants from "../Common/Constants";
|
import { CollectionCreationDefaults } from "../Contracts/ViewModels";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as SharedConstants from "./Constants";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
|
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
|
||||||
import { sendMessage } from "../Common/MessageHandler";
|
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
|
||||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { userContext } from "../UserContext";
|
|
||||||
|
|
||||||
export class CreateSqlCollectionUtilities {
|
export const getMaxThroughput = (defaults: CollectionCreationDefaults, container: Explorer): number => {
|
||||||
public static createSqlCollection(
|
|
||||||
armEndpoint: string,
|
|
||||||
databaseId: string,
|
|
||||||
analyticalStorageTtl: number,
|
|
||||||
collectionId: string,
|
|
||||||
indexingPolicy: DataModels.IndexingPolicy,
|
|
||||||
offerThroughput: number,
|
|
||||||
partitionKey: string,
|
|
||||||
partitionKeyVersion: number,
|
|
||||||
createDatabase: boolean,
|
|
||||||
useDatabaseSharedOffer: boolean,
|
|
||||||
sid: string,
|
|
||||||
rg: string,
|
|
||||||
dba: string,
|
|
||||||
uniqueKeyPolicy: DataModels.UniqueKeyPolicy,
|
|
||||||
additionalOptions: DataModels.RpOptions
|
|
||||||
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
|
||||||
const params: DataModels.SqlCollectionParameters = {
|
|
||||||
uniqueKeyPolicy,
|
|
||||||
db: databaseId,
|
|
||||||
coll: collectionId,
|
|
||||||
pk: partitionKey,
|
|
||||||
offerThroughput,
|
|
||||||
cd: createDatabase,
|
|
||||||
st: useDatabaseSharedOffer,
|
|
||||||
sid,
|
|
||||||
rg,
|
|
||||||
dba,
|
|
||||||
analyticalStorageTtl,
|
|
||||||
indexingPolicy,
|
|
||||||
partitionKeyVersion
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params.cd) {
|
|
||||||
return AddDbUtilities.createSqlDatabase(armEndpoint, params, additionalOptions).then(() => {
|
|
||||||
return CreateSqlCollectionUtilities.createSqlCollectionWithARM(armEndpoint, params, additionalOptions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return CreateSqlCollectionUtilities.createSqlCollectionWithARM(armEndpoint, params, additionalOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async createSqlCollectionWithARM(
|
|
||||||
armEndpoint: string,
|
|
||||||
params: DataModels.SqlCollectionParameters,
|
|
||||||
rpOptions: DataModels.RpOptions
|
|
||||||
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
|
||||||
const rpPayloadToCreateCollection: DataModels.SqlCollectionCreationRequest = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.coll,
|
|
||||||
partitionKey: {
|
|
||||||
paths: [params.pk],
|
|
||||||
kind: "Hash",
|
|
||||||
version: params.partitionKeyVersion
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params.analyticalStorageTtl) {
|
|
||||||
rpPayloadToCreateCollection.properties.resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.indexingPolicy) {
|
|
||||||
rpPayloadToCreateCollection.properties.resource.indexingPolicy = params.indexingPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!params.st) {
|
|
||||||
if (rpOptions) {
|
|
||||||
rpPayloadToCreateCollection.properties.options = rpOptions;
|
|
||||||
} else {
|
|
||||||
rpPayloadToCreateCollection.properties.options["throughput"] =
|
|
||||||
params.offerThroughput && params.offerThroughput.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.uniqueKeyPolicy) {
|
|
||||||
rpPayloadToCreateCollection.properties.resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let response = await new ResourceProviderClient<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
|
|
||||||
CreateSqlCollectionUtilities.getSqlCollectionUri(params),
|
|
||||||
DataExplorerConstants.ArmApiVersions.publicVersion,
|
|
||||||
rpPayloadToCreateCollection
|
|
||||||
);
|
|
||||||
return response;
|
|
||||||
} catch (response) {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error creating collection: ${JSON.stringify(response)}`
|
|
||||||
);
|
|
||||||
if (response.status === HttpStatusCodes.Forbidden) {
|
|
||||||
sendMessage({ type: MessageTypes.ForbiddenError });
|
|
||||||
}
|
|
||||||
throw new Error(`Error creating collection`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getSqlCollectionUri(params: DataModels.SqlCollectionParameters): string {
|
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${params.dba}/sqlDatabases/${params.db}/containers/${params.coll}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateCollectionUtilities {
|
|
||||||
public static createGremlinGraph(
|
|
||||||
armEndpoint: string,
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
indexingPolicy: DataModels.IndexingPolicy,
|
|
||||||
offerThroughput: number,
|
|
||||||
partitionKey: string,
|
|
||||||
partitionKeyVersion: number,
|
|
||||||
createDatabase: boolean,
|
|
||||||
useDatabaseSharedOffer: boolean,
|
|
||||||
sid: string,
|
|
||||||
rg: string,
|
|
||||||
dba: string,
|
|
||||||
additionalOptions: DataModels.RpOptions
|
|
||||||
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
|
||||||
const params: DataModels.GraphParameters = {
|
|
||||||
db: databaseId,
|
|
||||||
coll: collectionId,
|
|
||||||
pk: partitionKey,
|
|
||||||
offerThroughput,
|
|
||||||
cd: createDatabase,
|
|
||||||
st: useDatabaseSharedOffer,
|
|
||||||
sid,
|
|
||||||
rg,
|
|
||||||
dba,
|
|
||||||
indexingPolicy,
|
|
||||||
partitionKeyVersion
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params.cd) {
|
|
||||||
return AddDbUtilities.createGremlinDatabase(armEndpoint, params, additionalOptions).then(() => {
|
|
||||||
return CreateCollectionUtilities.createGremlinGraphWithARM(armEndpoint, params, additionalOptions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return CreateCollectionUtilities.createGremlinGraphWithARM(armEndpoint, params, additionalOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async createGremlinGraphWithARM(
|
|
||||||
armEndpoint: string,
|
|
||||||
params: DataModels.GraphParameters,
|
|
||||||
rpOptions: DataModels.RpOptions
|
|
||||||
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
|
||||||
const rpPayloadToCreateCollection: DataModels.GraphCreationRequest = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.coll,
|
|
||||||
partitionKey: {
|
|
||||||
paths: [params.pk],
|
|
||||||
kind: "Hash",
|
|
||||||
version: params.partitionKeyVersion
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params.indexingPolicy) {
|
|
||||||
rpPayloadToCreateCollection.properties.resource.indexingPolicy = params.indexingPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!params.st) {
|
|
||||||
if (rpOptions) {
|
|
||||||
rpPayloadToCreateCollection.properties.options = rpOptions;
|
|
||||||
} else {
|
|
||||||
rpPayloadToCreateCollection.properties.options["throughput"] =
|
|
||||||
params.offerThroughput && params.offerThroughput.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let response = await new ResourceProviderClient<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
|
|
||||||
CreateCollectionUtilities.getGremlinGraphUri(params),
|
|
||||||
DataExplorerConstants.ArmApiVersions.publicVersion,
|
|
||||||
rpPayloadToCreateCollection
|
|
||||||
);
|
|
||||||
return response;
|
|
||||||
} catch (response) {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error creating graph: ${JSON.stringify(response)}`
|
|
||||||
);
|
|
||||||
if (response.status === HttpStatusCodes.Forbidden) {
|
|
||||||
sendMessage({ type: MessageTypes.ForbiddenError });
|
|
||||||
}
|
|
||||||
throw new Error(`Error creating graph`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getGremlinGraphUri(params: DataModels.GraphParameters): string {
|
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${params.dba}/gremlinDatabases/${params.db}/graphs/${params.coll}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class Utilities {
|
|
||||||
public static async createAzureTableWithARM(
|
|
||||||
armEndpoint: string,
|
|
||||||
params: DataModels.CreateDatabaseAndCollectionRequest,
|
|
||||||
rpOptions: DataModels.RpOptions
|
|
||||||
): Promise<any> {
|
|
||||||
const rpPayloadToCreateDatabase: DataModels.CreationRequest = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.collectionId
|
|
||||||
},
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!params.databaseLevelThroughput) {
|
|
||||||
if (rpOptions) {
|
|
||||||
rpPayloadToCreateDatabase.properties.options = rpOptions;
|
|
||||||
} else {
|
|
||||||
rpPayloadToCreateDatabase.properties.options["throughput"] =
|
|
||||||
params.offerThroughput && params.offerThroughput.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await new ResourceProviderClient(armEndpoint).putAsync(
|
|
||||||
Utilities._getAzureTableUri(params),
|
|
||||||
DataExplorerConstants.ArmApiVersions.publicVersion,
|
|
||||||
rpPayloadToCreateDatabase
|
|
||||||
);
|
|
||||||
} catch (reason) {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error creating table: ${JSON.stringify(reason)}, Payload: ${params}`
|
|
||||||
);
|
|
||||||
if (reason.status === HttpStatusCodes.Forbidden) {
|
|
||||||
sendMessage({ type: MessageTypes.ForbiddenError });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new Error(`Error creating table`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getMaxRUForStorageOption(
|
|
||||||
defaults: ViewModels.CollectionCreationDefaults,
|
|
||||||
storageOption: string
|
|
||||||
): number {
|
|
||||||
if (storageOption === SharedConstants.CollectionCreation.storage10Gb) {
|
|
||||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs10K;
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaults.throughput.unlimitedmax;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getMinRUForStorageOption(
|
|
||||||
defaults: ViewModels.CollectionCreationDefaults,
|
|
||||||
storageOption: string
|
|
||||||
): number {
|
|
||||||
if (storageOption === SharedConstants.CollectionCreation.storage10Gb) {
|
|
||||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaults.throughput.unlimitedmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getMaxThroughput(defaults: ViewModels.CollectionCreationDefaults, container: Explorer): number {
|
|
||||||
const throughput = defaults.throughput.unlimited;
|
const throughput = defaults.throughput.unlimited;
|
||||||
if (typeof throughput === "number") {
|
if (typeof throughput === "number") {
|
||||||
return throughput;
|
return throughput;
|
||||||
} else {
|
} else {
|
||||||
return this._exceedsThreshold(throughput.collectionThreshold, container)
|
return _exceedsThreshold(throughput.collectionThreshold, container)
|
||||||
? throughput.greatThanThreshold
|
? throughput.greatThanThreshold
|
||||||
: throughput.lessThanOrEqualToThreshold;
|
: throughput.lessThanOrEqualToThreshold;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private static _exceedsThreshold(unlimitedThreshold: number, container: Explorer): boolean {
|
const _exceedsThreshold = (unlimitedThreshold: number, container: Explorer): boolean => {
|
||||||
const databases = (container && container.databases && container.databases()) || [];
|
const databases = (container && container.databases && container.databases()) || [];
|
||||||
return _.any(
|
return any(
|
||||||
databases,
|
databases,
|
||||||
database =>
|
database =>
|
||||||
database && database.collections && database.collections() && database.collections().length > unlimitedThreshold
|
database && database.collections && database.collections() && database.collections().length > unlimitedThreshold
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
private static _getAzureTableUri(params: DataModels.CreateDatabaseAndCollectionRequest): string {
|
|
||||||
return `subscriptions/${userContext.subscriptionId}/resourceGroups/${userContext.resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/tables/${params.collectionId}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
import { AddDbUtilities } from "./AddDatabaseUtility";
|
|
||||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
|
||||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
|
||||||
|
|
||||||
describe("Add Database Utitlity", () => {
|
|
||||||
const armEndpoint = "https://management.azure.com";
|
|
||||||
const properties = {
|
|
||||||
pk: "state",
|
|
||||||
coll: "abc-collection",
|
|
||||||
cd: true,
|
|
||||||
db: "a1-db",
|
|
||||||
offerThroughput: 50000,
|
|
||||||
st: true,
|
|
||||||
sid: "a1",
|
|
||||||
rg: "b1",
|
|
||||||
dba: "main"
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("getRpClient", () => {
|
|
||||||
it("should return an instance of ResourceProviderClient", () => {
|
|
||||||
expect(AddDbUtilities.getRpClient()).not.toBeFalsy();
|
|
||||||
expect(AddDbUtilities.getRpClient()).toBeInstanceOf(ResourceProviderClient);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getGremlinDatabaseUri", () => {
|
|
||||||
it("should return a uri in the correct format", () => {
|
|
||||||
expect(AddDbUtilities.getGremlinDatabaseUri(properties)).toEqual(
|
|
||||||
"subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/gremlinDatabases/a1-db"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("createGremlinDatabase", () => {
|
|
||||||
it("should utilize resource provider client", () => {
|
|
||||||
const resourceProviderClientSpy = spyOn<any>(AddDbUtilities, "getRpClient");
|
|
||||||
AddDbUtilities.createGremlinDatabase(armEndpoint, properties, undefined);
|
|
||||||
expect(resourceProviderClientSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke getGremlinDatabaseUri", () => {
|
|
||||||
const getGremlinDatabaseUriSpy = spyOn<any>(AddDbUtilities, "getGremlinDatabaseUri");
|
|
||||||
AddDbUtilities.createGremlinDatabase(armEndpoint, properties, undefined);
|
|
||||||
expect(getGremlinDatabaseUriSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke a put call via resource provider client to create a database and set throughput if shared throughtput is true", () => {
|
|
||||||
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
|
||||||
AddDbUtilities.createGremlinDatabase(armEndpoint, properties, undefined);
|
|
||||||
expect(
|
|
||||||
resourceProviderClientPutAsyncSpy
|
|
||||||
).toHaveBeenCalledWith(
|
|
||||||
"subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/gremlinDatabases/a1-db",
|
|
||||||
"2020-04-01",
|
|
||||||
{ properties: { options: { throughput: "50000" }, resource: { id: "a1-db" } } }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke a put call via resource provider client to create a database and set autopilot if shared throughtput is true and autopilot settings are passed", () => {
|
|
||||||
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
|
||||||
AddDbUtilities.createGremlinDatabase(armEndpoint, properties, { "x-ms-cosmos-offer-autopilot-tier": "1" });
|
|
||||||
expect(
|
|
||||||
resourceProviderClientPutAsyncSpy
|
|
||||||
).toHaveBeenCalledWith(
|
|
||||||
"subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/gremlinDatabases/a1-db",
|
|
||||||
"2020-04-01",
|
|
||||||
{ properties: { options: { "x-ms-cosmos-offer-autopilot-tier": "1" }, resource: { id: "a1-db" } } }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke a put call via resource provider client to create a database and not set throughput if shared throughtput is false", () => {
|
|
||||||
const properties = {
|
|
||||||
pk: "state",
|
|
||||||
coll: "abc-collection",
|
|
||||||
cd: true,
|
|
||||||
db: "a2-db",
|
|
||||||
st: false,
|
|
||||||
sid: "a2",
|
|
||||||
rg: "c1",
|
|
||||||
dba: "main"
|
|
||||||
};
|
|
||||||
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
|
||||||
AddDbUtilities.createGremlinDatabase(armEndpoint, properties, undefined);
|
|
||||||
expect(
|
|
||||||
resourceProviderClientPutAsyncSpy
|
|
||||||
).toHaveBeenCalledWith(
|
|
||||||
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/main/gremlinDatabases/a2-db",
|
|
||||||
"2020-04-01",
|
|
||||||
{ properties: { options: {}, resource: { id: "a2-db" } } }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("createSqlDatabase", () => {
|
|
||||||
it("should utilize resource provider client", () => {
|
|
||||||
const resourceProviderClientSpy = spyOn<any>(AddDbUtilities, "getRpClient");
|
|
||||||
AddDbUtilities.createSqlDatabase(armEndpoint, properties, undefined);
|
|
||||||
expect(resourceProviderClientSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke getSqlDatabaseUri", () => {
|
|
||||||
const getSqlDatabaseUriSpy = spyOn<any>(AddDbUtilities, "getSqlDatabaseUri");
|
|
||||||
AddDbUtilities.createSqlDatabase(armEndpoint, properties, undefined);
|
|
||||||
expect(getSqlDatabaseUriSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke a put call via resource provider client to create a database and set throughput if shared throughtput is true", () => {
|
|
||||||
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
|
||||||
AddDbUtilities.createSqlDatabase(armEndpoint, properties, undefined);
|
|
||||||
expect(
|
|
||||||
resourceProviderClientPutAsyncSpy
|
|
||||||
).toHaveBeenCalledWith(
|
|
||||||
"subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/sqlDatabases/a1-db",
|
|
||||||
"2020-04-01",
|
|
||||||
{ properties: { options: { throughput: "50000" }, resource: { id: "a1-db" } } }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke a put call via resource provider client to create a database and set autopilot if shared throughtput is true and autopilot settings are passed", () => {
|
|
||||||
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
|
||||||
AddDbUtilities.createSqlDatabase(armEndpoint, properties, { "x-ms-cosmos-offer-autopilot-tier": "1" });
|
|
||||||
expect(
|
|
||||||
resourceProviderClientPutAsyncSpy
|
|
||||||
).toHaveBeenCalledWith(
|
|
||||||
"subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/sqlDatabases/a1-db",
|
|
||||||
"2020-04-01",
|
|
||||||
{ properties: { options: { "x-ms-cosmos-offer-autopilot-tier": "1" }, resource: { id: "a1-db" } } }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke a put call via resource provider client to create a database and not set throughput if shared throughtput is false", () => {
|
|
||||||
const properties = {
|
|
||||||
pk: "state",
|
|
||||||
coll: "abc-collection",
|
|
||||||
cd: true,
|
|
||||||
db: "a2-db",
|
|
||||||
st: false,
|
|
||||||
sid: "a2",
|
|
||||||
rg: "c1",
|
|
||||||
dba: "main"
|
|
||||||
};
|
|
||||||
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
|
||||||
AddDbUtilities.createSqlDatabase(armEndpoint, properties, undefined);
|
|
||||||
expect(
|
|
||||||
resourceProviderClientPutAsyncSpy
|
|
||||||
).toHaveBeenCalledWith(
|
|
||||||
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/main/sqlDatabases/a2-db",
|
|
||||||
"2020-04-01",
|
|
||||||
{ properties: { options: {}, resource: { id: "a2-db" } } }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,187 +0,0 @@
|
||||||
import * as DataExplorerConstants from "../Common/Constants";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import { configContext } from "../ConfigContext";
|
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
|
||||||
import { sendMessage } from "../Common/MessageHandler";
|
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
|
||||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
|
||||||
import { userContext } from "../UserContext";
|
|
||||||
import { createUpdateCassandraKeyspace } from "../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
|
||||||
|
|
||||||
export class AddDbUtilities {
|
|
||||||
// todo - remove any
|
|
||||||
public static async createMongoDatabaseWithARM(
|
|
||||||
armEndpoint: string,
|
|
||||||
params: DataModels.RpParameters,
|
|
||||||
rpOptions: DataModels.RpOptions
|
|
||||||
): Promise<any> {
|
|
||||||
const rpPayloadToCreateDatabase: DataModels.MongoCreationRequest = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.db
|
|
||||||
},
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params.st) {
|
|
||||||
if (rpOptions) {
|
|
||||||
rpPayloadToCreateDatabase.properties.options = rpOptions;
|
|
||||||
} else {
|
|
||||||
rpPayloadToCreateDatabase.properties.options["throughput"] =
|
|
||||||
params.offerThroughput && params.offerThroughput.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await AddDbUtilities.getRpClient<DataModels.CreateDatabaseWithRpResponse>(armEndpoint).putAsync(
|
|
||||||
AddDbUtilities._getMongoDatabaseUri(params),
|
|
||||||
DataExplorerConstants.ArmApiVersions.publicVersion,
|
|
||||||
rpPayloadToCreateDatabase
|
|
||||||
);
|
|
||||||
} catch (reason) {
|
|
||||||
AddDbUtilities._handleCreationError(reason, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo - remove any
|
|
||||||
public static async createCassandraKeyspace(
|
|
||||||
params: DataModels.RpParameters,
|
|
||||||
rpOptions: DataModels.RpOptions
|
|
||||||
): Promise<any> {
|
|
||||||
const rpPayloadToCreateKeyspace: DataModels.CreationRequest = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.db
|
|
||||||
},
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params.st) {
|
|
||||||
if (rpOptions) {
|
|
||||||
rpPayloadToCreateKeyspace.properties.options = rpOptions;
|
|
||||||
} else {
|
|
||||||
rpPayloadToCreateKeyspace.properties.options["throughput"] =
|
|
||||||
params.offerThroughput && params.offerThroughput.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await createUpdateCassandraKeyspace(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount?.name,
|
|
||||||
params.db,
|
|
||||||
rpPayloadToCreateKeyspace
|
|
||||||
);
|
|
||||||
} catch (reason) {
|
|
||||||
AddDbUtilities._handleCreationError(reason, params, "keyspace");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async createSqlDatabase(
|
|
||||||
armEndpoint: string,
|
|
||||||
params: DataModels.RpParameters,
|
|
||||||
rpOptions: DataModels.RpOptions
|
|
||||||
): Promise<any> {
|
|
||||||
const rpPayloadToCreateSqlDatabase: DataModels.CreationRequest = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.db
|
|
||||||
},
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params.st) {
|
|
||||||
if (rpOptions) {
|
|
||||||
rpPayloadToCreateSqlDatabase.properties.options = rpOptions;
|
|
||||||
} else {
|
|
||||||
rpPayloadToCreateSqlDatabase.properties.options["throughput"] =
|
|
||||||
params.offerThroughput && params.offerThroughput.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await AddDbUtilities.getRpClient<DataModels.CreateDatabaseWithRpResponse>(armEndpoint).putAsync(
|
|
||||||
AddDbUtilities.getSqlDatabaseUri(params),
|
|
||||||
DataExplorerConstants.ArmApiVersions.publicVersion,
|
|
||||||
rpPayloadToCreateSqlDatabase
|
|
||||||
);
|
|
||||||
} catch (reason) {
|
|
||||||
AddDbUtilities._handleCreationError(reason, params, "database");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getRpClient<T>(armEndpoint?: string): ResourceProviderClient<T> {
|
|
||||||
return new ResourceProviderClient<T>(armEndpoint || configContext.ARM_ENDPOINT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async createGremlinDatabase(
|
|
||||||
armEndpoint: string,
|
|
||||||
params: DataModels.RpParameters,
|
|
||||||
autoPilotSettings: DataModels.RpOptions
|
|
||||||
): Promise<DataModels.CreateDatabaseWithRpResponse> {
|
|
||||||
const rpPayloadToCreateDatabase: DataModels.CreationRequest = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.db
|
|
||||||
},
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const uri = AddDbUtilities.getGremlinDatabaseUri(params);
|
|
||||||
|
|
||||||
if (params.st) {
|
|
||||||
if (autoPilotSettings) {
|
|
||||||
rpPayloadToCreateDatabase.properties.options = autoPilotSettings;
|
|
||||||
} else {
|
|
||||||
rpPayloadToCreateDatabase.properties.options["throughput"] =
|
|
||||||
params.offerThroughput && params.offerThroughput.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<DataModels.CreateDatabaseWithRpResponse>((resolve, reject) => {
|
|
||||||
AddDbUtilities.getRpClient<DataModels.CreateDatabaseWithRpResponse>(armEndpoint)
|
|
||||||
.putAsync(uri, DataExplorerConstants.ArmApiVersions.publicVersion, rpPayloadToCreateDatabase)
|
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
reason => {
|
|
||||||
AddDbUtilities._handleCreationError(reason, params);
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _handleCreationError(reason: any, params: DataModels.RpParameters, dbType: string = "database") {
|
|
||||||
NotificationConsoleUtils.logConsoleError(`Error creating ${dbType}: ${JSON.stringify(reason)}, Payload: ${params}`);
|
|
||||||
if (reason.status === HttpStatusCodes.Forbidden) {
|
|
||||||
sendMessage({ type: MessageTypes.ForbiddenError });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new Error(`Error creating ${dbType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _getMongoDatabaseUri(params: DataModels.RpParameters): string {
|
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _getCassandraKeyspaceUri(params: DataModels.RpParameters): string {
|
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/cassandraKeyspaces/${params.db}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getGremlinDatabaseUri(params: DataModels.RpParameters): string {
|
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${params.dba}/gremlinDatabases/${params.db}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getSqlDatabaseUri(params: DataModels.RpParameters): string {
|
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${params.dba}/sqlDatabases/${params.db}`;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue