Skip to content

Commit b69478f

Browse files
authored
Merge pull request #1169 from bbc/feat/mos-openmedia-hot-standby
feat(MOS): support OpenMedia's hot standby
2 parents 29c0800 + 685862b commit b69478f

File tree

5 files changed

+100
-29
lines changed

5 files changed

+100
-29
lines changed

packages/mos-gateway/src/$schemas/devices.json

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,20 @@
6161
"ui:title": "(Optional) MOS Query Port",
6262
"ui:description": "Connect to an alternate port for 'query' port MOS messages",
6363
"default": 10542
64-
}
64+
}
6565
},
66-
"required": ["lower", "upper", "query"],
66+
"required": [
67+
"lower",
68+
"upper",
69+
"query"
70+
],
6771
"additionalProperties": false
6872
}
6973
},
70-
"required": ["id", "host"],
74+
"required": [
75+
"id",
76+
"host"
77+
],
7178
"additionalProperties": false
7279
},
7380
"secondary": {
@@ -105,6 +112,12 @@
105112
"ui:description": "How often to ping NRCS to determine connection status",
106113
"default": 30000
107114
},
115+
"openMediaHotStandby": {
116+
"type": "boolean",
117+
"ui:title": "Secondary: OpenMedia Hot Standby",
118+
"ui:description": "Is the secondary connection a OpenMedia hot standby for the primary",
119+
"default": false
120+
},
108121
"ports": {
109122
"type": "object",
110123
"ui:title": "Ports",
@@ -126,16 +139,25 @@
126139
"ui:title": "(Optional) MOS Query Port",
127140
"ui:description": "Connect to an alternate port for 'query' port MOS messages",
128141
"default": 10542
129-
}
142+
}
130143
},
131-
"required": ["lower", "upper", "query"],
144+
"required": [
145+
"lower",
146+
"upper",
147+
"query"
148+
],
132149
"additionalProperties": false
133-
}
150+
}
134151
},
135-
"required": ["id", "host"],
152+
"required": [
153+
"id",
154+
"host"
155+
],
136156
"additionalProperties": false
137157
}
138158
},
139-
"required": ["primary"],
159+
"required": [
160+
"primary"
161+
],
140162
"additionalProperties": false
141-
}
163+
}

packages/mos-gateway/src/CoreMosDeviceHandler.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ interface IStoryItemChange {
6262
itemDiff: PartialDeep<IMOSItem>
6363
}
6464

