Skip to content

Commit

Permalink
Improve startup speed (vscode-kubernetes-tools#310)
Browse files Browse the repository at this point in the history
  • Loading branch information
itowlson authored Jul 19, 2018
1 parent c1062dd commit 348dca8
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 65 deletions.
58 changes: 37 additions & 21 deletions src/components/clusterprovider/azure/azureclusterprovider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as restify from 'restify';
import restify = require('restify');
import * as portfinder from 'portfinder';
import * as clusterproviderregistry from '../clusterproviderregistry';
import * as azure from './azure';
Expand All @@ -11,7 +11,8 @@ import { refreshExplorer } from '../common/explorer';

// TODO: de-globalise
let wizardServer: restify.Server;
let wizardPort: number;
let wizardPort: number | undefined;
let registered = false;

type HtmlRequestHandler = (
step: string | undefined,
Expand All @@ -20,30 +21,45 @@ type HtmlRequestHandler = (
) => Promise<string>;

export async function init(registry: clusterproviderregistry.ClusterProviderRegistry, context: azure.Context): Promise<void> {
if (!wizardServer) {
wizardServer = restify.createServer({
formatters: {
'text/html': (req, resp, body) => body
}
});
if (!registered) {
const serve = serveCallback(context);
registry.register({id: 'aks', displayName: "Azure Kubernetes Service", supportedActions: ['create', 'configure'], serve: serve});
registry.register({id: 'acs', displayName: "Azure Container Service", supportedActions: ['create', 'configure'], serve: serve});
registered = true;
}
}

function serveCallback(context: azure.Context): () => Promise<number> {
return () => serve(context);
}

wizardPort = await portfinder.getPortPromise({ port: 44000 });
async function serve(context: azure.Context): Promise<number> {
if (wizardPort) {
return wizardPort;
}

const htmlServer = new HtmlServer(context);
const restifyImpl: typeof restify = require('restify');
wizardServer = restifyImpl.createServer({
formatters: {
'text/html': (req, resp, body) => body
}
});

wizardServer.use(restify.plugins.queryParser(), restify.plugins.bodyParser());
wizardServer.listen(wizardPort, '127.0.0.1');
wizardPort = await portfinder.getPortPromise({ port: 44000 });

// You MUST use fat arrow notation for the handler callbacks: passing the
// function reference directly will foul up the 'this' pointer.
wizardServer.get('/create', (req, resp, n) => htmlServer.handleGetCreate(req, resp, n));
wizardServer.post('/create', (req, resp, n) => htmlServer.handlePostCreate(req, resp, n));
wizardServer.get('/configure', (req, resp, n) => htmlServer.handleGetConfigure(req, resp, n));
wizardServer.post('/configure', (req, resp, n) => htmlServer.handlePostConfigure(req, resp, n));
const htmlServer = new HtmlServer(context);

registry.register({id: 'aks', displayName: "Azure Kubernetes Service", port: wizardPort, supportedActions: ['create', 'configure']});
registry.register({id: 'acs', displayName: "Azure Container Service", port: wizardPort, supportedActions: ['create', 'configure']});
}
wizardServer.use(restifyImpl.plugins.queryParser(), restifyImpl.plugins.bodyParser());
wizardServer.listen(wizardPort, '127.0.0.1');

// You MUST use fat arrow notation for the handler callbacks: passing the
// function reference directly will foul up the 'this' pointer.
wizardServer.get('/create', (req, resp, n) => htmlServer.handleGetCreate(req, resp, n));
wizardServer.post('/create', (req, resp, n) => htmlServer.handlePostCreate(req, resp, n));
wizardServer.get('/configure', (req, resp, n) => htmlServer.handleGetConfigure(req, resp, n));
wizardServer.post('/configure', (req, resp, n) => htmlServer.handlePostConfigure(req, resp, n));

return wizardPort;
}

class HtmlServer {
Expand Down
4 changes: 2 additions & 2 deletions src/components/clusterprovider/clusterproviderregistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ export type ClusterProviderAction = 'create' | 'configure';
export interface ClusterProvider {
readonly id: string;
readonly displayName: string;
readonly port: number;
readonly supportedActions: ClusterProviderAction[];
serve(): Promise<number>;
}

export interface ClusterProviderRegistry {
Expand All @@ -16,7 +16,7 @@ class RegistryImpl implements ClusterProviderRegistry {
private readonly providers = new Array<ClusterProvider>();

public register(clusterProvider: ClusterProvider) {
console.log(`You registered ${clusterProvider.id} for port ${clusterProvider.port}`);
console.log(`You registered cluster type ${clusterProvider.id}`);
this.providers.push(clusterProvider);
}

Expand Down
17 changes: 10 additions & 7 deletions src/components/clusterprovider/clusterproviderserver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as restify from 'restify';
import restify = require('restify');
import * as portfinder from 'portfinder';
import * as clusterproviderregistry from './clusterproviderregistry';
import { styles, script, waitScript } from '../../wizard';
Expand All @@ -8,16 +8,17 @@ let cpServer: restify.Server;
let cpPort: number;

export async function init(): Promise<void> {
const restifyImpl: typeof restify = require('restify');
if (!cpServer) {
cpServer = restify.createServer({
cpServer = restifyImpl.createServer({
formatters: {
'text/html': (req, resp, body) => body
}
});

cpPort = await portfinder.getPortPromise({ port: 44000 });

cpServer.use(restify.plugins.queryParser());
cpServer.use(restifyImpl.plugins.queryParser());
cpServer.listen(cpPort, '127.0.0.1');
cpServer.get('/', handleRequest);
}
Expand All @@ -27,10 +28,10 @@ export function url(action: clusterproviderregistry.ClusterProviderAction): stri
return `http://localhost:${cpPort}/?action=${action}`;
}

function handleRequest(request: restify.Request, response: restify.Response, next: restify.Next): void {
async function handleRequest(request: restify.Request, response: restify.Response, next: restify.Next): Promise<void> {
const clusterType = request.query['clusterType'];
if (clusterType) {
handleClusterTypeSelection(request, response, next);
await handleClusterTypeSelection(request, response, next);
} else {
handleGetProviderList(request, response, next);
}
Expand All @@ -46,14 +47,16 @@ function handleGetProviderList(request: restify.Request, response: restify.Respo
next();
}

function handleClusterTypeSelection(request: restify.Request, response: restify.Response, next: restify.Next): void {
async function handleClusterTypeSelection(request: restify.Request, response: restify.Response, next: restify.Next): Promise<void> {
const clusterType = request.query['clusterType'];
const action = request.query["action"];

reporter.sendTelemetryEvent("cloudselection", { action: action, clusterType: clusterType });

const clusterProvider = clusterproviderregistry.get().list().find((cp) => cp.id === clusterType); // TODO: move into clusterproviderregistry
const url = `http://localhost:${clusterProvider.port}/${action}?clusterType=${clusterProvider.id}`;
const port = await clusterProvider.serve();
console.log(`${clusterProvider.id} wizard serving on port ${port}`);
const url = `http://localhost:${port}/${action}?clusterType=${clusterProvider.id}`;
response.redirect(307, url, next);
}

Expand Down
61 changes: 39 additions & 22 deletions src/components/clusterprovider/minikube/minikubeclusterprovider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as restify from 'restify';
import restify = require('restify');
import * as portfinder from 'portfinder';
import * as vscode from 'vscode';
import * as clusterproviderregistry from '../clusterproviderregistry';
Expand All @@ -19,32 +19,49 @@ type HtmlRequestHandler = (
) => Promise<string>;

let minikubeWizardServer: restify.Server;
let minikubeWizardPort: number | undefined;
let registered = false;

export async function init(registry: clusterproviderregistry.ClusterProviderRegistry, context: Context): Promise<void> {
if (!minikubeWizardServer) {
minikubeWizardServer = restify.createServer({
formatters: {
'text/html': (req, resp, body) => body
}
});

const port = await portfinder.getPortPromise({ port: 44000 });

const htmlServer = new HtmlServer(context);
minikubeWizardServer.use(restify.plugins.queryParser(), restify.plugins.bodyParser());
minikubeWizardServer.listen(port, '127.0.0.1');

// You MUST use fat arrow notation for the handler callbacks: passing the
// function reference directly will foul up the 'this' pointer.
minikubeWizardServer.get('/create', (req, resp, n) => htmlServer.handleGetCreate(req, resp, n));
minikubeWizardServer.post('/create', (req, resp, n) => htmlServer.handlePostCreate(req, resp, n));
minikubeWizardServer.get('/configure', (req, resp, n) => htmlServer.handleGetConfigure(req, resp, n));
minikubeWizardServer.post('/configure', (req, resp, n) => htmlServer.handlePostConfigure(req, resp, n));

registry.register({id: 'minikube', displayName: "Minikube local cluster", port: port, supportedActions: ['create', 'configure']});
if (!registered) {
const serve = serveCallback(context);
registry.register({id: 'minikube', displayName: "Minikube local cluster", supportedActions: ['create', 'configure'], serve: serve});
registered = true;
}
}

function serveCallback(context: Context): () => Promise<number> {
return () => serve(context);
}

async function serve(context: Context): Promise<number> {
if (minikubeWizardPort) {
return minikubeWizardPort;
}

const restifyImpl: typeof restify = require('restify');
minikubeWizardServer = restifyImpl.createServer({
formatters: {
'text/html': (req, resp, body) => body
}
});

minikubeWizardPort = await portfinder.getPortPromise({ port: 44000 });

const htmlServer = new HtmlServer(context);
minikubeWizardServer.use(restifyImpl.plugins.queryParser(), restifyImpl.plugins.bodyParser());
minikubeWizardServer.listen(minikubeWizardPort, '127.0.0.1');

// You MUST use fat arrow notation for the handler callbacks: passing the
// function reference directly will foul up the 'this' pointer.
minikubeWizardServer.get('/create', (req, resp, n) => htmlServer.handleGetCreate(req, resp, n));
minikubeWizardServer.post('/create', (req, resp, n) => htmlServer.handlePostCreate(req, resp, n));
minikubeWizardServer.get('/configure', (req, resp, n) => htmlServer.handleGetConfigure(req, resp, n));
minikubeWizardServer.post('/configure', (req, resp, n) => htmlServer.handlePostConfigure(req, resp, n));

return minikubeWizardPort;
}

class HtmlServer {
constructor(readonly context: Context) {}

Expand Down
26 changes: 15 additions & 11 deletions src/components/download/download.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
const home = process.env['HOME'];

import * as download_core from 'download'; // NEVER do a naked import of this as it corrupts $HOME on Windows - use wrapper functions from this module (or export the download function from this module if you really need the flexibility of the core implementation)
import * as path from 'path';
import * as stream from 'stream';
import * as tmp from 'tmp';

import { succeeded, Errorable } from '../../errorable';

// Fix download module corrupting HOME environment variable on Windows
// See https://github.com/Azure/vscode-kubernetes-tools/pull/302#issuecomment-404678781
// and https://github.com/kevva/npm-conf/issues/13
if (home) {
process.env['HOME'] = home;
}

type DownloadFunc =
(url: string, destination?: string, options?: any)
=> Promise<Buffer> & stream.Duplex; // Stream has additional events - see https://www.npmjs.com/package/download

const download: DownloadFunc = download_core;
let download: DownloadFunc;

function ensureDownloadFunc() {
if (!download) {
// Fix download module corrupting HOME environment variable on Windows
// See https://github.com/Azure/vscode-kubernetes-tools/pull/302#issuecomment-404678781
// and https://github.com/kevva/npm-conf/issues/13
const home = process.env['HOME'];
download = require('download');
if (home) {
process.env['HOME'] = home;
}
}
}

export async function toTempFile(sourceUrl: string): Promise<Errorable<string>> {
const tempFileObj = tmp.fileSync({ prefix: "vsk-autoinstall-" });
Expand All @@ -30,6 +33,7 @@ export async function toTempFile(sourceUrl: string): Promise<Errorable<string>>
}

export async function to(sourceUrl: string, destinationFile: string): Promise<Errorable<void>> {
ensureDownloadFunc();
try {
await download(sourceUrl, path.dirname(destinationFile), { filename: path.basename(destinationFile) });
return { succeeded: true, result: null };
Expand Down
5 changes: 3 additions & 2 deletions src/explainer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import * as k8s from 'k8s';
import k8s = require('k8s');
import * as pluralize from 'pluralize';
import * as kubeconfig from './kubeconfig';
import { formatComplex, formatOne, Typed, formatType } from "./schema-formatting";
Expand All @@ -16,8 +16,9 @@ function fixUrl(kapi: any /* deliberately bypass type checks as .domain is inter
}

function readSwaggerCore(kc: kubeconfig.KubeConfig): Promise<any> {
const k8sImpl: typeof k8s = require('k8s');
return new Promise((resolve, reject) => {
const kapi = k8s.api(apiCredentials(kc));
const kapi = k8sImpl.api(apiCredentials(kc));
fixUrl(kapi);
kapi.get('swagger.json', (err, data) => {
if (err) {
Expand Down

0 comments on commit 348dca8

Please sign in to comment.