Skip to content

Commit

Permalink
feat: support for specifying more than one ISA system to connect to, …
Browse files Browse the repository at this point in the history
…round-robin on failure
  • Loading branch information
sparkpunkd committed Jul 4, 2019
1 parent 975b4bf commit 0353c37
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 20 deletions.
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Build the typescript interface module:

This package has automated tests that run with [jest](). Test with:

yarn test
yarn test

### Running

Expand All @@ -55,7 +55,7 @@ This will start a [nodemon](https://nodemon.io/) watch on the source files and r

For production use, a simple server can be run with:

yarn server
yarn server

These servers listen on port `3000` by default.

Expand All @@ -68,7 +68,7 @@ Experiment from the REPL with:
Import into an external project with:

import { Quantel } from 'tv-automation-quantel-gateway'
const { Quantel } = require('tv-autonation-quantel-gateway')
const { Quantel } = require('tv-automation-quantel-gateway')

See the [walkthrough for how to do playback](./doc/plyout_walkthrough.md) with this module as a Node.js API.

Expand All @@ -82,25 +82,31 @@ To connect to an ISA system on a different host, POST to:

/connect/:address

The `:address` should consist of a DNS name or IP address and, optionally, a port number (defaults to 2096), where the CORBA Interoperable Object Reference (IOR) of the Quantel ISA is advertised. For example:
ISA systems are normally deployed in _master_ and _slave_ pairs. The `:address` should consist of a comma separated list of DNS names or IP addresses and, optionally, with port number (defaults to 2096), where the CORBA Interoperable Object Reference (IOR) of the Quantel ISA(s) is/are advertised. For example:

/connect/isa.national.ztv.com:3737
/connect/isa-master.national.ztv.com:3737,isa-slave.national.ztv.com:3737

A successful request produces a JSON response with the discovered IOR (`isaIOR`) and ISA endpoint address (`href`). Subsequently, the currently configured connection can be queried with a GET request to `/connect`.

### Topology of a Quantel system

Paths are all of the form ...
In general, paths are all of the form ...

/:zoneID/server/:serverID/port/:portID
/:zoneID/clip/:clipID(/fragments)
/:zoneID/format/:formatID
/:zoneID/copy/:copyID

In most cases and in the current implementation, `:zoneID` is the `default` local zone at the ISA that the gateway connects to.

Types are:

* `:zoneID` - zone number or `default`;
* `:serverID` - integer number or the string name of the server;
* `:portID` - string name of the port.
* `:portID` - string name of the port;
* `:clipID` - identifier for a clip within a zone;
* `:formatID` - identifier for a specific media format;
* `:copyID` - identifier for a new clip that is a copy of an existing clip.

A GET request to the root path `/` lists all available zones.

Expand Down
61 changes: 60 additions & 1 deletion src/__tests__/test_framework.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ describe('Error handling when no server running', () => {

test('Test failed IOR HTTP connection', async () => {
expect.assertions(1)
await expect(Quantel.testConnection()).rejects.toThrow('CORBA subsystem: TRANSIENT')
await expect(Quantel.getServers()).rejects.toThrow('CORBA subsystem: TRANSIENT')
})

test('Test fail to get servers 1', async () => {
Expand Down Expand Up @@ -165,3 +165,62 @@ describe('Error handling when server has failed', () => {
await spawn.stop()
})
})

describe('Error handling when server has failed, two servers', () => {

let isaIOR: string

beforeAll(async () => {
isaIOR = await spawn.start()
})

test('Default get connection reference and close', async () => {
await expect(Quantel.getISAReference(['http://127.0.0.1:2096', 'http://localhost:2096' ]))
.resolves.toStrictEqual({
type: 'ConnectionDetails',
href: 'http://127.0.0.1:2096',
isaIOR,
refs: [ 'http://127.0.0.1:2096', 'http://localhost:2096' ],
robin: 2 } as Quantel.ConnectionDetails)
})

test('Stopping server', async () => {
await expect(spawn.stop()).resolves.toBeUndefined()
})

test('Test fail to get servers 1', async () => {
expect.assertions(1)
await expect(Quantel.getServers()).rejects.toThrow('CORBA subsystem: TRANSIENT')
})

test('Test the failed CORBA connection 1', async () => {
expect.assertions(1)
await expect(Quantel.testConnection()).rejects.toThrow('ECONNREFUSED')
})

test('Test the failed CORBA connection 2', async () => {
expect.assertions(1)
await expect(Quantel.testConnection()).rejects.toThrow('ECONNREFUSED')
})

test('Restart server and get details', async () => {
isaIOR = await spawn.start()
// await new Promise((resolve) => setTimeout(() => resolve(), 1000))
await expect(Quantel.testConnection()).resolves.toBe('PONG!')
await expect(Quantel.getISAReference()).resolves.toStrictEqual({
type: 'ConnectionDetails',
href: 'http://localhost:2096',
isaIOR,
refs: [ 'http://127.0.0.1:2096', 'http://localhost:2096' ],
robin: 5 } as Quantel.ConnectionDetails)
})

test('Check that get servers now works', async () => {
await expect(Quantel.getServers()).resolves.toBeTruthy()
})

afterAll(async () => {
Quantel.destroyOrb()
await spawn.stop()
})
})
29 changes: 18 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,11 @@ export namespace Quantel {
}
}

function resetConnection () {
isaIOR = null
robin++
}

export async function getISAReference (ref?: string | string[], count?: number): Promise<ConnectionDetails> {
if (typeof ref === 'string') ref = [ ref ]
let myCount: number = count ? count + 1 : 1
Expand All @@ -403,6 +408,7 @@ export namespace Quantel {
isaIOR = isaIOR.then(x => x, (): Promise<string> => new Promise((resolve, reject) => {
if (stickyRef[index].endsWith('/')) { stickyRef[index] = stickyRef[index].slice(0, -1) }
if (stickyRef[index].indexOf(':') < 0) { stickyRef[index] = stickyRef[index] + ':2096' }
console.log('About to request', index, stickyRef[index])
request({
uri: stickyRef[index] + '/ZoneManager.ior',
resolveWithFullResponse: true
Expand All @@ -415,16 +421,20 @@ export namespace Quantel {
`HTTP request for ISA IOR failed with status ${res.statusCode}: ${res.statusMessage}`,
res.statusCode))
} else {
robin++
getISAReference(undefined, myCount).then(x => resolve(x.isaIOR), reject)
console.log('Got in here 1')
resetConnection()
getISAReference(undefined, myCount).catch(reject)
resolve(isaIOR as Promise<string>)
}
}
}, err => {
if (myCount >= stickyRef.length) {
reject(err)
} else {
robin++
getISAReference(undefined, myCount).then(x => resolve(x.isaIOR), reject)
console.log('Got in here 2')
resetConnection()
getISAReference(undefined, myCount).catch(reject)
resolve(isaIOR as Promise<string>)
}
})
}))
Expand All @@ -437,11 +447,6 @@ export namespace Quantel {
}
}

