Skip to content

Commit

Permalink
Merge pull request #1169 from bbc/feat/mos-openmedia-hot-standby
Browse files Browse the repository at this point in the history
feat(MOS): support OpenMedia's hot standby
  • Loading branch information
jstarpl authored Dec 11, 2024
2 parents 29c0800 + 685862b commit b69478f
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 29 deletions.
40 changes: 31 additions & 9 deletions packages/mos-gateway/src/$schemas/devices.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,20 @@
"ui:title": "(Optional) MOS Query Port",
"ui:description": "Connect to an alternate port for 'query' port MOS messages",
"default": 10542
}
}
},
"required": ["lower", "upper", "query"],
"required": [
"lower",
"upper",
"query"
],
"additionalProperties": false
}
},
"required": ["id", "host"],
"required": [
"id",
"host"
],
"additionalProperties": false
},
"secondary": {
Expand Down Expand Up @@ -105,6 +112,12 @@
"ui:description": "How often to ping NRCS to determine connection status",
"default": 30000
},
"openMediaHotStandby": {
"type": "boolean",
"ui:title": "Secondary: OpenMedia Hot Standby",
"ui:description": "Is the secondary connection a OpenMedia hot standby for the primary",
"default": false
},
"ports": {
"type": "object",
"ui:title": "Ports",
Expand All @@ -126,16 +139,25 @@
"ui:title": "(Optional) MOS Query Port",
"ui:description": "Connect to an alternate port for 'query' port MOS messages",
"default": 10542
}
}
},
"required": ["lower", "upper", "query"],
"required": [
"lower",
"upper",
"query"
],
"additionalProperties": false
}
}
},
"required": ["id", "host"],
"required": [
"id",
"host"
],
"additionalProperties": false
}
},
"required": ["primary"],
"required": [
"primary"
],
"additionalProperties": false
}
}
59 changes: 46 additions & 13 deletions packages/mos-gateway/src/CoreMosDeviceHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ interface IStoryItemChange {
itemDiff: PartialDeep<IMOSItem>
}

export interface CoreMosDeviceHandlerOptions {
openMediaHotStandby?: boolean
}

