From 66e81adf3b4cef0dc341c44cb1a5eea870302eac Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Wed, 20 Jul 2022 13:44:12 +0200 Subject: [PATCH] chore(samples): add custom machine type samples (#742) --- .../custom-machine-type/createWithHelper.js | 256 ++++++++++++++++++ .../createWithoutHelper.js | 96 +++++++ .../extraMemWithoutHelper.js | 98 +++++++ .../custom-machine-type/helperClass.js | 224 +++++++++++++++ .../custom-machine-type/updateMemory.js | 116 ++++++++ compute/test/customMachineType.test.js | 235 ++++++++++++++++ 6 files changed, 1025 insertions(+) create mode 100644 compute/instances/custom-machine-type/createWithHelper.js create mode 100644 compute/instances/custom-machine-type/createWithoutHelper.js create mode 100644 compute/instances/custom-machine-type/extraMemWithoutHelper.js create mode 100644 compute/instances/custom-machine-type/helperClass.js create mode 100644 compute/instances/custom-machine-type/updateMemory.js create mode 100644 compute/test/customMachineType.test.js diff --git a/compute/instances/custom-machine-type/createWithHelper.js b/compute/instances/custom-machine-type/createWithHelper.js new file mode 100644 index 0000000000..8bd49b6bd9 --- /dev/null +++ b/compute/instances/custom-machine-type/createWithHelper.js @@ -0,0 +1,256 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Creates a new VM instance with a custom machine type. + * + * @param {string} projectId - ID or number of the project you want to use. + * @param {string} zone - Name of the zone you want to use, for example: us-west3-b + * @param {string} instanceName - Name of the new machine. + * @param {string} cpuSeries - Type of CPU you want to use. + * @param {int} coreCount - Number of CPU cores you want to use. + * @param {int} memory - The amount of memory for the VM instance, in megabytes. + */ +function main(projectId, zone, instanceName, cpuSeries, coreCount, memory) { + // [START compute_custom_machine_type_create_with_helper] + /** + * TODO(developer): Uncomment and replace these variables before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const zone = 'europe-central2-b'; + // const instanceName = 'YOUR_INSTANCE_NAME'; + // const cpuSeries = 'N1'; + // const coreCount = 2 + // const memory = 256 + + const compute = require('@google-cloud/compute'); + + function range(from, to, step) { + return [...Array(Math.floor((to - from) / step) + 1)].map( + (_, i) => from + i * step + ); + } + + class CustomMachineType { + constructor(zone, cpuSeries, coreCount, memory) { + this.zone = zone; + this.cpuSeries = cpuSeries; + this.coreCount = coreCount; + this.memory = memory; + + this.N1 = 'custom'; + this.N2 = 'n2-custom'; + this.N2D = 'n2d-custom'; + this.E2 = 'e2-custom'; + this.E2Micro = 'e2-custom-micro'; + this.E2Small = 'e2-custom-small'; + this.E2Medium = 'e2-custom-medium'; + + this.CpuSeriesE2Limit = { + allowedCores: range(2, 33, 2), + minMemPerCore: 512, + maxMemPerCore: 8192, + allowExtraMemory: false, + extraMemoryLimit: 0, + }; + + this.CpuSeriesE2MicroLimit = { + allowedCores: [], + minMemPerCore: 1024, + maxMemPerCore: 2048, + allowExtraMemory: false, + extraMemoryLimit: 0, + }; + + this.CpuSeriesE2SmallLimit = { + allowedCores: [], + minMemPerCore: 2048, + maxMemPerCore: 4096, + allowExtraMemory: false, + extraMemoryLimit: 0, + }; + + this.CpuSeriesE2MediumLimit = { + allowedCores: [], + minMemPerCore: 4096, + maxMemPerCore: 8192, + allowExtraMemory: false, + extraMemoryLimit: 0, + }; + + this.CpuSeriesN2Limit = { + allowedCores: [...range(2, 33, 2), ...range(36, 129, 4)], + minMemPerCore: 512, + maxMemPerCore: 8192, + allowExtraMemory: true, + extraMemoryLimit: 624 << 10, + }; + + this.CpuSeriesN2DLimit = { + allowedCores: [2, 4, 8, 16, 32, 48, 64, 80, 96], + minMemPerCore: 512, + maxMemPerCore: 8192, + allowExtraMemory: true, + extraMemoryLimit: 768 << 10, + }; + + this.CpuSeriesN1Limit = { + allowedCores: [1, range(2, 97, 2)], + minMemPerCore: 922, + maxMemPerCore: 6656, + allowExtraMemory: true, + extraMemoryLimit: 624 << 10, + }; + + this.TYPE_LIMITS = { + [this.N1]: this.CpuSeriesN1Limit, + [this.N2]: this.CpuSeriesN2Limit, + [this.N2D]: this.CpuSeriesN2DLimit, + [this.E2]: this.CpuSeriesE2Limit, + [this.E2Micro]: this.CpuSeriesE2MicroLimit, + [this.E2Small]: this.CpuSeriesE2SmallLimit, + [this.E2Medium]: this.CpuSeriesE2MediumLimit, + }; + + if (![this.E2, this.N1, this.N2, this.N2D].includes(cpuSeries)) { + throw new Error(`Incorrect CPU type: ${this.cpuSeries}`); + } + + this.typeLimit = this.TYPE_LIMITS[this.cpuSeries]; + + // Check whether the requested parameters are allowed. + // Find more information about limitations of custom machine types at: + // https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types + + // Check the number of cores + if ( + this.typeLimit.allowedCores.length > 0 && + !this.typeLimit.allowedCores.includes(coreCount) + ) { + throw new Error( + `Invalid number of cores requested. Allowed number of cores for ${this.cpuSeries} is: ${this.typeLimit.allowedCores}` + ); + } + + // Memory must be a multiple of 256 MB + if (this.memory % 256 !== 0) { + throw new Error('Requested memory must be a multiple of 256 MB'); + } + + // Check if the requested memory isn't too little + if (this.memory < this.coreCount * this.typeLimit.minMemPerCore) { + throw new Error( + `Requested memory is too low. Minimal memory for ${this.cpuSeries} is ${this.typeLimit.minMemPerCore} MB per core` + ); + } + + // Check if the requested memory isn't too much + if ( + this.memory > this.coreCount * this.typeLimit.maxMemPerCore && + !this.typeLimit.allowExtraMemory + ) { + throw new Error( + `Requested memory is too large.. Maximum memory allowed for ${this.cpuSeries} is ${this.typeLimit.maxMemPerCore} MB per core` + ); + } + + if ( + this.memory > this.typeLimit.extraMemoryLimit && + this.typeLimit.allowExtraMemory + ) { + throw new Error( + `Requested memory is too large.. Maximum memory allowed for ${this.cpuSeries} is ${this.typeLimit.extraMemoryLimit} MB` + ); + } + } + + // Returns the custom machine type in form of a string acceptable by Compute Engine API. + getMachineTypeURI() { + if ( + [this.E2Small, this.E2Micro, this.E2Medium].includes(this.cpuSeries) + ) { + return `zones/${this.zone}/machineTypes/${this.cpuSeries}-${this.memory}`; + } + + if (this.memory > this.coreCount * this.typeLimit.maxMemPerCore) { + return `zones/${this.zone}/machineTypes/${this.cpuSeries}-${this.coreCount}-${this.memory}-ext`; + } + + return `zones/${zone}/machineTypes/${this.cpuSeries}-${this.coreCount}-${this.memory}`; + } + } + + async function createInstanceWithCustomMachineTypeWithHelper() { + const instancesClient = new compute.InstancesClient(); + + const machineType = new CustomMachineType( + zone, + cpuSeries, + coreCount, + memory + ).getMachineTypeURI(); + + const [response] = await instancesClient.insert({ + instanceResource: { + name: instanceName, + disks: [ + { + initializeParams: { + diskSizeGb: '64', + sourceImage: + 'projects/debian-cloud/global/images/family/debian-11/', + }, + autoDelete: true, + boot: true, + }, + ], + machineType, + networkInterfaces: [ + { + name: 'global/networks/default', + }, + ], + }, + project: projectId, + zone, + }); + let operation = response.latestResponse; + const operationsClient = new compute.ZoneOperationsClient(); + + // Wait for the create operation to complete. + while (operation.status !== 'DONE') { + [operation] = await operationsClient.wait({ + operation: operation.name, + project: projectId, + zone: operation.zone.split('/').pop(), + }); + } + + console.log('Instance created.'); + } + + createInstanceWithCustomMachineTypeWithHelper(); + // [END compute_custom_machine_type_create_with_helper] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +const args = process.argv.slice(2); +args[4] = parseInt(args[4]); +args[5] = parseInt(args[5]); + +main(...args); diff --git a/compute/instances/custom-machine-type/createWithoutHelper.js b/compute/instances/custom-machine-type/createWithoutHelper.js new file mode 100644 index 0000000000..804b673230 --- /dev/null +++ b/compute/instances/custom-machine-type/createWithoutHelper.js @@ -0,0 +1,96 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Creates new VM instances without using a CustomMachineType class. + * + * @param {string} projectId - ID or number of the project you want to use. + * @param {string} zone - Name of the zone you want to use, for example: us-west3-b + * @param {string} instanceName - Name of the new machine. + * @param {string} cpuSeries - Type of CPU you want to use. + * @param {int} coreCount - Number of CPU cores you want to use. + * @param {int} memory - The amount of memory for the VM instance, in megabytes. + */ +function main(projectId, zone, instanceName, cpuSeries, coreCount, memory) { + // [START compute_custom_machine_type_create_without_helper] + /** + * TODO(developer): Uncomment and replace these variables before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const zone = 'europe-central2-b'; + // const instanceName = 'YOUR_INSTANCE_NAME'; + // const cpuSeries = 'N1'; + // const coreCount = 2 + // const memory = 256 + + const compute = require('@google-cloud/compute'); + + async function createWithoutHelper() { + const instancesClient = new compute.InstancesClient(); + + const machineType = `zones/${zone}/machineTypes/${cpuSeries}-${coreCount}-${memory}`; + + const [response] = await instancesClient.insert({ + instanceResource: { + name: instanceName, + disks: [ + { + initializeParams: { + diskSizeGb: '64', + sourceImage: + 'projects/debian-cloud/global/images/family/debian-11/', + }, + autoDelete: true, + boot: true, + }, + ], + machineType, + networkInterfaces: [ + { + name: 'global/networks/default', + }, + ], + }, + project: projectId, + zone, + }); + let operation = response.latestResponse; + const operationsClient = new compute.ZoneOperationsClient(); + + // Wait for the create operation to complete. + while (operation.status !== 'DONE') { + [operation] = await operationsClient.wait({ + operation: operation.name, + project: projectId, + zone: operation.zone.split('/').pop(), + }); + } + + console.log('Instance created.'); + } + + createWithoutHelper(); + // [END compute_custom_machine_type_create_without_helper] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +const args = process.argv.slice(2); +args[4] = parseInt(args[4]); +args[5] = parseInt(args[5]); + +main(...args); diff --git a/compute/instances/custom-machine-type/extraMemWithoutHelper.js b/compute/instances/custom-machine-type/extraMemWithoutHelper.js new file mode 100644 index 0000000000..2173b36cd6 --- /dev/null +++ b/compute/instances/custom-machine-type/extraMemWithoutHelper.js @@ -0,0 +1,98 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Creates new VM instances with extra memory without using a CustomMachineType class. + * + * @param {string} projectId - ID or number of the project you want to use. + * @param {string} zone - Name of the zone you want to use, for example: us-west3-b + * @param {string} instanceName - Name of the new machine. + * @param {string} cpuSeries - Type of CPU you want to use. + * @param {int} coreCount - Number of CPU cores you want to use. + * @param {int} memory - The amount of memory for the VM instance, in megabytes. + */ +function main(projectId, zone, instanceName, cpuSeries, coreCount, memory) { + // [START compute_custom_machine_type_extra_mem_no_helper] + /** + * TODO(developer): Uncomment and replace these variables before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const zone = 'europe-central2-b'; + // const instanceName = 'YOUR_INSTANCE_NAME'; + // const cpuSeries = 'N1'; + // const coreCount = 2 + // const memory = 256 + + // The coreCount and memory values are not validated anywhere and can be rejected by the API. + + const compute = require('@google-cloud/compute'); + + async function createInstanceWithExtraMemWithoutHelper() { + const instancesClient = new compute.InstancesClient(); + + const machineType = `zones/${zone}/machineTypes/${cpuSeries}-${coreCount}-${memory}-ext`; + + const [response] = await instancesClient.insert({ + instanceResource: { + name: instanceName, + disks: [ + { + initializeParams: { + diskSizeGb: '64', + sourceImage: + 'projects/debian-cloud/global/images/family/debian-11/', + }, + autoDelete: true, + boot: true, + }, + ], + machineType, + networkInterfaces: [ + { + name: 'global/networks/default', + }, + ], + }, + project: projectId, + zone, + }); + let operation = response.latestResponse; + const operationsClient = new compute.ZoneOperationsClient(); + + // Wait for the create operation to complete. + while (operation.status !== 'DONE') { + [operation] = await operationsClient.wait({ + operation: operation.name, + project: projectId, + zone: operation.zone.split('/').pop(), + }); + } + + console.log('Instance created.'); + } + + createInstanceWithExtraMemWithoutHelper(); + // [END compute_custom_machine_type_extra_mem_no_helper] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +const args = process.argv.slice(2); +args[4] = parseInt(args[4]); +args[5] = parseInt(args[5]); + +main(...args); diff --git a/compute/instances/custom-machine-type/helperClass.js b/compute/instances/custom-machine-type/helperClass.js new file mode 100644 index 0000000000..c483215407 --- /dev/null +++ b/compute/instances/custom-machine-type/helperClass.js @@ -0,0 +1,224 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Creates an instance of CustoMachineTypeClass. + * + * @param {string} zone - Name of the zone you want to use, for example: us-west3-b + * @param {string} cpuSeries - Type of CPU you want to use. + * @param {int} coreCount - Number of CPU cores you want to use. + * @param {int} memory - The amount of memory for the VM instance, in megabytes. + */ +function main(zone, cpuSeries, coreCount, memory) { + // [START compute_custom_machine_type_helper_class] + /** + * TODO(developer): Uncomment and replace these variables before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const zone = 'europe-central2-b'; + // const cpuSeries = 'N1'; + // const coreCount = 2 + // const memory = 256 + + function range(from, to, step) { + return [...Array(Math.floor((to - from) / step) + 1)].map( + (_, i) => from + i * step + ); + } + + class CustomMachineType { + constructor(zone, cpuSeries, coreCount, memory) { + this.zone = zone; + this.cpuSeries = cpuSeries; + this.coreCount = coreCount; + this.memory = memory; + + this.N1 = 'custom'; + this.N2 = 'n2-custom'; + this.N2D = 'n2d-custom'; + this.E2 = 'e2-custom'; + this.E2Micro = 'e2-custom-micro'; + this.E2Small = 'e2-custom-small'; + this.E2Medium = 'e2-custom-medium'; + + this.CpuSeriesE2Limit = { + allowedCores: range(2, 33, 2), + minMemPerCore: 512, + maxMemPerCore: 8192, + allowExtraMemory: false, + extraMemoryLimit: 0, + }; + + this.CpuSeriesE2MicroLimit = { + allowedCores: [], + minMemPerCore: 1024, + maxMemPerCore: 2048, + allowExtraMemory: false, + extraMemoryLimit: 0, + }; + + this.CpuSeriesE2SmallLimit = { + allowedCores: [], + minMemPerCore: 2048, + maxMemPerCore: 4096, + allowExtraMemory: false, + extraMemoryLimit: 0, + }; + + this.CpuSeriesE2MediumLimit = { + allowedCores: [], + minMemPerCore: 4096, + maxMemPerCore: 8192, + allowExtraMemory: false, + extraMemoryLimit: 0, + }; + + this.CpuSeriesN2Limit = { + allowedCores: [...range(2, 33, 2), ...range(36, 129, 4)], + minMemPerCore: 512, + maxMemPerCore: 8192, + allowExtraMemory: true, + extraMemoryLimit: 624 << 10, + }; + + this.CpuSeriesN2DLimit = { + allowedCores: [2, 4, 8, 16, 32, 48, 64, 80, 96], + minMemPerCore: 512, + maxMemPerCore: 8192, + allowExtraMemory: true, + extraMemoryLimit: 768 << 10, + }; + + this.CpuSeriesN1Limit = { + allowedCores: [1, range(2, 97, 2)], + minMemPerCore: 922, + maxMemPerCore: 6656, + allowExtraMemory: true, + extraMemoryLimit: 624 << 10, + }; + + this.TYPE_LIMITS = { + [this.N1]: this.CpuSeriesN1Limit, + [this.N2]: this.CpuSeriesN2Limit, + [this.N2D]: this.CpuSeriesN2DLimit, + [this.E2]: this.CpuSeriesE2Limit, + [this.E2Micro]: this.CpuSeriesE2MicroLimit, + [this.E2Small]: this.CpuSeriesE2SmallLimit, + [this.E2Medium]: this.CpuSeriesE2MediumLimit, + }; + + this.typeLimit = this.TYPE_LIMITS[this.cpuSeries]; + } + + validate() { + // Check the number of cores + if ( + this.typeLimit.allowedCores.length > 0 && + !this.typeLimit.allowedCores.includes(this.coreCount) + ) { + throw new Error( + `Invalid number of cores requested. Allowed number of cores for ${this.cpuSeries} is: ${this.typeLimit.allowedCores}` + ); + } + + // Memory must be a multiple of 256 MB + if (this.memory % 256 !== 0) { + throw new Error('Requested memory must be a multiple of 256 MB'); + } + + // Check if the requested memory isn't too little + if (this.memory < this.coreCount * this.typeLimit.minMemPerCore) { + throw new Error( + `Requested memory is too low. Minimal memory for ${this.cpuSeries} is ${this.typeLimit.minMemPerCore} MB per core` + ); + } + + // Check if the requested memory isn't too much + if ( + this.memory > this.coreCount * this.typeLimit.maxMemPerCore && + !this.typeLimit.allowExtraMemory + ) { + throw new Error( + `Requested memory is too large.. Maximum memory allowed for ${this.cpuSeries} is ${this.typeLimit.maxMemPerCore} MB per core` + ); + } + + if ( + this.memory > this.typeLimit.extraMemoryLimit && + this.typeLimit.allowExtraMemory + ) { + throw new Error( + `Requested memory is too large.. Maximum memory allowed for ${this.cpuSeries} is ${this.typeLimit.extraMemoryLimit} MB` + ); + } + } + + // Returns the custom machine type in form of a string acceptable by Compute Engine API. + getMachineTypeURI() { + if ( + [this.E2Small, this.E2Micro, this.E2Medium].includes(this.cpuSeries) + ) { + return `zones/${this.zone}/machineTypes/${this.cpuSeries}-${this.memory}`; + } + + if (this.memory > this.coreCount * this.typeLimit.maxMemPerCore) { + return `zones/${this.zone}/machineTypes/${this.cpuSeries}-${coreCount}-${this.memory}-ext`; + } + + return `zones/${zone}/machineTypes/${this.cpuSeries}-${this.coreCount}-${this.memory}`; + } + + // Returns machine type in a format without the zone. For example, n2-custom-0-10240. + // This format is used to create instance templates. + getMachineType() { + return this.getMachineTypeURI().split('/').pop(); + } + } + + async function createCustomMachineType() { + if ( + [ + CustomMachineType.E2Small, + CustomMachineType.E2Micro, + CustomMachineType.E2Medium, + ].includes(cpuSeries) + ) { + coreCount = 2; + } + + const machineType = new CustomMachineType( + zone, + cpuSeries, + coreCount, + memory + ); + + console.log(`URI: ${machineType.getMachineTypeURI()}`); + console.log(`MachineType: ${machineType.getMachineType()}`); + } + + createCustomMachineType(); + // [END compute_custom_machine_type_helper_class] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +const args = process.argv.slice(2); +args[2] = parseInt(args[2]); +args[3] = parseInt(args[3]); + +main(...args); diff --git a/compute/instances/custom-machine-type/updateMemory.js b/compute/instances/custom-machine-type/updateMemory.js new file mode 100644 index 0000000000..1d680ef549 --- /dev/null +++ b/compute/instances/custom-machine-type/updateMemory.js @@ -0,0 +1,116 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Sends an update instance machine type request to the Compute Engine API and waits for it to complete. + * + * @param {string} projectId - ID or number of the project you want to use. + * @param {string} zone - Name of the zone you want to use, for example: us-west3-b + * @param {string} instanceName - Name of the new machine. + * @param {int} newMemory - The new amount of memory for the VM instance, in megabytes. + */ +function main(projectId, zone, instanceName, newMemory) { + // [START compute_custom_machine_type_update_memory] + /** + * TODO(developer): Uncomment and replace these variables before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const zone = 'europe-central2-b'; + // const instanceName = 'YOUR_INSTANCE_NAME'; + // const newMemory = 256; + + const compute = require('@google-cloud/compute'); + + async function modifyInstanceWithExtendedMemory() { + const instancesClient = new compute.InstancesClient(); + + const [instance] = await instancesClient.get({ + project: projectId, + zone, + instance: instanceName, + }); + + if ( + !['machineTypes/n1-', 'machineTypes/n2-', 'machineTypes/n2d-'].some( + type => instance.machineType.includes(type) + ) + ) { + throw new Error('extra memory is available only for N1, N2 and N2D CPUs'); + } + + // Make sure that the machine is turned off + if (!['TERMINATED', 'STOPPED'].some(status => instance.status === status)) { + const [response] = await instancesClient.stop({ + project: projectId, + zone, + instance: instanceName, + }); + + let operation = response.latestResponse; + const operationsClient = new compute.ZoneOperationsClient(); + + // Wait for the stop operation to complete. + while (operation.status !== 'DONE') { + [operation] = await operationsClient.wait({ + operation: operation.name, + project: projectId, + zone: operation.zone.split('/').pop(), + }); + } + } + + // Modify the machine definition, remember that extended memory + // is available only for N1, N2 and N2D CPUs + + const start = instance.machineType.substring( + 0, + instance.machineType.lastIndexOf('-') + ); + + const [response] = await instancesClient.setMachineType({ + project: projectId, + zone, + instance: instanceName, + instancesSetMachineTypeRequestResource: { + machineType: `${start}-${newMemory}-ext`, + }, + }); + let operation = response.latestResponse; + const operationsClient = new compute.ZoneOperationsClient(); + + // Wait for the update operation to complete. + while (operation.status !== 'DONE') { + [operation] = await operationsClient.wait({ + operation: operation.name, + project: projectId, + zone: operation.zone.split('/').pop(), + }); + } + + console.log('Instance updated.'); + } + + modifyInstanceWithExtendedMemory(); + // [END compute_custom_machine_type_update_memory] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +const args = process.argv.slice(2); +args[3] = parseInt(args[3]); + +main(...args); diff --git a/compute/test/customMachineType.test.js b/compute/test/customMachineType.test.js new file mode 100644 index 0000000000..f9693fc567 --- /dev/null +++ b/compute/test/customMachineType.test.js @@ -0,0 +1,235 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const compute = require('@google-cloud/compute'); + +const {describe, it} = require('mocha'); +const cp = require('child_process'); +const {assert} = require('chai'); + +const {generateTestId, getStaleVMInstances, deleteInstance} = require('./util'); + +const instancesClient = new compute.InstancesClient(); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); + +const getInstance = async (projectId, zone, instanceName) => { + const [instance] = await instancesClient.get({ + project: projectId, + zone, + instance: instanceName, + }); + return instance; +}; + +describe('custom machine type tests', () => { + const instanceName = generateTestId(); + const zone = 'europe-central2-b'; + let projectId; + + describe('CustomMachineType class tests', () => { + it('should create correct CustomMachineType class', async () => { + const testData = [ + { + cpuSeries: 'custom', + memory: 8192, + cpu: 8, + out: 'URI: zones/europe-central2-b/machineTypes/custom-8-8192', + outMt: 'MachineType: custom-8-8192', + }, + { + cpuSeries: 'n2-custom', + memory: 4096, + cpu: 4, + out: 'URI: zones/europe-central2-b/machineTypes/n2-custom-4-4096', + outMt: 'MachineType: n2-custom-4-4096', + }, + { + cpuSeries: 'n2d-custom', + memory: 8192, + cpu: 4, + out: 'URI: zones/europe-central2-b/machineTypes/n2d-custom-4-8192', + outMt: 'MachineType: n2d-custom-4-8192', + }, + { + cpuSeries: 'e2-custom', + memory: 8192, + cpu: 8, + out: 'URI: zones/europe-central2-b/machineTypes/e2-custom-8-8192', + outMt: 'MachineType: e2-custom-8-8192', + }, + { + cpuSeries: 'e2-custom-small', + memory: 4096, + cpu: 0, + out: 'URI: zones/europe-central2-b/machineTypes/e2-custom-small-4096', + outMt: 'MachineType: e2-custom-small-4096', + }, + { + cpuSeries: 'e2-custom-micro', + memory: 2048, + cpu: 0, + out: 'URI: zones/europe-central2-b/machineTypes/e2-custom-micro-2048', + outMt: 'MachineType: e2-custom-micro-2048', + }, + { + cpuSeries: 'n2-custom', + memory: 638720, + cpu: 8, + out: 'URI: zones/europe-central2-b/machineTypes/n2-custom-8-638720-ext', + outMt: 'MachineType: n2-custom-8-638720-ext', + }, + ]; + + for (const test of testData) { + const output = execSync( + `node instances/custom-machine-type/helperClass ${zone} ${test.cpuSeries} ${test.cpu} ${test.memory}` + ); + + assert.include(output, test.out); + assert.include(output, test.outMt); + } + }); + + it('should throw an error if wrong memory provided to CustomMachineType class', async () => { + try { + execSync( + `node instances/custom-machine-type/helperClass ${zone} custom 8194 8` + ); + } catch (err) { + assert.include( + err.stderr.toString(), + 'Requested memory must be a multiple of 256 MB' + ); + } + }); + + it('should throw an error if wrong cpu count provided to CustomMachineType class', async () => { + try { + execSync( + `node instances/custom-machine-type/helperClass ${zone} n2-custom 8194 66` + ); + } catch (err) { + assert.include( + err.stderr.toString(), + 'Invalid number of cores requested' + ); + } + }); + }); + + describe('instances with custom machine type tests', () => { + after(async () => { + const instances = await getStaleVMInstances(); + await Promise.all( + instances.map(instance => + deleteInstance(instance.zone, instance.instanceName) + ) + ); + }); + + afterEach(async () => { + await deleteInstance(zone, instanceName); + }); + + beforeEach(async () => { + projectId = await instancesClient.getProjectId(); + }); + + it('should create instance with custom machine type', async () => { + const customMT = `zones/${zone}/machineTypes/n2-custom-8-10240`; + + const output = execSync( + `node instances/custom-machine-type/createCustomMachineType ${projectId} ${zone} ${instanceName} ${customMT}` + ); + assert.match(output, /Instance created./); + + const instance = await getInstance(projectId, zone, instanceName); + assert.equal( + instance.machineType, + `https://www.googleapis.com/compute/v1/projects/${projectId}/zones/${zone}/machineTypes/n2-custom-8-10240` + ); + }); + + it('should create instance with custom machine type with helper', async () => { + const output = execSync( + `node instances/custom-machine-type/createWithHelper ${projectId} ${zone} ${instanceName} e2-custom 4 8192` + ); + assert.match(output, /Instance created./); + + const instance = await getInstance(projectId, zone, instanceName); + assert.equal( + instance.machineType, + `https://www.googleapis.com/compute/v1/projects/${projectId}/zones/${zone}/machineTypes/e2-custom-4-8192` + ); + }); + + it('should create instance with custom machine type with shared core', async () => { + const output = execSync( + `node instances/custom-machine-type/createSharedWithMemory ${projectId} ${zone} ${instanceName} e2-custom-micro 2048` + ); + assert.match(output, /Instance created./); + + const instance = await getInstance(projectId, zone, instanceName); + assert.equal( + instance.machineType, + `https://www.googleapis.com/compute/v1/projects/${projectId}/zones/${zone}/machineTypes/e2-custom-micro-2048` + ); + }); + + it('should create instance with custom machine type and update memory', async () => { + const customMT = `zones/${zone}/machineTypes/n2-custom-8-10240`; + + const output = execSync( + `node instances/custom-machine-type/createCustomMachineType ${projectId} ${zone} ${instanceName} ${customMT}` + ); + assert.match(output, /Instance created./); + + execSync( + `node instances/custom-machine-type/updateMemory ${projectId} ${zone} ${instanceName} 819200` + ); + + const instance = await getInstance(projectId, zone, instanceName); + assert.include(instance.machineType, '819200-ext'); + }); + + it('should create instance with custom machine type without helper', async () => { + const output = execSync( + `node instances/custom-machine-type/createWithoutHelper ${projectId} ${zone} ${instanceName} e2-custom 4 8192` + ); + assert.match(output, /Instance created./); + + const instance = await getInstance(projectId, zone, instanceName); + assert.equal( + instance.machineType, + `https://www.googleapis.com/compute/v1/projects/${projectId}/zones/${zone}/machineTypes/e2-custom-4-8192` + ); + }); + + it('should create instance with custom machine type with extra mem without helper', async () => { + const output = execSync( + `node instances/custom-machine-type/extraMemWithoutHelper ${projectId} ${zone} ${instanceName} custom 4 24320` + ); + assert.match(output, /Instance created./); + + const instance = await getInstance(projectId, zone, instanceName); + assert.equal( + instance.machineType, + `https://www.googleapis.com/compute/v1/projects/${projectId}/zones/${zone}/machineTypes/custom-4-24320-ext` + ); + }); + }); +});