diff --git a/news/1 Enhancements/7014.md b/news/1 Enhancements/7014.md new file mode 100644 index 000000000000..e0b01c98da37 --- /dev/null +++ b/news/1 Enhancements/7014.md @@ -0,0 +1 @@ +Add ability to reconnect to existing Jupyter kernel on remote jupyter hosts - @kdkavanagh diff --git a/package.json b/package.json index c022da8be5d3..526f8a0ac77a 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,8 @@ "onCommand:python.datascience.showhistorypane", "onCommand:python.datascience.importnotebook", "onCommand:python.datascience.selectjupyteruri", + "onCommand:python.dataScience.jupyterserverkernelid", + "onCommand:python.dataScience.jupyterserverallowkernelshutdown", "onCommand:python.datascience.exportfileasnotebook", "onCommand:python.datascience.exportfileandoutputasnotebook", "onCommand:python.enableSourceMapSupport" @@ -1365,6 +1367,18 @@ "description": "Select the Jupyter server URI to connect to. Select 'local' to launch a new Juypter server on the local machine.", "scope": "resource" }, + "python.dataScience.jupyterServerKernelId": { + "type": "string", + "default": "", + "description": "Select the Jupyter server Kernel UUID to connect to. Leave blank to start a new kernel", + "scope": "resource" + }, + "python.dataScience.jupyterServerAllowKernelShutdown": { + "type": "boolean", + "default": true, + "description": "Shutdown the Jupyter kernel when finished", + "scope": "resource" + }, "python.dataScience.notebookFileRoot": { "type": "string", "default": "${workspaceFolder}", diff --git a/package.nls.json b/package.nls.json index 1d794158e855..a92fd9f2b97a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -174,6 +174,10 @@ "DataScience.jupyterSelectURISpecifyURI": "Type in the URI to connect to a running Jupyter server", "DataScience.jupyterSelectURIPrompt": "Enter the URI of a Jupyter server", "DataScience.jupyterSelectURIInvalidURI": "Invalid URI specified", + "DataScience.jupyterServerReconnectKernelLocal": "Select Jupyer Kernel", + "DataScience.jupyterServerReconnectKernelStartNewLocal": "Start new kernel on Jupyter server", + "DataScience.jupyterServerKernelAutoShutdownLocal": "Automatically shutdown Kernel when closed", + "DataScience.jupyterServerKernelLeaveRunningLocal": "Leave Kernel running when closed", "DataScience.jupyterSelectPasswordPrompt": "Enter your notebook password", "DataScience.jupyterNotebookFailure": "Jupyter notebook failed to launch. \r\n{0}", "DataScience.jupyterNotebookConnectFailed": "Failed to connect to Jupyter notebook. \r\n{0}\r\n{1}", diff --git a/src/client/common/types.ts b/src/client/common/types.ts index bf2354d29f9f..fa251867c7d0 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -303,6 +303,8 @@ export interface IDataScienceSettings { jupyterInterruptTimeout: number; jupyterLaunchTimeout: number; jupyterLaunchRetries: number; + jupyterServerAllowKernelShutdown: boolean; + jupyterServerKernelId: string; jupyterServerURI: string; notebookFileRoot: string; changeDirOnImportExport: boolean; diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index cf28e8d0244f..8d6f8958d03d 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -176,6 +176,10 @@ export namespace DataScience { export const jupyterSelectURISpecifyURI = localize('DataScience.jupyterSelectURISpecifyURI', 'Type in the URI for the Jupyter server'); export const jupyterSelectURIPrompt = localize('DataScience.jupyterSelectURIPrompt', 'Enter the URI of a Jupyter server'); export const jupyterSelectURIInvalidURI = localize('DataScience.jupyterSelectURIInvalidURI', 'Invalid URI specified'); + export const jupyterServerReconnectKernelLocal = localize('DataScience.jupyterServerReconnectKernelLocal', 'Select Jupyer Kernel'); + export const jupyterServerReconnectKernelStartNewLocal = localize('DataScience.jupyterServerReconnectKernelStartNewLocal', 'Start new kernel on Jupyter server'); + export const jupyterServerKernelAutoShutdownLocal = localize('DataScience.jupyterServerKernelAutoShutdownLocal', 'Automatically shutdown Kernel when closed'); + export const jupyterServerKernelLeaveRunningLocal = localize('DataScience.jupyterServerKernelLeaveRunningLocal', 'Leave Kernel running when closed'); export const jupyterSelectPasswordPrompt = localize('DataScience.jupyterSelectPasswordPrompt', 'Enter your notebook password'); export const jupyterNotebookFailure = localize('DataScience.jupyterNotebookFailure', 'Jupyter notebook failed to launch. \r\n{0}'); export const jupyterNotebookConnectFailed = localize('DataScience.jupyterNotebookConnectFailed', 'Failed to connect to Jupyter notebook. \r\n{0}\r\n{1}'); diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index 216f37fea021..c9bc41936473 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -112,6 +112,8 @@ export enum Telemetry { ExpandAll = 'DATASCIENCE.EXPAND_ALL', CollapseAll = 'DATASCIENCE.COLLAPSE_ALL', SelectJupyterURI = 'DATASCIENCE.SELECT_JUPYTER_URI', + JupyterKernelSpecified = 'DATASCIENCE.JUPYTER_KERNEL_SPECIFIED', + JupyterKernelAutoShutdown = 'DATASCIENCE.JUPYTER_AUTO_SHUTDOWN', SetJupyterURIToLocal = 'DATASCIENCE.SET_JUPYTER_URI_LOCAL', SetJupyterURIToUserSpecified = 'DATASCIENCE.SET_JUPYTER_URI_USER_SPECIFIED', Interrupt = 'DATASCIENCE.INTERRUPT', diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index 55e5579ad43a..feb42371b513 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -3,6 +3,7 @@ 'use strict'; import '../common/extensions'; +import { Kernel } from '@jupyterlab/services'; import { JSONObject } from '@phosphor/coreutils'; import { inject, injectable } from 'inversify'; import { URL } from 'url'; @@ -11,7 +12,7 @@ import * as vscode from 'vscode'; import { IApplicationShell, ICommandManager, IDebugService, IDocumentManager, IWorkspaceService } from '../common/application/types'; import { PYTHON_ALLFILES, PYTHON_LANGUAGE } from '../common/constants'; import { ContextKey } from '../common/contextKey'; -import { traceError } from '../common/logger'; +import { traceError, traceInfo } from '../common/logger'; import { BANNER_NAME_DS_SURVEY, IConfigurationService, @@ -26,7 +27,12 @@ import { IServiceContainer } from '../ioc/types'; import { captureTelemetry, sendTelemetryEvent } from '../telemetry'; import { hasCells } from './cellFactory'; import { Commands, EditorContexts, Settings, Telemetry } from './constants'; -import { ICodeWatcher, IDataScience, IDataScienceCodeLensProvider, IDataScienceCommandListener } from './types'; +import { JupyterExecutionBase } from './jupyter/jupyterExecution'; +import { ICodeWatcher, IDataScience, IDataScienceCodeLensProvider, IDataScienceCommandListener, IJupyterSessionManager } from './types'; + +interface IKernelQuickPickItem extends vscode.QuickPickItem { + kernelId: string; +} @injectable() export class DataScience implements IDataScience { @@ -44,7 +50,8 @@ export class DataScience implements IDataScience { @inject(IDocumentManager) private documentManager: IDocumentManager, @inject(IApplicationShell) private appShell: IApplicationShell, @inject(IDebugService) private debugService: IDebugService, - @inject(IWorkspaceService) private workspace: IWorkspaceService + @inject(IWorkspaceService) private workspace: IWorkspaceService, + @inject(IJupyterSessionManager) private sessionManager: IJupyterSessionManager ) { this.commandListeners = this.serviceContainer.getAll(IDataScienceCommandListener); this.dataScienceSurveyBanner = this.serviceContainer.get(IPythonExtensionBanner, BANNER_NAME_DS_SURVEY); @@ -289,6 +296,61 @@ export class DataScience implements IDataScience { if (userURI) { await this.configuration.updateSetting('dataScience.jupyterServerURI', userURI, undefined, vscode.ConfigurationTarget.Workspace); + + const connInfo = JupyterExecutionBase.createRemoteConnectionInfo(userURI, this.configuration); + + const runningKernels: Kernel.IModel[] = await this.sessionManager.getActiveKernels(connInfo); + const arr: IKernelQuickPickItem[] = runningKernels.map(runningKernel => { + traceInfo(`Found running kernel ${runningKernel.id}, running since ${runningKernel.last_activity}`); + const localLastActivity = runningKernel.last_activity ? new Date(runningKernel.last_activity.toString()).toLocaleString() : '?'; + return { + label: `Kernel ${runningKernel.name} - ${runningKernel.id}`, + detail: `Running since ${localLastActivity}, ${runningKernel.connections} existing connections`, + kernelId: runningKernel.id + }; + }); + const startNewKernel = { + label: localize.DataScience.jupyterServerReconnectKernelStartNewLocal(), + picked: true, + kernelId: 'none' + }; + arr.unshift(startNewKernel); + + const kernelSelection = await this.appShell.showQuickPick(arr, { + ignoreFocusOut: true, + placeHolder: localize.DataScience.jupyterServerReconnectKernelLocal() + }); + + const autoShutdown = { + label: localize.DataScience.jupyterServerKernelAutoShutdownLocal(), + picked: true + }; + const leaveRunning = { + label: localize.DataScience.jupyterServerKernelLeaveRunningLocal(), + picked: true + }; + const shutdownOptions = [autoShutdown, leaveRunning]; + const shutdownSelection = await this.appShell.showQuickPick(shutdownOptions, { ignoreFocusOut: true }); + + let kernelUUID: string = ''; + if (kernelSelection && kernelSelection !== startNewKernel) { + traceInfo(`Will connect to existing kernel ${kernelSelection.kernelId}`); + kernelUUID = kernelSelection.kernelId; + sendTelemetryEvent(Telemetry.JupyterKernelSpecified); + + } else { + traceInfo('Will create a new kernel for connection'); + } + await this.configuration.updateSetting('dataScience.jupyterServerKernelId', kernelUUID, undefined, vscode.ConfigurationTarget.Workspace); + + let allowShutdown = true; + if (shutdownSelection === leaveRunning) { + traceInfo('Session will not be shutdown on close'); + allowShutdown = false; + } + sendTelemetryEvent(Telemetry.JupyterKernelAutoShutdown, undefined, { autoShutdownEnabled: allowShutdown }); + await this.configuration.updateSetting('dataScience.jupyterServerAllowKernelShutdown', allowShutdown, undefined, vscode.ConfigurationTarget.Workspace); + } } diff --git a/src/client/datascience/jupyter/jupyterExecution.ts b/src/client/datascience/jupyter/jupyterExecution.ts index 4bf699a9167a..a75de37758d0 100644 --- a/src/client/datascience/jupyter/jupyterExecution.ts +++ b/src/client/datascience/jupyter/jupyterExecution.ts @@ -49,6 +49,10 @@ enum ModuleExistsResult { export class JupyterExecutionBase implements IJupyterExecution { + public get sessionChanged(): Event { + return this.eventEmitter.event; + } + private processServicePromise: Promise; private commands: Record = {}; private jupyterPath: string | undefined; @@ -86,8 +90,27 @@ export class JupyterExecutionBase implements IJupyterExecution { } } - public get sessionChanged(): Event { - return this.eventEmitter.event; + public static createRemoteConnectionInfo = (uri: string, configuration: IConfigurationService): IConnection => { + let url: URL; + try { + url = new URL(uri); + } catch (err) { + // This should already have been parsed when set, so just throw if it's not right here + throw err; + } + const settings = configuration.getSettings(); + const allowUnauthorized = settings.datascience.allowUnauthorizedRemoteConnection ? settings.datascience.allowUnauthorizedRemoteConnection : false; + + return { + allowUnauthorized, + baseUrl: `${url.protocol}//${url.host}${url.pathname}`, + token: `${url.searchParams.get('token')}`, + hostName: url.hostname, + localLaunch: false, + localProcExitCode: undefined, + disconnected: (_l) => { return { dispose: noop }; }, + dispose: noop + }; } public dispose(): Promise { @@ -291,7 +314,7 @@ export class JupyterExecutionBase implements IJupyterExecution { } } else { // If we have a URI spec up a connection info for it - connection = this.createRemoteConnectionInfo(options.uri); + connection = JupyterExecutionBase.createRemoteConnectionInfo(options.uri, this.configuration); kernelSpec = undefined; } @@ -310,29 +333,6 @@ export class JupyterExecutionBase implements IJupyterExecution { return { connection, kernelSpec }; } - private createRemoteConnectionInfo = (uri: string): IConnection => { - let url: URL; - try { - url = new URL(uri); - } catch (err) { - // This should already have been parsed when set, so just throw if it's not right here - throw err; - } - const settings = this.configuration.getSettings(); - const allowUnauthorized = settings.datascience.allowUnauthorizedRemoteConnection ? settings.datascience.allowUnauthorizedRemoteConnection : false; - - return { - allowUnauthorized, - baseUrl: `${url.protocol}//${url.host}${url.pathname}`, - token: `${url.searchParams.get('token')}`, - hostName: url.hostname, - localLaunch: false, - localProcExitCode: undefined, - disconnected: (_l) => { return { dispose: noop }; }, - dispose: noop - }; - } - // tslint:disable-next-line: max-func-body-length @captureTelemetry(Telemetry.StartJupyter) private async startNotebookServer(useDefaultConfig: boolean, cancelToken?: CancellationToken): Promise<{ connection: IConnection; kernelSpec: IJupyterKernelSpec | undefined }> { diff --git a/src/client/datascience/jupyter/jupyterSession.ts b/src/client/datascience/jupyter/jupyterSession.ts index 515739ed95df..d1f65590aa4a 100644 --- a/src/client/datascience/jupyter/jupyterSession.ts +++ b/src/client/datascience/jupyter/jupyterSession.ts @@ -47,15 +47,21 @@ export class JupyterSession implements IJupyterSession { private connected: boolean = false; private jupyterPasswordConnect: IJupyterPasswordConnect; private oldSessions: Session.ISession[] = []; + private allowShutdown: boolean; + private desiredKernelId: string | undefined; constructor( connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, - jupyterPasswordConnect: IJupyterPasswordConnect + jupyterPasswordConnect: IJupyterPasswordConnect, + desiredKernelId: string | undefined, + allowShutdown: boolean ) { this.connInfo = connInfo; this.kernelSpec = kernelSpec; this.jupyterPasswordConnect = jupyterPasswordConnect; + this.allowShutdown = allowShutdown; + this.desiredKernelId = desiredKernelId; } public dispose(): Promise { @@ -219,17 +225,29 @@ export class JupyterSession implements IJupyterSession { private async createSession(serverSettings: ServerConnection.ISettings, contentsManager: ContentsManager, cancelToken?: CancellationToken): Promise { - // Create a temporary notebook for this session. - this.notebookFiles.push(await contentsManager.newUntitled({ type: 'notebook' })); + // Create a temporary notebook for this session. Give it a unique name, so there is no possiblity of us reusing a session lingering the jupyter server + // (sessions in jupyter are indexed by path, regardless if the file exists of not and regardless of the session name, so deleting the file later doesn't save us) + // See https://github.com/jupyter/notebook/blob/5.7.x/notebook/services/sessions/handlers.py#L65 + const sessionUUID = uuid(); + const nbFile = await contentsManager.newUntitled({ type: 'notebook' }); + this.notebookFiles.push(await contentsManager.rename(nbFile.path, `.vscode-jupyter-session-${sessionUUID}.ipynb`)); - // Create our session options using this temporary notebook and our connection info const options: Session.IOptions = { path: this.notebookFiles[this.notebookFiles.length - 1].path, kernelName: this.kernelSpec ? this.kernelSpec.name : '', - name: uuid(), // This is crucial to distinguish this session from any other. + name: sessionUUID, // This is crucial to distinguish this session from any other. serverSettings: serverSettings }; + if (this.desiredKernelId) { + options.kernelId = this.desiredKernelId; + traceInfo(`Connecting to existing kernel ${this.desiredKernelId}`); + } else { + traceInfo('Creating new kernel for connection'); + } + + // Create our session options using this temporary notebook and our connection info + return Cancellation.race(() => this.sessionManager!.startNew(options), cancelToken); } @@ -347,10 +365,14 @@ export class JupyterSession implements IJupyterSession { }); } } - await waitForPromise(session.shutdown(), 1000); + if (this.allowShutdown) { + await waitForPromise(session.shutdown(), 1000); + } } else { - // Shutdown may fail if the process has been killed - await waitForPromise(session.shutdown(), 1000); + if (this.allowShutdown) { + // Shutdown may fail if the process has been killed + await waitForPromise(session.shutdown(), 1000); + } } } catch { noop(); diff --git a/src/client/datascience/jupyter/jupyterSessionManager.ts b/src/client/datascience/jupyter/jupyterSessionManager.ts index b36053bb1418..089a2b19935b 100644 --- a/src/client/datascience/jupyter/jupyterSessionManager.ts +++ b/src/client/datascience/jupyter/jupyterSessionManager.ts @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -import { ServerConnection, SessionManager } from '@jupyterlab/services'; +import { Kernel, ServerConnection, SessionManager } from '@jupyterlab/services'; import { inject, injectable } from 'inversify'; import { CancellationToken } from 'vscode-jsonrpc'; +import { IConfigurationService } from '../../common/types'; import { IConnection, IJupyterKernelSpec, IJupyterPasswordConnect, IJupyterSession, IJupyterSessionManager } from '../types'; import { JupyterKernelSpec } from './jupyterKernelSpec'; import { JupyterSession } from './jupyterSession'; @@ -12,12 +13,16 @@ import { JupyterSession } from './jupyterSession'; @injectable() export class JupyterSessionManager implements IJupyterSessionManager { constructor( - @inject(IJupyterPasswordConnect) private jupyterPasswordConnect: IJupyterPasswordConnect - ) {} + @inject(IJupyterPasswordConnect) private jupyterPasswordConnect: IJupyterPasswordConnect, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService + ) { } - public async startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken) : Promise { + public async startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken): Promise { // Create a new session and attempt to connect to it - const session = new JupyterSession(connInfo, kernelSpec, this.jupyterPasswordConnect); + const settings = this.configurationService.getSettings(); + const allowShutdown = settings.datascience.jupyterServerAllowKernelShutdown; + const kernelId = settings.datascience.jupyterServerKernelId; + const session = new JupyterSession(connInfo, kernelSpec, this.jupyterPasswordConnect, kernelId, allowShutdown); try { await session.connect(cancelToken); } finally { @@ -28,19 +33,15 @@ export class JupyterSessionManager implements IJupyterSessionManager { return session; } - public async getActiveKernelSpecs(connection: IConnection) : Promise { - let sessionManager: SessionManager | undefined ; + public getActiveKernels(connection: IConnection): Promise { + return Kernel.listRunning(this.makeServerSettings(connection)); + } + + public async getActiveKernelSpecs(connection: IConnection): Promise { + let sessionManager: SessionManager | undefined; try { // Use our connection to create a session manager - const serverSettings = ServerConnection.makeSettings( - { - baseUrl: connection.baseUrl, - token: connection.token, - pageUrl: '', - // A web socket is required to allow token authentication (what if there is no token authentication?) - wsUrl: connection.baseUrl.replace('http', 'ws'), - init: { cache: 'no-store', credentials: 'same-origin' } - }); + const serverSettings = this.makeServerSettings(connection); sessionManager = new SessionManager({ serverSettings: serverSettings }); // Ask the session manager to refresh its list of kernel specs. @@ -65,4 +66,16 @@ export class JupyterSessionManager implements IJupyterSessionManager { } + private makeServerSettings(connection: IConnection): ServerConnection.ISettings { + return ServerConnection.makeSettings( + { + baseUrl: connection.baseUrl, + token: connection.token, + pageUrl: '', + // A web socket is required to allow token authentication (what if there is no token authentication?) + wsUrl: connection.baseUrl.replace('http', 'ws'), + init: { cache: 'no-store', credentials: 'same-origin' } + }); + } + } diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts b/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts index 517eaa655569..9f2361142150 100644 --- a/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts +++ b/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts @@ -3,20 +3,26 @@ 'use strict'; import { CancellationToken } from 'vscode-jsonrpc'; +import { Kernel } from '@jupyterlab/services'; import { noop } from '../../../../test/core'; import { IConnection, IJupyterKernelSpec, IJupyterSession, IJupyterSessionManager } from '../../types'; export class GuestJupyterSessionManager implements IJupyterSessionManager { - public constructor(private realSessionManager : IJupyterSessionManager) { + public constructor(private realSessionManager: IJupyterSessionManager) { noop(); } - public startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken) : Promise { + public startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken): Promise { return this.realSessionManager.startNew(connInfo, kernelSpec, cancelToken); } - public async getActiveKernelSpecs(_connection: IConnection) : Promise { + public async getActiveKernels(_connection: IConnection): Promise { + // Don't return any kernels in guest mode. They're only needed for the host side + return Promise.resolve([]); + } + + public async getActiveKernelSpecs(_connection: IConnection): Promise { // Don't return any kernel specs in guest mode. They're only needed for the host side return Promise.resolve([]); } diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 14591afabafe..93207aa513b6 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -148,6 +148,7 @@ export const IJupyterSessionManager = Symbol('IJupyterSessionManager'); export interface IJupyterSessionManager { startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken): Promise; getActiveKernelSpecs(connInfo: IConnection): Promise; + getActiveKernels(connInfo: IConnection): Promise; } export interface IJupyterKernelSpec extends IAsyncDisposable { diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 9e70f61d7c9d..f3ca42733a03 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1272,6 +1272,8 @@ export interface IEventNamePropertyMapping { [Telemetry.SelfCertsMessageClose]: never | undefined; [Telemetry.SelfCertsMessageEnabled]: never | undefined; [Telemetry.SelectJupyterURI]: never | undefined; + [Telemetry.JupyterKernelSpecified]: never | undefined; + [Telemetry.JupyterKernelAutoShutdown]: { autoShutdownEnabled: boolean }; [Telemetry.SetJupyterURIToLocal]: never | undefined; [Telemetry.SetJupyterURIToUserSpecified]: never | undefined; [Telemetry.ShiftEnterBannerShown]: never | undefined; diff --git a/src/datascience-ui/react-common/settingsReactSide.ts b/src/datascience-ui/react-common/settingsReactSide.ts index e7c81bff10c6..35f269cd73fa 100644 --- a/src/datascience-ui/react-common/settingsReactSide.ts +++ b/src/datascience-ui/react-common/settingsReactSide.ts @@ -36,6 +36,8 @@ function load() { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/color.test.ts b/src/test/datascience/color.test.ts index 1088dd868f46..6638a62d5616 100644 --- a/src/test/datascience/color.test.ts +++ b/src/test/datascience/color.test.ts @@ -53,6 +53,8 @@ suite('Theme colors', () => { jupyterLaunchTimeout: 20000, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 4aec10054698..02da59ba6710 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -407,6 +407,8 @@ export class DataScienceIocContainer extends UnitTestIocContainer { jupyterLaunchTimeout: 20000, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/editor-integration/codewatcher.unit.test.ts b/src/test/datascience/editor-integration/codewatcher.unit.test.ts index 716246c00b1c..c9efadb5f096 100644 --- a/src/test/datascience/editor-integration/codewatcher.unit.test.ts +++ b/src/test/datascience/editor-integration/codewatcher.unit.test.ts @@ -74,6 +74,8 @@ suite('DataScience Code Watcher Unit Tests', () => { jupyterLaunchTimeout: 20000, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/execution.unit.test.ts b/src/test/datascience/execution.unit.test.ts index c4c3d60738c3..b8e3ad409208 100644 --- a/src/test/datascience/execution.unit.test.ts +++ b/src/test/datascience/execution.unit.test.ts @@ -540,6 +540,8 @@ suite('Jupyter Execution', async () => { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts index 76653d3b7e34..9caa0a36faae 100644 --- a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts +++ b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts @@ -133,6 +133,8 @@ suite('Interactive window command listener', async () => { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: '', changeDirOnImportExport: true, notebookFileRoot: 'WORKSPACE', diff --git a/src/test/datascience/interactiveWindowTestHelpers.tsx b/src/test/datascience/interactiveWindowTestHelpers.tsx index c018277e6091..b95fa55c5e17 100644 --- a/src/test/datascience/interactiveWindowTestHelpers.tsx +++ b/src/test/datascience/interactiveWindowTestHelpers.tsx @@ -307,6 +307,8 @@ export function defaultDataScienceSettings(): IDataScienceSettings { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/mockJupyterManager.ts b/src/test/datascience/mockJupyterManager.ts index a8b967665899..bf45dc4cd8ee 100644 --- a/src/test/datascience/mockJupyterManager.ts +++ b/src/test/datascience/mockJupyterManager.ts @@ -12,6 +12,7 @@ import * as uuid from 'uuid/v4'; import { EventEmitter } from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; +import { Kernel } from '@jupyterlab/services'; import { Cancellation } from '../../client/common/cancellation'; import { ExecutionResult, IProcessServiceFactory, IPythonExecutionFactory, Output } from '../../client/common/process/types'; import { IAsyncDisposableRegistry, IConfigurationService } from '../../client/common/types'; @@ -236,6 +237,10 @@ export class MockJupyterManager implements IJupyterSessionManager { } } + public getActiveKernels(_connection: IConnection): Promise { + return Promise.resolve([]); + } + public getActiveKernelSpecs(_connection: IConnection): Promise { return Promise.resolve([]); } diff --git a/src/test/datascience/reactHelpers.ts b/src/test/datascience/reactHelpers.ts index c4152c58dc6d..5d6bc7b4ef2b 100644 --- a/src/test/datascience/reactHelpers.ts +++ b/src/test/datascience/reactHelpers.ts @@ -349,6 +349,8 @@ export function setUpDomEnvironment() { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true,