diff --git a/docs/guide/cluster.md b/docs/guide/cluster.md index 8f02135..ce95ce2 100644 --- a/docs/guide/cluster.md +++ b/docs/guide/cluster.md @@ -4,19 +4,31 @@ If you are running multiple room-assistant instances they come together in a cluster. By default, the instances discover each other using mDNS. This requires all of them live in the same subnet. You can also specify the adresses of other instances and tweak some other things manually. -## Preferred leaders +## Usage tips + +### Preferred leaders In some situations you may want some instances of room-assistant to be the leading ones more than others. This could for example be the case if you have instances that are connected to the network better than others. To accomplish this you can configure custom weights that are then used during the leader election process. The `weight` option should ideally be configured on every instance of room-assistant that you run to achieve consistent behavior. The preferred leader instances need to have the largest weight numbers. +### Some nodes not appearing + +If the auto discovery does not seem to pick up all (or any) nodes for your cluster you can approach the problem from two different directions: + +1. Make sure your router and network settings are compatible with mDNS/Bonjour. An easy way to test this is pinging your devices by their hostname, e.g. `ping raspberrypi.local`. +2. Disable `autoDiscovery` and provide the `peerAddresses` manually. Make sure to give your devices DHCP reservations or static IPs. + +Should the other nodes not appear in your cluster even after configuring it manually you should make sure that the devices can communication with each other via UDP on the port you configured, by default `6425`. + ## Settings -| Name | Type | Default | Description | -| ------------------ | ------ | ------- | ------------------------------------------------------------ | -| `networkInterface` | String | | The specific network interface that room-assistant should advertise its presence on, e.g. `eth0`. | -| `port` | Number | `6425` | The UDP port that room-assistant should use for internal communication. | -| `timeout` | Number | `60` | Number of seconds that an instance can go without sending a heartbeat and not be dropped from the cluster. | -| `weight` | Number | Random | Value used to sort when electing a leading instance. The instance with the highest weight in the cluster becomes the leader. | -| `peerAddresses` | Array | | A list of endpoint addresses (IP + port) of other room-assistant instances. | +| Name | Type | Default | Description | +| ------------------ | ------- | ------- | ------------------------------------------------------------ | +| `networkInterface` | String | | The specific network interface that room-assistant should advertise its presence on, e.g. `eth0`. | +| `port` | Number | `6425` | The UDP port that room-assistant should use for internal communication. | +| `timeout` | Number | `60` | Number of seconds that an instance can go without sending a heartbeat and not be dropped from the cluster. | +| `weight` | Number | Random | Value used to sort when electing a leading instance. The instance with the highest weight in the cluster becomes the leader. | +| `autoDiscovery` | Boolean | `true` | Allows mDNS-based auto discovery of other room-assistant instances to be turned off. | +| `peerAddresses` | Array | | A list of endpoint addresses (IP + port) of other room-assistant instances. | ::: details Example Config diff --git a/src/cluster/cluster.config.ts b/src/cluster/cluster.config.ts index 977bcbe..31477d4 100644 --- a/src/cluster/cluster.config.ts +++ b/src/cluster/cluster.config.ts @@ -3,5 +3,6 @@ export class ClusterConfig { port = 6425; timeout = 60; weight?: number; + autoDiscovery = true; peerAddresses: string[] = []; } diff --git a/src/cluster/cluster.service.spec.ts b/src/cluster/cluster.service.spec.ts index 110f7c7..2c1032e 100644 --- a/src/cluster/cluster.service.spec.ts +++ b/src/cluster/cluster.service.spec.ts @@ -26,6 +26,9 @@ import Democracy from 'democracy'; import { Test, TestingModule } from '@nestjs/testing'; import { ClusterService } from './cluster.service'; import { ConfigModule } from '../config/config.module'; +import { ClusterConfig } from './cluster.config'; +import { ConfigService } from '../config/config.service'; +import c from 'config'; jest.mock('os'); jest.mock('democracy'); @@ -33,6 +36,12 @@ jest.mock('mdns', () => mockMdns, { virtual: true }); describe('ClusterService', () => { let service: ClusterService; + const mockConfig = new ClusterConfig(); + const configService = { + get: jest.fn().mockImplementation((key: string) => { + return key === 'cluster' ? mockConfig : c.get(key); + }) + }; beforeAll(async () => { (networkInterfaces as jest.Mock).mockReturnValue({ @@ -59,10 +68,15 @@ describe('ClusterService', () => { }); beforeEach(async () => { + jest.clearAllMocks(); + const module: TestingModule = await Test.createTestingModule({ imports: [ConfigModule], providers: [ClusterService] - }).compile(); + }) + .overrideProvider(ConfigService) + .useValue(configService) + .compile(); service = module.get(ClusterService); }); @@ -91,4 +105,12 @@ describe('ClusterService', () => { expect(mockBrowser.start).toHaveBeenCalled(); expect(mockAdvertisement.start).toHaveBeenCalled(); }); + + it('should not advertise the instance if auto discovery has been turned off', () => { + mockConfig.autoDiscovery = false; + + service.onApplicationBootstrap(); + expect(mockMdns.createAdvertisement).not.toHaveBeenCalled(); + expect(mockMdns.createBrowser).not.toHaveBeenCalled(); + }); }); diff --git a/src/cluster/cluster.service.ts b/src/cluster/cluster.service.ts index 6c1f5c3..563934e 100644 --- a/src/cluster/cluster.service.ts +++ b/src/cluster/cluster.service.ts @@ -55,12 +55,14 @@ export class ClusterService extends Democracy } onApplicationBootstrap(): any { - if (mdns !== undefined) { - this.startBonjourDiscovery(); - } else { - this.logger.warn( - 'Dependency "mdns" was not found, automatic discovery has been disabled. You will have to provide the addresses of other room-assistant nodes manually in the config.' - ); + if (this.config.autoDiscovery) { + if (mdns !== undefined) { + this.startBonjourDiscovery(); + } else { + this.logger.warn( + 'Dependency "mdns" was not found, automatic discovery has been disabled. You will have to provide the addresses of other room-assistant nodes manually in the config.' + ); + } } this.on('added', this.handleNodeAdded); @@ -96,6 +98,7 @@ export class ClusterService extends Democracy this.logger.error(e.message, e.trace); }); + this.logger.log('Starting mDNS advertisements and discovery'); this.advertisement.start(); this.browser.start(); }