Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(MOS): support OpenMedia's hot standby #1169

Merged
merged 9 commits into from
Dec 11, 2024
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that this PR is not mergable yet? Because there needs to be an update to mos-connection before?

Copy link
Member

@Julusian Julusian Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it appears so.
The PR nrkno/sofie-mos-connection#105 has been merged in that repository, but v4.2.0 has only been half created, so cannot be referenced here yet.
It sounds like there are some more changes waiting in mos-connection

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the PR, but there are some additional fixes without a PR yet

}

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

Expand Down
Loading