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

Open
wants to merge 7 commits into
base: release51
Choose a base branch
from
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
}
}
50 changes: 37 additions & 13 deletions packages/mos-gateway/src/CoreMosDeviceHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@ export class CoreMosDeviceHandler {
private _pendingStoryItemChanges: Array<IStoryItemChange> = []
private _pendingChangeTimeout: number = 60 * 1000
private mosTypes: MosTypes
private _openMediaHotStandby: boolean

private _messageQueue: Queue

constructor(parent: CoreHandler, mosDevice: IMOSDevice, mosHandler: MosHandler) {
constructor(parent: CoreHandler, mosDevice: IMOSDevice, mosHandler: MosHandler, openMediaHotStandby: boolean) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if this should be put into an options object? Carrying these opaque flags enabling out-of-spec behavior seems like something that will get problematic very quickly. What about?

Suggested change
constructor(parent: CoreHandler, mosDevice: IMOSDevice, mosHandler: MosHandler, openMediaHotStandby: boolean) {
constructor(parent: CoreHandler, mosDevice: IMOSDevice, mosHandler: MosHandler, opts?: { openMediaHotStandby? : boolean }) {

this._coreParentHandler = parent
this._mosDevice = mosDevice
this._mosHandler = mosHandler
this._openMediaHotStandby = openMediaHotStandby

this._messageQueue = new Queue()

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

if (connectionStatus.PrimaryConnected) {
if (connectionStatus.SecondaryConnected || !this._mosDevice.idSecondary) {
if (this._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
8 changes: 6 additions & 2 deletions packages/mos-gateway/src/coreHandler.ts
Original file line number Diff line number Diff line change
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,
openMediaHotStandby: boolean
Copy link
Member

Choose a reason for hiding this comment

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

Same as above - I understand that these additional options are necessary to pass around, but maybe we could group these flags into some sort of an options object where their meaning will be available, and not as just boolean arguments into functions?

Suggested change
openMediaHotStandby: boolean
opts?: { openMediaHotStandby?: boolean }

): Promise<CoreMosDeviceHandler> {
this.logger.info('registerMosDevice -------------')
const coreMos = new CoreMosDeviceHandler(this, mosDevice, mosHandler)
const coreMos = new CoreMosDeviceHandler(this, mosDevice, mosHandler, openMediaHotStandby)

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,
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?

}

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

Expand Down