From 04c61a53d14f8b745ab37755743dc55478d36936 Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Thu, 18 Nov 2021 15:20:23 -0500 Subject: [PATCH] test: rstudio alb integration tests (#813) --- .../provision-account/provision-account.js | 2 +- .../rstudio/launch-rstudio-workspace.test.js | 158 ++++++++++++++++++ .../cidr-workspace-service-catalog.test.js | 26 ++- .../config/settings/example.yml | 3 + .../workspace-service-catalog.js | 2 +- main/integration-tests/support/setup.js | 6 +- 6 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 main/integration-tests/__test__/advanced-tests/rstudio/launch-rstudio-workspace.test.js diff --git a/addons/addon-base-raas/packages/base-raas-workflow-steps/lib/steps/provision-account/provision-account.js b/addons/addon-base-raas/packages/base-raas-workflow-steps/lib/steps/provision-account/provision-account.js index a3e2dd20ef..c58f0f8467 100644 --- a/addons/addon-base-raas/packages/base-raas-workflow-steps/lib/steps/provision-account/provision-account.js +++ b/addons/addon-base-raas/packages/base-raas-workflow-steps/lib/steps/provision-account/provision-account.js @@ -378,7 +378,7 @@ class ProvisionAccount extends StepBase { /* response example: data = { CreateAccountStatus: { - AccountId: "333333333333", + AccountId: "333333333333", Id: "car-exampleaccountcreationrequestid", State: "SUCCEEDED" } diff --git a/main/integration-tests/__test__/advanced-tests/rstudio/launch-rstudio-workspace.test.js b/main/integration-tests/__test__/advanced-tests/rstudio/launch-rstudio-workspace.test.js new file mode 100644 index 0000000000..7dc35e5ecc --- /dev/null +++ b/main/integration-tests/__test__/advanced-tests/rstudio/launch-rstudio-workspace.test.js @@ -0,0 +1,158 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { sleep } = require('@aws-ee/base-services/lib/helpers/utils'); +const axios = require('axios').default; +const { runSetup } = require('../../../support/setup'); +const { deleteWorkspaceServiceCatalog } = require('../../../support/complex/delete-workspace-service-catalog'); + +describe('Launch and terminate RStudio instance', () => { + let setup; + let adminSession; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + }); + + afterAll(async () => { + // await setup.cleanup(); + }); + + async function checkAllRstudioWorkspaceIsTerminated() { + const response = await adminSession.resources.workspaceServiceCatalogs.get(); + const workspaces = response.filter(workspace => { + return ( + workspace.envTypeId === setup.defaults.envTypes.rstudio.envTypeId && + !['TERMINATED', 'FAILED'].includes(workspace.status) + ); + }); + if (workspaces.length > 0) { + throw new Error('All RStudio workspaces should be terminated or failed'); + } + } + + // eslint-disable-next-line jest/expect-expect + it('should launch a RStudio instance', async () => { + // Putting checkAllRstudioWorkspaceIsTerminated check here, because putting this check in `beforeAll` will not stop executing the test if the check does fail + // https://github.com/facebook/jest/issues/2713 + await checkAllRstudioWorkspaceIsTerminated(); + + const envId = await launchRStudioWorkspace(); + // const envId = 'f7cb0f78-3fd2-4351-bea0-b6a05096c2c5'; + + // For installations without AppStream enabled, check that workspace CIDR can be changed + if (!setup.defaults.isAppStreamEnabled) { + await checkCIDR(envId); + } + + const rstudioServerUrlResponse = await checkConnectionUrlCanBeCreated(envId); + await checkConnectionUrlNetworkConnectivity(rstudioServerUrlResponse); + await checkWorkspaceCanBeTerminatedCorrectly(envId); + }); + + async function launchRStudioWorkspace() { + const workspaceName = setup.gen.string({ prefix: 'launch-studio-workspace-test' }); + const createWorkspaceBody = { + name: workspaceName, + envTypeId: setup.defaults.envTypes.rstudio.envTypeId, + envTypeConfigId: setup.defaults.envTypes.rstudio.envTypeConfigId, + studyIds: [], + description: 'test', + projectId: setup.defaults.project.id, + }; + if (!setup.defaults.isAppStreamEnabled) { + createWorkspaceBody.cidr = '0.0.0.0/24'; + } + const env = await adminSession.resources.workspaceServiceCatalogs.create(createWorkspaceBody); + await sleep(2000); + await adminSession.resources.workflows + .versions('wf-provision-environment-sc') + .version(1) + .findAndPollWorkflow(env.id, 10000, 90); + return env.id; + } + + async function checkCIDR(envId) { + const cidrs = { + cidr: [ + { + protocol: 'tcp', + fromPort: 22, + toPort: 22, + cidrBlocks: ['0.0.0.0/32'], + }, + { + protocol: 'tcp', + fromPort: 80, + toPort: 80, + cidrBlocks: ['0.0.0.0/32'], + }, + { + protocol: 'tcp', + fromPort: 443, + toPort: 443, + cidrBlocks: ['0.0.0.0/32'], + }, + ], + }; + await expect( + adminSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(envId).cidr(cidrs), + ).resolves.toBeDefined(); + } + + async function checkWorkspaceCanBeTerminatedCorrectly(envId) { + await adminSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(envId).delete(); + await sleep(2000); + await adminSession.resources.workflows + .versions('wf-terminate-environment-sc') + .version(1) + .findAndPollWorkflow(envId, 10000, 35); + + const envState = await adminSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(envId).get(); + + // Check that workspace terminated correctly + expect(envState.status).toEqual('TERMINATED'); + await deleteWorkspaceServiceCatalog({ aws: setup.aws, id: envId }); + } + + async function checkConnectionUrlCanBeCreated(envId) { + const rstudioServerUrlResponse = await adminSession.resources.workspaceServiceCatalogs + .workspaceServiceCatalog(envId) + .connections() + .connection('id-1') + .createUrl(); + expect(rstudioServerUrlResponse.url).toBeDefined(); + if (setup.defaults.isAppStreamEnabled) { + expect(rstudioServerUrlResponse.appstreamDestinationUrl).toBeDefined(); + } + + return rstudioServerUrlResponse; + } + + async function checkConnectionUrlNetworkConnectivity(rstudioServerUrlResponse) { + if (!setup.defaults.isAppStreamEnabled) { + // VERIFY active workspaces are associated with unexpired TLSv1.2 certs + // By default Axios verify that the domain's SSL cert is valid. If we're able to get a response from the domain it means the domain's cert is valid + // Getting a 403 response code is expected because our client's IP address is not whitelisted to access the RStudio server + const url = rstudioServerUrlResponse.url; + await expect(axios.get(url)).rejects.toThrow('Request failed with status code 403'); + } else { + // If AppStream is enabled, we should not be able to access the RStudio url from the internet + const url = rstudioServerUrlResponse.appstreamDestinationUrl; + await expect(axios.get(url)).rejects.toThrow(/getaddrinfo ENOTFOUND .*/); + } + } +}); diff --git a/main/integration-tests/__test__/api-tests/common/workspace-service-catalogs/cidr-workspace-service-catalog.test.js b/main/integration-tests/__test__/api-tests/common/workspace-service-catalogs/cidr-workspace-service-catalog.test.js index e97ac6a372..fa96232ee4 100644 --- a/main/integration-tests/__test__/api-tests/common/workspace-service-catalogs/cidr-workspace-service-catalog.test.js +++ b/main/integration-tests/__test__/api-tests/common/workspace-service-catalogs/cidr-workspace-service-catalog.test.js @@ -41,6 +41,28 @@ describe('Cidr workspace-service-catalog scenarios', () => { }); describe('Cidr workspace-service-catalog', () => { + const cidrs = { + cidr: [ + { + protocol: 'tcp', + fromPort: 22, + toPort: 22, + cidrBlocks: ['0.0.0.0/32'], + }, + { + protocol: 'tcp', + fromPort: 80, + toPort: 80, + cidrBlocks: ['0.0.0.0/32'], + }, + { + protocol: 'tcp', + fromPort: 443, + toPort: 443, + cidrBlocks: ['0.0.0.0/32'], + }, + ], + }; it('should fail if user is inactive', async () => { const adminSession2 = await setup.createAdminSession(); const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); @@ -58,8 +80,6 @@ describe('Cidr workspace-service-catalog scenarios', () => { envTypeConfigId: configurationId, }); - const cidrs = [{ fromPort: 10, toPort: 20, protocol: 'http', cidrBlocks: ['0.0.0.0/32'] }]; - await expect( adminSession2.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).cidr(cidrs), ).rejects.toMatchObject({ @@ -82,8 +102,6 @@ describe('Cidr workspace-service-catalog scenarios', () => { envTypeConfigId: configurationId, }); - const cidrs = [{ fromPort: 10, toPort: 20, protocol: 'http', cidrBlocks: ['0.0.0.0/32'] }]; - await expect( anonymousSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).cidr(cidrs), ).rejects.toMatchObject({ diff --git a/main/integration-tests/config/settings/example.yml b/main/integration-tests/config/settings/example.yml index 9baba7df10..d18d24f7e2 100644 --- a/main/integration-tests/config/settings/example.yml +++ b/main/integration-tests/config/settings/example.yml @@ -61,6 +61,9 @@ emrEnvTypeId: "prod-sampleEMR-pa-sampleEMR" # Provide the id of a configuration for an imported EMR environment emrConfigId: "sampleEMRConfigName" +rstudioServerId: "prod-sampleRStudio-pa-sampleRstudio" +rstudioServerConfigId: "sampleRStudioConfigName" + # Provide the id of the external BYOB Data Source study byobStudy: "sampleByobStudyName" # ------- CONFIG VALUES BELOW ONLY REQUIRED FOR TESTS IN "appstream-egress-enabled" FOLDER ------ diff --git a/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js b/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js index fbc4416dc0..2fa51aa76b 100644 --- a/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js +++ b/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js @@ -55,7 +55,7 @@ class WorkspaceServiceCatalog extends Resource { async cidr(body) { const api = `${this.api}/cidr`; - const response = await this.doCall(async () => this.axiosClient.put(api, body, {})); + const response = await this.doCall(async () => this.axiosClient.post(api, body, {})); await sleep(this.deflakeDelay()); return response; diff --git a/main/integration-tests/support/setup.js b/main/integration-tests/support/setup.js index b8fc4e1cb2..1c431b70c6 100644 --- a/main/integration-tests/support/setup.js +++ b/main/integration-tests/support/setup.js @@ -106,7 +106,7 @@ class Setup { // so it has to stay active throughout the test suite duration. // Therefore the buffer time (in minutes) should be the longest time taken by any single test suite // If the current token has less than the buffer minutes remaining, we create a new one. - const bufferInMinutes = 10; + const bufferInMinutes = 25; const tokenExpired = (expiresAt - Date.now()) / 60 / 1000 < bufferInMinutes; // Only create a new client session if we haven't done that already or if the token has expired @@ -153,6 +153,10 @@ class Setup { envTypeId: this.settings.get('emrEnvTypeId'), envTypeConfigId: this.settings.get('emrConfigId'), }, + rstudio: { + envTypeId: this.settings.get('rstudioServerId'), + envTypeConfigId: this.settings.get('rstudioServerConfigId'), + }, }; const byobStudy = await this.settings.get('byobStudy');