/**
* Represents a connection between a mos-device and Core
*/
Expand All @@ -76,13 +80,20 @@ export class CoreMosDeviceHandler {
private _pendingStoryItemChanges: Array<IStoryItemChange> = []
private _pendingChangeTimeout: number = 60 * 1000
private mosTypes: MosTypes
private _options: CoreMosDeviceHandlerOptions

private _messageQueue: Queue

constructor(parent: CoreHandler, mosDevice: IMOSDevice, mosHandler: MosHandler) {
constructor(
parent: CoreHandler,
mosDevice: IMOSDevice,
mosHandler: MosHandler,
options: CoreMosDeviceHandlerOptions
) {
this._coreParentHandler = parent
this._mosDevice = mosDevice
this._mosHandler = mosHandler
this._options = options

this._messageQueue = new Queue()

Expand Down Expand Up @@ -139,25 +150,47 @@ export class CoreMosDeviceHandler {
let statusCode: StatusCode
const messages: Array<string> = []

if (connectionStatus.PrimaryConnected) {
if (connectionStatus.SecondaryConnected || !this._mosDevice.idSecondary) {
if (this._options.openMediaHotStandby) {
// OpenMedia treats secondary server as hot-standby
// And thus is not considered as a warning if it's not connected
if (connectionStatus.PrimaryConnected) {
statusCode = StatusCode.GOOD
} else {
statusCode = StatusCode.WARNING_MINOR
// Primary not connected is only bad if there is no secondary:
if (connectionStatus.SecondaryConnected) {
statusCode = StatusCode.GOOD
messages.push(connectionStatus.SecondaryStatus || 'Running NRCS on hot standby')
} else {
statusCode = StatusCode.BAD
// Send messages for both connections
messages.push(connectionStatus.PrimaryStatus || 'Primary and hot standby are not connected')
messages.push(connectionStatus.SecondaryStatus || 'Primary and hot standby are not connected')
}
}
} else {
if (connectionStatus.SecondaryConnected) {
statusCode = StatusCode.WARNING_MAJOR
if (connectionStatus.PrimaryConnected) {
// ENPS expect both Primary and Secondary to be connected if both of them are configured
if (connectionStatus.SecondaryConnected || !this._mosDevice.idSecondary) {
statusCode = StatusCode.GOOD
} else {
statusCode = StatusCode.WARNING_MINOR
}
} else {
statusCode = StatusCode.BAD
if (connectionStatus.SecondaryConnected) {
// Primary not connected should give a warning if Secondary is used.
statusCode = StatusCode.WARNING_MAJOR
} else {
// If neither Primary nor Secondary is connected, it's a bad state.
statusCode = StatusCode.BAD
}
}
}

if (!connectionStatus.PrimaryConnected) {
messages.push(connectionStatus.PrimaryStatus || 'Primary not connected')
}
if (this._mosDevice.idSecondary && !connectionStatus.SecondaryConnected) {
messages.push(connectionStatus.SecondaryStatus || 'Fallback not connected')
if (!connectionStatus.PrimaryConnected) {
messages.push(connectionStatus.PrimaryStatus || 'Primary not connected')
}
if (this._mosDevice.idSecondary && !connectionStatus.SecondaryConnected) {
messages.push(connectionStatus.SecondaryStatus || 'Fallback not connected')
}
}

this.core
Expand Down
10 changes: 7 additions & 3 deletions packages/mos-gateway/src/coreHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { MosHandler } from './mosHandler'
import { DeviceConfig } from './connector'
import { MOS_DEVICE_CONFIG_MANIFEST } from './configManifest'
import { getVersions } from './versions'
import { CoreMosDeviceHandler } from './CoreMosDeviceHandler'
import { CoreMosDeviceHandler, CoreMosDeviceHandlerOptions } from './CoreMosDeviceHandler'
import { PeripheralDeviceCommandId } from '@sofie-automation/shared-lib/dist/core/model/Ids'

export interface CoreConfig {
Expand Down Expand Up @@ -142,9 +142,13 @@ export class CoreHandler {

return options
}
async registerMosDevice(mosDevice: IMOSDevice, mosHandler: MosHandler): Promise<CoreMosDeviceHandler> {
async registerMosDevice(
mosDevice: IMOSDevice,
mosHandler: MosHandler,
deviceOptions: CoreMosDeviceHandlerOptions
): Promise<CoreMosDeviceHandler> {
this.logger.info('registerMosDevice -------------')
const coreMos = new CoreMosDeviceHandler(this, mosDevice, mosHandler)
const coreMos = new CoreMosDeviceHandler(this, mosDevice, mosHandler, deviceOptions)

this._coreMosHandlers.push(coreMos)
return coreMos.init().then(() => {
Expand Down
1 change: 1 addition & 0 deletions packages/mos-gateway/src/generated/devices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface MosDeviceConfig {
dontUseQueryPort?: boolean
timeout?: number
heartbeatInterval?: number
openMediaHotStandby?: boolean
ports?: {
lower: number
upper: number
Expand Down
19 changes: 15 additions & 4 deletions packages/mos-gateway/src/mosHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ export class MosHandler {
private _logger: Winston.Logger
private _disposed = false
private _settings?: MosGatewayConfig
private _openMediaHotStandby: Record<string, boolean>
private _coreHandler: CoreHandler | undefined
private _observers: Array<Observer<any>> = []
private _triggerupdateDevicesTimeout: any = null
private mosTypes: MosTypes

constructor(logger: Winston.Logger) {
this._logger = logger
this._openMediaHotStandby = {}
this.mosTypes = getMosTypes(this.strict) // temporary, another will be set upon init()
}
async init(config: MosConfig, coreHandler: CoreHandler): Promise<void> {
Expand Down Expand Up @@ -101,7 +103,7 @@ export class MosHandler {

this.mosTypes = getMosTypes(this.strict)

await this._initMosConnection()
await this._updateDevices()

if (!this._coreHandler) throw Error('_coreHandler is undefined!')
this._coreHandler.onConnected(() => {
Expand All @@ -110,8 +112,6 @@ export class MosHandler {
this.sendStatusOfAllMosDevices()
})
this.setupObservers()

return this._updateDevices()
}
async dispose(): Promise<void> {
this._disposed = true
Expand Down Expand Up @@ -243,7 +243,11 @@ export class MosHandler {

if (!this._coreHandler) throw Error('_coreHandler is undefined!')

const coreMosHandler = await this._coreHandler.registerMosDevice(mosDevice, this)
const coreMosHandler = await this._coreHandler.registerMosDevice(mosDevice, this, {
openMediaHotStandby: mosDevice.idSecondary
? this._openMediaHotStandby[mosDevice.idSecondary]
: false,
})
// this._logger.info('mosDevice registered -------------')
// Setup message flow between the devices:

Expand Down Expand Up @@ -420,6 +424,8 @@ export class MosHandler {
for (const [deviceId, device] of Object.entries<{ options: MosDeviceConfig }>(devices)) {
if (device) {
if (device.options.secondary) {
this._openMediaHotStandby[device.options.secondary.id] =
device.options.secondary?.openMediaHotStandby || false
// If the host isn't set, don't use secondary:
if (!device.options.secondary.host || !device.options.secondary.id)
delete device.options.secondary
Expand Down Expand Up @@ -482,6 +488,11 @@ export class MosHandler {
deviceOptions.primary.heartbeatInterval =
deviceOptions.primary.heartbeatInterval || DEFAULT_MOS_HEARTBEAT_INTERVAL

if (deviceOptions.secondary?.id && this._openMediaHotStandby[deviceOptions.secondary.id]) {
//@ts-expect-error this is not yet added to the official mos-connection
deviceOptions.secondary.openMediaHotStandby = true
}

const mosDevice: MosDevice = await this.mos.connect(deviceOptions)
this._ownMosDevices[deviceId] = mosDevice

Expand Down

0 comments on commit b69478f

Please sign in to comment.