Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/Sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const {
normalizeSize,
apiRequest
} = require('./utils')
const { SANDBOX_SIZES } = require('./constants')
const { SANDBOX_SIZES, PROTOCOL_VERSION, API_PREFIX } = require('./constants')
const { SandboxSocket } = require('./ws')

/**
Expand All @@ -44,6 +44,7 @@ class Sandbox {
this.region = options.region
this.idleTimeout = options.idleTimeout
this.maxLifetime = options.maxLifetime
this.protocolVersion = options.protocolVersion || PROTOCOL_VERSION

this.namespace = options.namespace
this.apiHost = options.apiHost
Expand Down Expand Up @@ -98,7 +99,7 @@ class Sandbox {
if (options.policy !== undefined) body.policy = options.policy
if (options.ports !== undefined) body.ports = options.ports

const url = `${creds.apiHost}/api/v1/namespaces/${creds.namespace}/sandboxes`
const url = `${creds.apiHost}${API_PREFIX}/namespaces/${creds.namespace}/sandboxes`
const payload = await apiRequest('POST', url, creds.apiKey, body)

const sandboxId = payload.sandboxId
Expand All @@ -112,6 +113,7 @@ class Sandbox {
region: payload.region,
idleTimeout: payload.idleTimeout,
maxLifetime: payload.maxLifetime,
protocolVersion: payload.protocolVersion || PROTOCOL_VERSION,
previewUrls: parsePreviewUrls(payload.previewUrls),
managementEndpoint: payload.managementEndpoint || null,
namespace: creds.namespace,
Expand Down Expand Up @@ -146,7 +148,7 @@ class Sandbox {
console.warn('[aio-lib-sandbox] alpha — APIs may change without notice')
const creds = resolveCredentials(options)
const base = options.managementEndpoint || creds.apiHost
const url = `${base}/api/v1/namespaces/${creds.namespace}/sandboxes/${sandboxId}`
const url = `${base}${API_PREFIX}/namespaces/${creds.namespace}/sandboxes/${sandboxId}`
const payload = await apiRequest('GET', url, creds.apiKey)

return new Sandbox({
Expand All @@ -157,6 +159,7 @@ class Sandbox {
region: payload.region,
idleTimeout: payload.idleTimeout,
maxLifetime: payload.maxLifetime,
protocolVersion: payload.protocolVersion || PROTOCOL_VERSION,
managementEndpoint: payload.managementEndpoint || options.managementEndpoint || null,
previewUrls: parsePreviewUrls(payload.previewUrls),
namespace: creds.namespace,
Expand All @@ -175,6 +178,15 @@ class Sandbox {
return SANDBOX_SIZES
}

/**
* Sandbox wire protocol major bundled with this SDK.
*
* @type {string}
*/
static get protocolVersion () {
return PROTOCOL_VERSION
}