function resetConnection () {
isaIOR = null
robin++
}

export function destroyOrb () {
quantel.destroyOrb()
}
Expand All @@ -459,11 +464,13 @@ export namespace Quantel {
// Resolves to 'PONG!' on success, otherwise rejects with a connection error
export async function testConnection (): Promise<string> {
try {
console.log('Testing connection', robin)
await getISAReference()
return await quantel.testConnection(await isaIOR)
} catch (err) {
if (err.message.indexOf('TRANSIENT') >= 0) { resetConnection() }
if (err.message.indexOf('OBJECT_NOT_EXIST') >= 0) {
// if (err.message.indexOf('TRANSIENT') >= 0) { resetConnection() }
if (err.message.indexOf('OBJECT_NOT_EXIST') >= 0 ||
err.message.indexOf('TRANSIENT') >= 0) {
resetConnection()
return testConnection()
}
Expand Down
7 changes: 6 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ router.post('/connect/:addr', async (ctx) => {
if (ctx.params.addr.indexOf(':') < 0) {
ctx.params.addr += ':2096'
}
ctx.body = await Quantel.getISAReference(`http://${ctx.params.addr}`)
if (ctx.params.addr.indexOf(',') >= 0) {
ctx.body = await Quantel.getISAReference(
ctx.params.addr.split(',').map((x: string) => `http://${x}`))
} else {
ctx.body = await Quantel.getISAReference(`http://${ctx.params.addr}`)
}
} catch (err) {
if (err.message.indexOf('ENOTFOUND') >= 0) {
err.status = 404
Expand Down

0 comments on commit 0353c37

Please sign in to comment.