65+
export interface CoreMosDeviceHandlerOptions {
66+
openMediaHotStandby?: boolean
67+
}
68+
6569
/**
6670
* Represents a connection between a mos-device and Core
6771
*/
@@ -76,13 +80,20 @@ export class CoreMosDeviceHandler {
7680
private _pendingStoryItemChanges: Array<IStoryItemChange> = []
7781
private _pendingChangeTimeout: number = 60 * 1000
7882
private mosTypes: MosTypes
83+
private _options: CoreMosDeviceHandlerOptions
7984

8085
private _messageQueue: Queue
8186

82-
constructor(parent: CoreHandler, mosDevice: IMOSDevice, mosHandler: MosHandler) {
87+
constructor(
88+
parent: CoreHandler,
89+
mosDevice: IMOSDevice,
90+
mosHandler: MosHandler,
91+
options: CoreMosDeviceHandlerOptions
92+
) {
8393
this._coreParentHandler = parent
8494
this._mosDevice = mosDevice
8595
this._mosHandler = mosHandler
96+
this._options = options
8697

8798
this._messageQueue = new Queue()
8899

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

142-
if (connectionStatus.PrimaryConnected) {
143-
if (connectionStatus.SecondaryConnected || !this._mosDevice.idSecondary) {
153+
if (this._options.openMediaHotStandby) {
154+
// OpenMedia treats secondary server as hot-standby
155+
// And thus is not considered as a warning if it's not connected
156+
if (connectionStatus.PrimaryConnected) {
144157
statusCode = StatusCode.GOOD
145158
} else {
146-
statusCode = StatusCode.WARNING_MINOR
159+
// Primary not connected is only bad if there is no secondary:
160+
if (connectionStatus.SecondaryConnected) {
161+
statusCode = StatusCode.GOOD
162+
messages.push(connectionStatus.SecondaryStatus || 'Running NRCS on hot standby')
163+
} else {
164+
statusCode = StatusCode.BAD
165+
// Send messages for both connections
166+
messages.push(connectionStatus.PrimaryStatus || 'Primary and hot standby are not connected')
167+
messages.push(connectionStatus.SecondaryStatus || 'Primary and hot standby are not connected')
168+
}
147169
}
148170
} else {
149-
if (connectionStatus.SecondaryConnected) {
150-
statusCode = StatusCode.WARNING_MAJOR
171+
if (connectionStatus.PrimaryConnected) {
172+
// ENPS expect both Primary and Secondary to be connected if both of them are configured
173+
if (connectionStatus.SecondaryConnected || !this._mosDevice.idSecondary) {
174+
statusCode = StatusCode.GOOD
175+
} else {
176+
statusCode = StatusCode.WARNING_MINOR
177+
}
151178
} else {
152-
statusCode = StatusCode.BAD
179+
if (connectionStatus.SecondaryConnected) {
180+
// Primary not connected should give a warning if Secondary is used.
181+
statusCode = StatusCode.WARNING_MAJOR
182+
} else {
183+
// If neither Primary nor Secondary is connected, it's a bad state.
184+
statusCode = StatusCode.BAD
185+
}
153186
}
154-
}
155187

156-
if (!connectionStatus.PrimaryConnected) {
157-
messages.push(connectionStatus.PrimaryStatus || 'Primary not connected')
158-
}
159-
if (this._mosDevice.idSecondary && !connectionStatus.SecondaryConnected) {
160-
messages.push(connectionStatus.SecondaryStatus || 'Fallback not connected')
188+
if (!connectionStatus.PrimaryConnected) {
189+
messages.push(connectionStatus.PrimaryStatus || 'Primary not connected')
190+
}
191+
if (this._mosDevice.idSecondary && !connectionStatus.SecondaryConnected) {
192+
messages.push(connectionStatus.SecondaryStatus || 'Fallback not connected')
193+
}
161194
}
162195

163196
this.core

packages/mos-gateway/src/coreHandler.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { MosHandler } from './mosHandler'
1818
import { DeviceConfig } from './connector'
1919
import { MOS_DEVICE_CONFIG_MANIFEST } from './configManifest'
2020
import { getVersions } from './versions'
21-
import { CoreMosDeviceHandler } from './CoreMosDeviceHandler'
21+
import { CoreMosDeviceHandler, CoreMosDeviceHandlerOptions } from './CoreMosDeviceHandler'
2222
import { PeripheralDeviceCommandId } from '@sofie-automation/shared-lib/dist/core/model/Ids'
2323

2424
export interface CoreConfig {
@@ -142,9 +142,13 @@ export class CoreHandler {
142142

143143
return options
144144
}
145-
async registerMosDevice(mosDevice: IMOSDevice, mosHandler: MosHandler): Promise<CoreMosDeviceHandler> {
145+
async registerMosDevice(
146+
mosDevice: IMOSDevice,
147+
mosHandler: MosHandler,
148+
deviceOptions: CoreMosDeviceHandlerOptions
149+
): Promise<CoreMosDeviceHandler> {
146150
this.logger.info('registerMosDevice -------------')
147-
const coreMos = new CoreMosDeviceHandler(this, mosDevice, mosHandler)
151+
const coreMos = new CoreMosDeviceHandler(this, mosDevice, mosHandler, deviceOptions)
148152

149153
this._coreMosHandlers.push(coreMos)
150154
return coreMos.init().then(() => {

packages/mos-gateway/src/generated/devices.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface MosDeviceConfig {
2424
dontUseQueryPort?: boolean
2525
timeout?: number
2626
heartbeatInterval?: number
27+
openMediaHotStandby?: boolean
2728
ports?: {
2829
lower: number
2930
upper: number

packages/mos-gateway/src/mosHandler.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,15 @@ export class MosHandler {
5959
private _logger: Winston.Logger
6060
private _disposed = false
6161
private _settings?: MosGatewayConfig
62+
private _openMediaHotStandby: Record<string, boolean>
6263
private _coreHandler: CoreHandler | undefined
6364
private _observers: Array<Observer<any>> = []
6465
private _triggerupdateDevicesTimeout: any = null
6566
private mosTypes: MosTypes
6667

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

102104
this.mosTypes = getMosTypes(this.strict)
103105

104-
await this._initMosConnection()
106+
await this._updateDevices()
105107

106108
if (!this._coreHandler) throw Error('_coreHandler is undefined!')
107109
this._coreHandler.onConnected(() => {
@@ -110,8 +112,6 @@ export class MosHandler {
110112
this.sendStatusOfAllMosDevices()
111113
})
112114
this.setupObservers()
113-
114-
return this._updateDevices()
115115
}
116116
async dispose(): Promise<void> {
117117
this._disposed = true
@@ -243,7 +243,11 @@ export class MosHandler {
243243

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

246-
const coreMosHandler = await this._coreHandler.registerMosDevice(mosDevice, this)
246+
const coreMosHandler = await this._coreHandler.registerMosDevice(mosDevice, this, {
247+
openMediaHotStandby: mosDevice.idSecondary
248+
? this._openMediaHotStandby[mosDevice.idSecondary]
249+
: false,
250+
})
247251
// this._logger.info('mosDevice registered -------------')
248252
// Setup message flow between the devices:
249253

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

491+
if (deviceOptions.secondary?.id && this._openMediaHotStandby[deviceOptions.secondary.id]) {
492+
//@ts-expect-error this is not yet added to the official mos-connection
493+
deviceOptions.secondary.openMediaHotStandby = true
494+
}
495+
485496
const mosDevice: MosDevice = await this.mos.connect(deviceOptions)
486497
this._ownMosDevices[deviceId] = mosDevice
487498

0 commit comments

Comments
 (0)