/**
* Exposes `resolveCredentials` as a static helper (useful for testing).
*
Expand Down Expand Up @@ -507,7 +519,7 @@ class Sandbox {
*/
async destroy () {
const base = this.managementEndpoint || this.apiHost
const url = `${base}/api/v1/namespaces/${this.namespace}/sandboxes/${this.id}`
const url = `${base}${API_PREFIX}/namespaces/${this.namespace}/sandboxes/${this.id}`
this.ws?.beginIntentionalClose()

let payload
Expand Down
5 changes: 4 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ const SANDBOX_SIZES = Object.freeze({
XLARGE: { cpu: '8000m', memory: '32Gi', gpu: 1 }
})

module.exports = { SANDBOX_SIZES }
const PROTOCOL_VERSION = '1'
const API_PREFIX = `/api/v${PROTOCOL_VERSION}`

module.exports = { SANDBOX_SIZES, PROTOCOL_VERSION, API_PREFIX }
6 changes: 5 additions & 1 deletion src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class SandboxWebSocketError extends SandboxSDKError {}
class SandboxCommandNotFoundError extends SandboxSDKError {}
class SandboxPortNotProvisionedError extends SandboxSDKError {}
class SandboxInvalidPortError extends SandboxClientError {}
class ProtocolVersionMismatchError extends SandboxClientError {}
class SandboxMalformedFrameError extends SandboxClientError {}

module.exports = {
SandboxSDKError,
Expand All @@ -39,5 +41,7 @@ module.exports = {
SandboxWebSocketError,
SandboxCommandNotFoundError,
SandboxPortNotProvisionedError,
SandboxInvalidPortError
SandboxInvalidPortError,
ProtocolVersionMismatchError,
SandboxMalformedFrameError
}
10 changes: 8 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ governing permissions and limitations under the License.
*/

const Sandbox = require('./Sandbox')
const { PROTOCOL_VERSION } = require('./constants')
const {
SandboxSDKError,
SandboxInitializationError,
Expand All @@ -20,11 +21,14 @@ const {
SandboxWebSocketError,
SandboxCommandNotFoundError,
SandboxPortNotProvisionedError,
SandboxInvalidPortError
SandboxInvalidPortError,
ProtocolVersionMismatchError,
SandboxMalformedFrameError
} = require('./errors')

module.exports = {
Sandbox,
SANDBOX_PROTOCOL_VERSION: PROTOCOL_VERSION,
SandboxSDKError,
SandboxInitializationError,
SandboxClientError,
Expand All @@ -34,5 +38,7 @@ module.exports = {
SandboxWebSocketError,
SandboxCommandNotFoundError,
SandboxPortNotProvisionedError,
SandboxInvalidPortError
SandboxInvalidPortError,
ProtocolVersionMismatchError,
SandboxMalformedFrameError
}
4 changes: 2 additions & 2 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const {
SandboxUnauthorizedError,
SandboxTimeoutError
} = require('./errors')
const { SANDBOX_SIZES } = require('./constants')
const { SANDBOX_SIZES, API_PREFIX } = require('./constants')

/**
* Builds a Basic authorization header from a Runtime API key.
Expand Down Expand Up @@ -72,7 +72,7 @@ function normalizeApiHost (host) {
function buildWebSocketEndpoint (apiHost, namespace, sandboxId) {
const url = new URL(apiHost)
url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:'
url.pathname = `/api/v1/namespaces/${namespace}/sandboxes/${sandboxId}/exec`
url.pathname = `${API_PREFIX}/namespaces/${namespace}/sandboxes/${sandboxId}/exec`
url.search = ''
return url.toString()
}
Expand Down
12 changes: 12 additions & 0 deletions src/ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const WebSocket = require('ws')
const {
SandboxClientError,
SandboxCommandNotFoundError,
ProtocolVersionMismatchError,
SandboxMalformedFrameError,
SandboxUnauthorizedError,
SandboxWebSocketError
} = require('./errors')
Expand Down Expand Up @@ -355,6 +357,16 @@ class SandboxSocket {
`Sandbox '${this.id}' rejected the WebSocket authentication token`
)
}
if (code === 4003) {
return new ProtocolVersionMismatchError(
`Sandbox '${this.id}' WebSocket protocol version does not match this SDK`
)
}
if (code === 4004) {
return new SandboxMalformedFrameError(
`Sandbox '${this.id}' rejected a malformed WebSocket frame`
)
}
return new SandboxWebSocketError(
`Sandbox '${this.id}' WebSocket closed with code ${code}`
)
Expand Down
32 changes: 30 additions & 2 deletions test/Sandbox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ const {
SandboxCommandNotFoundError,
SandboxInitializationError,
SandboxNotFoundError,
ProtocolVersionMismatchError,
SandboxPortNotProvisionedError,
SandboxInvalidPortError,
SandboxTimeoutError,
SandboxUnauthorizedError,
SandboxWebSocketError
SandboxWebSocketError,
SandboxMalformedFrameError
} = require('../src/errors')

jest.mock('ws')
Expand Down Expand Up @@ -199,6 +201,12 @@ describe('Sandbox', () => {
})
})

describe('protocolVersion', () => {
test('exposes the bundled sandbox protocol major', () => {
expect(Sandbox.protocolVersion).toBe('1')
})
})

// -------------------------------------------------------------------------
// Static factories
// -------------------------------------------------------------------------
Expand All @@ -213,6 +221,7 @@ describe('Sandbox', () => {
status: 'ready',
token: 'tok-new',
maxLifetime: 3600,
protocolVersion: '1',
previewUrls: {
3000: 'https://sb-new-3000.preview.example.net'
}
Expand All @@ -236,6 +245,7 @@ describe('Sandbox', () => {

expect(sandbox.id).toBe('sb-new')
expect(sandbox.status).toBe('ready')
expect(sandbox.protocolVersion).toBe('1')
expect(sandbox.previewUrls).toEqual(new Map([
[3000, 'https://sb-new-3000.preview.example.net']
]))
Expand Down Expand Up @@ -471,7 +481,8 @@ describe('Sandbox', () => {
sandboxId: 'sb-get',
status: 'running',
cluster: 'cluster-b',
region: 'va6'
region: 'va6',
protocolVersion: '1'
})
})

Expand All @@ -484,6 +495,7 @@ describe('Sandbox', () => {
expect(sandbox.id).toBe('sb-get')
expect(sandbox.status).toBe('running')
expect(sandbox.cluster).toBe('cluster-b')
expect(sandbox.protocolVersion).toBe('1')
})

test('stores idleTimeout from the get response on the instance', async () => {
Expand Down Expand Up @@ -602,6 +614,22 @@ describe('Sandbox', () => {
await expect(p).rejects.toThrow(SandboxUnauthorizedError)
})

test('rejects on protocol mismatch close code 4003 with ProtocolVersionMismatchError', async () => {
const sandbox = new Sandbox(BASE_OPTIONS)
const p = sandbox.connect()
sockets[0].open()
sockets[0].closeWith(4003)
await expect(p).rejects.toThrow(ProtocolVersionMismatchError)
})

test('rejects on malformed frame close code 4004 with SandboxMalformedFrameError', async () => {
const sandbox = new Sandbox(BASE_OPTIONS)
const p = sandbox.connect()
sockets[0].open()
sockets[0].closeWith(4004)
await expect(p).rejects.toThrow(SandboxMalformedFrameError)
})

test('rejects on unexpected socket close', async () => {
const sandbox = new Sandbox(BASE_OPTIONS)
const p = sandbox.connect()
Expand Down
Loading