• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nats-io / nats-server / 20326380621

17 Dec 2025 03:32PM UTC coverage: 84.522% (-0.05%) from 84.574%
20326380621

push

github

web-flow
NRG: Fix single node election (#7642)

This commit fixes single node election: previously, a single node would
simply store its vote, and never check if it already reached a majority.
So it would never transition to the leader state.

Signed-off-by: Daniele Sciascia <daniele@nats.io>

73985 of 87533 relevant lines covered (84.52%)

339454.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.98
/server/reload.go
1
// Copyright 2017-2025 The NATS Authors
2
// Licensed under the Apache License, Version 2.0 (the "License");
3
// you may not use this file except in compliance with the License.
4
// You may obtain a copy of the License at
5
//
6
// http://www.apache.org/licenses/LICENSE-2.0
7
//
8
// Unless required by applicable law or agreed to in writing, software
9
// distributed under the License is distributed on an "AS IS" BASIS,
10
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
// See the License for the specific language governing permissions and
12
// limitations under the License.
13

14
package server
15

16
import (
17
        "cmp"
18
        "crypto/tls"
19
        "errors"
20
        "fmt"
21
        "net/url"
22
        "reflect"
23
        "slices"
24
        "strings"
25
        "sync/atomic"
26
        "time"
27

28
        "github.com/klauspost/compress/s2"
29

30
        "github.com/nats-io/jwt/v2"
31
        "github.com/nats-io/nuid"
32
)
33

34
// FlagSnapshot captures the server options as specified by CLI flags at
35
// startup. This should not be modified once the server has started.
36
var FlagSnapshot *Options
37

38
type reloadContext struct {
39
        oldClusterPerms *RoutePermissions
40
}
41

42
// option is a hot-swappable configuration setting.
43
type option interface {
44
        // Apply the server option.
45
        Apply(server *Server)
46

47
        // IsLoggingChange indicates if this option requires reloading the logger.
48
        IsLoggingChange() bool
49

50
        // IsTraceLevelChange indicates if this option requires reloading cached trace level.
51
        // Clients store trace level separately.
52
        IsTraceLevelChange() bool
53

54
        // IsAuthChange indicates if this option requires reloading authorization.
55
        IsAuthChange() bool
56

57
        // IsTLSChange indicates if this option requires reloading TLS.
58
        IsTLSChange() bool
59

60
        // IsClusterPermsChange indicates if this option requires reloading
61
        // cluster permissions.
62
        IsClusterPermsChange() bool
63

64
        // IsClusterPoolSizeOrAccountsChange indicates if this option requires
65
        // special handling for changes in cluster's pool size or accounts list.
66
        IsClusterPoolSizeOrAccountsChange() bool
67

68
        // IsJetStreamChange inidicates a change in the servers config for JetStream.
69
        // Account changes will be handled separately in reloadAuthorization.
70
        IsJetStreamChange() bool
71

72
        // Indicates a change in the server that requires publishing the server's statz
73
        IsStatszChange() bool
74
}
75

76
// noopOption is a base struct that provides default no-op behaviors.
77
type noopOption struct{}
78

79
func (n noopOption) IsLoggingChange() bool {
3,087✔
80
        return false
3,087✔
81
}
3,087✔
82

83
func (n noopOption) IsTraceLevelChange() bool {
3,099✔
84
        return false
3,099✔
85
}
3,099✔
86

87
func (n noopOption) IsAuthChange() bool {
216✔
88
        return false
216✔
89
}
216✔
90

91
func (n noopOption) IsTLSChange() bool {
×
92
        return false
×
93
}
×
94

95
func (n noopOption) IsClusterPermsChange() bool {
2,927✔
96
        return false
2,927✔
97
}
2,927✔
98

99
func (n noopOption) IsClusterPoolSizeOrAccountsChange() bool {
2,927✔
100
        return false
2,927✔
101
}
2,927✔
102

103
func (n noopOption) IsJetStreamChange() bool {
3,101✔
104
        return false
3,101✔
105
}
3,101✔
106

107
func (n noopOption) IsStatszChange() bool {
3,099✔
108
        return false
3,099✔
109
}
3,099✔
110

111
// loggingOption is a base struct that provides default option behaviors for
112
// logging-related options.
113
type loggingOption struct {
114
        noopOption
115
}
116

117
func (l loggingOption) IsLoggingChange() bool {
17✔
118
        return true
17✔
119
}
17✔
120

121
// traceLevelOption is a base struct that provides default option behaviors for
122
// tracelevel-related options.
123
type traceLevelOption struct {
124
        loggingOption
125
}
126

127
func (l traceLevelOption) IsTraceLevelChange() bool {
5✔
128
        return true
5✔
129
}
5✔
130

131
// traceOption implements the option interface for the `trace` setting.
132
type traceOption struct {
133
        traceLevelOption
134
        newValue bool
135
}
136

137
// Apply is a no-op because logging will be reloaded after options are applied.
138
func (t *traceOption) Apply(server *Server) {
3✔
139
        server.Noticef("Reloaded: trace = %v", t.newValue)
3✔
140
}
3✔
141

142
// traceVersboseOption implements the option interface for the `trace_verbose` setting.
143
type traceVerboseOption struct {
144
        traceLevelOption
145
        newValue bool
146
}
147

148
// Apply is a no-op because logging will be reloaded after options are applied.
149
func (t *traceVerboseOption) Apply(server *Server) {
2✔
150
        server.Noticef("Reloaded: trace_verbose = %v", t.newValue)
2✔
151
}
2✔
152

153
// traceHeadersOption implements the option interface for the `trace_headers` setting.
154
type traceHeadersOption struct {
155
        traceLevelOption
156
        newValue bool
157
}
158

159
// Apply is a no-op because logging will be reloaded after options are applied.
160
func (t *traceHeadersOption) Apply(server *Server) {
×
161
        server.Noticef("Reloaded: trace_headers = %v", t.newValue)
×
162
}
×
163

164
// debugOption implements the option interface for the `debug` setting.
165
type debugOption struct {
166
        loggingOption
167
        newValue bool
168
}
169

170
// Apply is mostly a no-op because logging will be reloaded after options are applied.
171
// However we will kick the raft nodes if they exist to reload.
172
func (d *debugOption) Apply(server *Server) {
3✔
173
        server.Noticef("Reloaded: debug = %v", d.newValue)
3✔
174
        server.reloadDebugRaftNodes(d.newValue)
3✔
175
}
3✔
176

177
// logtimeOption implements the option interface for the `logtime` setting.
178
type logtimeOption struct {
179
        loggingOption
180
        newValue bool
181
}
182

183
// Apply is a no-op because logging will be reloaded after options are applied.
184
func (l *logtimeOption) Apply(server *Server) {
1✔
185
        server.Noticef("Reloaded: logtime = %v", l.newValue)
1✔
186
}
1✔
187

188
// logtimeUTCOption implements the option interface for the `logtime_utc` setting.
189
type logtimeUTCOption struct {
190
        loggingOption
191
        newValue bool
192
}
193

194
// Apply is a no-op because logging will be reloaded after options are applied.
195
func (l *logtimeUTCOption) Apply(server *Server) {
1✔
196
        server.Noticef("Reloaded: logtime_utc = %v", l.newValue)
1✔
197
}
1✔
198

199
// logfileOption implements the option interface for the `log_file` setting.
200
type logfileOption struct {
201
        loggingOption
202
        newValue string
203
}
204

205
// Apply is a no-op because logging will be reloaded after options are applied.
206
func (l *logfileOption) Apply(server *Server) {
5✔
207
        server.Noticef("Reloaded: log_file = %v", l.newValue)
5✔
208
}
5✔
209

210
// syslogOption implements the option interface for the `syslog` setting.
211
type syslogOption struct {
212
        loggingOption
213
        newValue bool
214
}
215

216
// Apply is a no-op because logging will be reloaded after options are applied.
217
func (s *syslogOption) Apply(server *Server) {
1✔
218
        server.Noticef("Reloaded: syslog = %v", s.newValue)
1✔
219
}
1✔
220

221
// remoteSyslogOption implements the option interface for the `remote_syslog`
222
// setting.
223
type remoteSyslogOption struct {
224
        loggingOption
225
        newValue string
226
}
227

228
// Apply is a no-op because logging will be reloaded after options are applied.
229
func (r *remoteSyslogOption) Apply(server *Server) {
1✔
230
        server.Noticef("Reloaded: remote_syslog = %v", r.newValue)
1✔
231
}
1✔
232

233
// tlsOption implements the option interface for the `tls` setting.
234
type tlsOption struct {
235
        noopOption
236
        newValue *tls.Config
237
}
238

239
// Apply the tls change.
240
func (t *tlsOption) Apply(server *Server) {
24✔
241
        server.mu.Lock()
24✔
242
        tlsRequired := t.newValue != nil
24✔
243
        server.info.TLSRequired = tlsRequired && !server.getOpts().AllowNonTLS
24✔
244
        message := "disabled"
24✔
245
        if tlsRequired {
47✔
246
                server.info.TLSVerify = (t.newValue.ClientAuth == tls.RequireAndVerifyClientCert)
23✔
247
                message = "enabled"
23✔
248
        }
23✔
249
        server.mu.Unlock()
24✔
250
        server.Noticef("Reloaded: tls = %s", message)
24✔
251
}
252

253
func (t *tlsOption) IsTLSChange() bool {
×
254
        return true
×
255
}
×
256

257
// tlsTimeoutOption implements the option interface for the tls `timeout`
258
// setting.
259
type tlsTimeoutOption struct {
260
        noopOption
261
        newValue float64
262
}
263

264
// Apply is a no-op because the timeout will be reloaded after options are
265
// applied.
266
func (t *tlsTimeoutOption) Apply(server *Server) {
×
267
        server.Noticef("Reloaded: tls timeout = %v", t.newValue)
×
268
}
×
269

270
// tlsPinnedCertOption implements the option interface for the tls `pinned_certs` setting.
271
type tlsPinnedCertOption struct {
272
        noopOption
273
        newValue PinnedCertSet
274
}
275

276
// Apply is a no-op because the pinned certs will be reloaded after options are  applied.
277
func (t *tlsPinnedCertOption) Apply(server *Server) {
1✔
278
        server.Noticef("Reloaded: %d pinned_certs", len(t.newValue))
1✔
279
}
1✔
280

281
// tlsHandshakeFirst implements the option interface for the tls `handshake first` setting.
282
type tlsHandshakeFirst struct {
283
        noopOption
284
        newValue bool
285
}
286

287
// Apply is a no-op because the timeout will be reloaded after options are applied.
288
func (t *tlsHandshakeFirst) Apply(server *Server) {
2✔
289
        server.Noticef("Reloaded: Client TLS handshake first: %v", t.newValue)
2✔
290
}
2✔
291

292
// tlsHandshakeFirstFallback implements the option interface for the tls `handshake first fallback delay` setting.
293
type tlsHandshakeFirstFallback struct {
294
        noopOption
295
        newValue time.Duration
296
}
297

298
// Apply is a no-op because the timeout will be reloaded after options are applied.
299
func (t *tlsHandshakeFirstFallback) Apply(server *Server) {
2✔
300
        server.Noticef("Reloaded: Client TLS handshake first fallback delay: %v", t.newValue)
2✔
301
}
2✔
302

303
// authOption is a base struct that provides default option behaviors.
304
type authOption struct {
305
        noopOption
306
}
307

308
func (o authOption) IsAuthChange() bool {
2,888✔
309
        return true
2,888✔
310
}
2,888✔
311

312
// usernameOption implements the option interface for the `username` setting.
313
type usernameOption struct {
314
        authOption
315
}
316

317
// Apply is a no-op because authorization will be reloaded after options are
318
// applied.
319
func (u *usernameOption) Apply(server *Server) {
4✔
320
        server.Noticef("Reloaded: authorization username")
4✔
321
}
4✔
322

323
// passwordOption implements the option interface for the `password` setting.
324
type passwordOption struct {
325
        authOption
326
}
327

328
// Apply is a no-op because authorization will be reloaded after options are
329
// applied.
330
func (p *passwordOption) Apply(server *Server) {
4✔
331
        server.Noticef("Reloaded: authorization password")
4✔
332
}
4✔
333

334
// authorizationOption implements the option interface for the `token`
335
// authorization setting.
336
type authorizationOption struct {
337
        authOption
338
}
339

340
// Apply is a no-op because authorization will be reloaded after options are
341
// applied.
342
func (a *authorizationOption) Apply(server *Server) {
3✔
343
        server.Noticef("Reloaded: authorization token")
3✔
344
}
3✔
345

346
// authTimeoutOption implements the option interface for the authorization
347
// `timeout` setting.
348
type authTimeoutOption struct {
349
        noopOption // Not authOption because this is a no-op; will be reloaded with options.
350
        newValue   float64
351
}
352

353
// Apply is a no-op because the timeout will be reloaded after options are
354
// applied.
355
func (a *authTimeoutOption) Apply(server *Server) {
4✔
356
        server.Noticef("Reloaded: authorization timeout = %v", a.newValue)
4✔
357
}
4✔
358

359
// tagsOption implements the option interface for the `tags` setting.
360
type tagsOption struct {
361
        noopOption // Not authOption because this is a no-op; will be reloaded with options.
362
}
363

364
func (u *tagsOption) Apply(server *Server) {
1✔
365
        server.Noticef("Reloaded: tags")
1✔
366
}
1✔
367

368
func (u *tagsOption) IsStatszChange() bool {
1✔
369
        return true
1✔
370
}
1✔
371

372
// metadataOption implements the option interface for the `metadata` setting.
373
type metadataOption struct {
374
        noopOption // Not authOption because this is a no-op; will be reloaded with options.
375
}
376

377
func (u *metadataOption) Apply(server *Server) {
1✔
378
        server.Noticef("Reloaded: metadata")
1✔
379
}
1✔
380

381
func (u *metadataOption) IsStatszChange() bool {
1✔
382
        return true
1✔
383
}
1✔
384

385
// usersOption implements the option interface for the authorization `users`
386
// setting.
387
type usersOption struct {
388
        authOption
389
}
390

391
func (u *usersOption) Apply(server *Server) {
1,343✔
392
        server.Noticef("Reloaded: authorization users")
1,343✔
393
}
1,343✔
394

395
// nkeysOption implements the option interface for the authorization `users`
396
// setting.
397
type nkeysOption struct {
398
        authOption
399
}
400

401
func (u *nkeysOption) Apply(server *Server) {
1✔
402
        server.Noticef("Reloaded: authorization nkey users")
1✔
403
}
1✔
404

405
// clusterOption implements the option interface for the `cluster` setting.
406
type clusterOption struct {
407
        authOption
408
        newValue        ClusterOpts
409
        permsChanged    bool
410
        accsAdded       []string
411
        accsRemoved     []string
412
        poolSizeChanged bool
413
        compressChanged bool
414
}
415

416
// Apply the cluster change.
417
func (c *clusterOption) Apply(s *Server) {
177✔
418
        // TODO: support enabling/disabling clustering.
177✔
419
        s.mu.Lock()
177✔
420
        tlsRequired := c.newValue.TLSConfig != nil
177✔
421
        s.routeInfo.TLSRequired = tlsRequired
177✔
422
        s.routeInfo.TLSVerify = tlsRequired
177✔
423
        s.routeInfo.AuthRequired = c.newValue.Username != ""
177✔
424
        if c.newValue.NoAdvertise {
179✔
425
                s.routeInfo.ClientConnectURLs = nil
2✔
426
                s.routeInfo.WSConnectURLs = nil
2✔
427
        } else {
177✔
428
                s.routeInfo.ClientConnectURLs = s.clientConnectURLs
175✔
429
                s.routeInfo.WSConnectURLs = s.websocket.connectURLs
175✔
430
        }
175✔
431
        s.setRouteInfoHostPortAndIP()
177✔
432
        var routes []*client
177✔
433
        if c.compressChanged {
200✔
434
                co := &s.getOpts().Cluster.Compression
23✔
435
                newMode := co.Mode
23✔
436
                s.forEachRoute(func(r *client) {
33✔
437
                        r.mu.Lock()
10✔
438
                        // Skip routes that are "not supported" (because they will never do
10✔
439
                        // compression) or the routes that have already the new compression
10✔
440
                        // mode.
10✔
441
                        if r.route.compression == CompressionNotSupported || r.route.compression == newMode {
15✔
442
                                r.mu.Unlock()
5✔
443
                                return
5✔
444
                        }
5✔
445
                        // We need to close the route if it had compression "off" or the new
446
                        // mode is compression "off", or if the new mode is "accept", because
447
                        // these require negotiation.
448
                        if r.route.compression == CompressionOff || newMode == CompressionOff || newMode == CompressionAccept {
10✔
449
                                routes = append(routes, r)
5✔
450
                        } else if newMode == CompressionS2Auto {
5✔
451
                                // If the mode is "s2_auto", we need to check if there is really
×
452
                                // need to change, and at any rate, we want to save the actual
×
453
                                // compression level here, not s2_auto.
×
454
                                r.updateS2AutoCompressionLevel(co, &r.route.compression)
×
455
                        } else {
×
456
                                // Simply change the compression writer
×
457
                                r.out.cw = s2.NewWriter(nil, s2WriterOptions(newMode)...)
×
458
                                r.route.compression = newMode
×
459
                        }
×
460
                        r.mu.Unlock()
5✔
461
                })
462
        }
463
        s.mu.Unlock()
177✔
464
        if c.newValue.Name != "" && c.newValue.Name != s.ClusterName() {
179✔
465
                s.setClusterName(c.newValue.Name)
2✔
466
        }
2✔
467
        for _, r := range routes {
182✔
468
                r.closeConnection(ClientClosed)
5✔
469
        }
5✔
470
        s.Noticef("Reloaded: cluster")
177✔
471
        if tlsRequired && c.newValue.TLSConfig.InsecureSkipVerify {
178✔
472
                s.Warnf(clusterTLSInsecureWarning)
1✔
473
        }
1✔
474
}
475

476
func (c *clusterOption) IsClusterPermsChange() bool {
177✔
477
        return c.permsChanged
177✔
478
}
177✔
479

480
func (c *clusterOption) IsClusterPoolSizeOrAccountsChange() bool {
177✔
481
        return c.poolSizeChanged || len(c.accsAdded) > 0 || len(c.accsRemoved) > 0
177✔
482
}
177✔
483

484
func (c *clusterOption) diffPoolAndAccounts(old *ClusterOpts) {
179✔
485
        c.poolSizeChanged = c.newValue.PoolSize != old.PoolSize
179✔
486
addLoop:
179✔
487
        for _, na := range c.newValue.PinnedAccounts {
284✔
488
                for _, oa := range old.PinnedAccounts {
248✔
489
                        if na == oa {
232✔
490
                                continue addLoop
89✔
491
                        }
492
                }
493
                c.accsAdded = append(c.accsAdded, na)
16✔
494
        }
495
removeLoop:
179✔
496
        for _, oa := range old.PinnedAccounts {
283✔
497
                for _, na := range c.newValue.PinnedAccounts {
248✔
498
                        if oa == na {
233✔
499
                                continue removeLoop
89✔
500
                        }
501
                }
502
                c.accsRemoved = append(c.accsRemoved, oa)
15✔
503
        }
504
}
505

506
// routesOption implements the option interface for the cluster `routes`
507
// setting.
508
type routesOption struct {
509
        noopOption
510
        add    []*url.URL
511
        remove []*url.URL
512
}
513

514
// Apply the route changes by adding and removing the necessary routes.
515
func (r *routesOption) Apply(server *Server) {
5✔
516
        server.mu.Lock()
5✔
517
        routes := make([]*client, server.numRoutes())
5✔
518
        i := 0
5✔
519
        server.forEachRoute(func(r *client) {
16✔
520
                routes[i] = r
11✔
521
                i++
11✔
522
        })
11✔
523
        // If there was a change, notify monitoring code that it should
524
        // update the route URLs if /varz endpoint is inspected.
525
        if len(r.add)+len(r.remove) > 0 {
10✔
526
                server.varzUpdateRouteURLs = true
5✔
527
        }
5✔
528
        server.mu.Unlock()
5✔
529

5✔
530
        // Remove routes.
5✔
531
        for _, remove := range r.remove {
10✔
532
                for _, client := range routes {
16✔
533
                        var url *url.URL
11✔
534
                        client.mu.Lock()
11✔
535
                        if client.route != nil {
22✔
536
                                url = client.route.url
11✔
537
                        }
11✔
538
                        client.mu.Unlock()
11✔
539
                        if url != nil && urlsAreEqual(url, remove) {
18✔
540
                                // Do not attempt to reconnect when route is removed.
7✔
541
                                client.setNoReconnect()
7✔
542
                                client.closeConnection(RouteRemoved)
7✔
543
                                server.Noticef("Removed route %v", remove)
7✔
544
                        }
7✔
545
                }
546
        }
547

548
        // Add routes.
549
        server.mu.Lock()
5✔
550
        server.solicitRoutes(r.add, server.getOpts().Cluster.PinnedAccounts)
5✔
551
        server.mu.Unlock()
5✔
552

5✔
553
        server.Noticef("Reloaded: cluster routes")
5✔
554
}
555

556
// maxConnOption implements the option interface for the `max_connections`
557
// setting.
558
type maxConnOption struct {
559
        noopOption
560
        newValue int
561
}
562

563
// Apply the max connections change by closing random connections til we are
564
// below the limit if necessary.
565
func (m *maxConnOption) Apply(server *Server) {
56✔
566
        server.mu.Lock()
56✔
567
        var (
56✔
568
                clients = make([]*client, len(server.clients))
56✔
569
                i       = 0
56✔
570
        )
56✔
571
        // Map iteration is random, which allows us to close random connections.
56✔
572
        for _, client := range server.clients {
58✔
573
                clients[i] = client
2✔
574
                i++
2✔
575
        }
2✔
576
        server.mu.Unlock()
56✔
577

56✔
578
        if m.newValue > 0 && len(clients) > m.newValue {
57✔
579
                // Close connections til we are within the limit.
1✔
580
                var (
1✔
581
                        numClose = len(clients) - m.newValue
1✔
582
                        closed   = 0
1✔
583
                )
1✔
584
                for _, client := range clients {
2✔
585
                        client.maxConnExceeded()
1✔
586
                        closed++
1✔
587
                        if closed >= numClose {
2✔
588
                                break
1✔
589
                        }
590
                }
591
                server.Noticef("Closed %d connections to fall within max_connections", closed)
1✔
592
        }
593
        server.Noticef("Reloaded: max_connections = %v", m.newValue)
56✔
594
}
595

596
// pidFileOption implements the option interface for the `pid_file` setting.
597
type pidFileOption struct {
598
        noopOption
599
        newValue string
600
}
601

602
// Apply the setting by logging the pid to the new file.
603
func (p *pidFileOption) Apply(server *Server) {
2✔
604
        if p.newValue == "" {
2✔
605
                return
×
606
        }
×
607
        if err := server.logPid(); err != nil {
2✔
608
                server.Errorf("Failed to write pidfile: %v", err)
×
609
        }
×
610
        server.Noticef("Reloaded: pid_file = %v", p.newValue)
2✔
611
}
612

613
// portsFileDirOption implements the option interface for the `portFileDir` setting.
614
type portsFileDirOption struct {
615
        noopOption
616
        oldValue string
617
        newValue string
618
}
619

620
func (p *portsFileDirOption) Apply(server *Server) {
1✔
621
        server.deletePortsFile(p.oldValue)
1✔
622
        server.logPorts()
1✔
623
        server.Noticef("Reloaded: ports_file_dir = %v", p.newValue)
1✔
624
}
1✔
625

626
// maxControlLineOption implements the option interface for the
627
// `max_control_line` setting.
628
type maxControlLineOption struct {
629
        noopOption
630
        newValue int32
631
}
632

633
// Apply the setting by updating each client.
634
func (m *maxControlLineOption) Apply(server *Server) {
2✔
635
        mcl := int32(m.newValue)
2✔
636
        server.mu.Lock()
2✔
637
        for _, client := range server.clients {
3✔
638
                atomic.StoreInt32(&client.mcl, mcl)
1✔
639
        }
1✔
640
        server.mu.Unlock()
2✔
641
        server.Noticef("Reloaded: max_control_line = %d", mcl)
2✔
642
}
643

644
// maxPayloadOption implements the option interface for the `max_payload`
645
// setting.
646
type maxPayloadOption struct {
647
        noopOption
648
        newValue int32
649
}
650

651
// Apply the setting by updating the server info and each client.
652
func (m *maxPayloadOption) Apply(server *Server) {
3✔
653
        server.mu.Lock()
3✔
654
        server.info.MaxPayload = m.newValue
3✔
655
        for _, client := range server.clients {
5✔
656
                atomic.StoreInt32(&client.mpay, int32(m.newValue))
2✔
657
        }
2✔
658
        server.mu.Unlock()
3✔
659
        server.Noticef("Reloaded: max_payload = %d", m.newValue)
3✔
660
}
661

662
// pingIntervalOption implements the option interface for the `ping_interval`
663
// setting.
664
type pingIntervalOption struct {
665
        noopOption
666
        newValue time.Duration
667
}
668

669
// Apply is a no-op because the ping interval will be reloaded after options
670
// are applied.
671
func (p *pingIntervalOption) Apply(server *Server) {
3✔
672
        server.Noticef("Reloaded: ping_interval = %s", p.newValue)
3✔
673
}
3✔
674

675
// maxPingsOutOption implements the option interface for the `ping_max`
676
// setting.
677
type maxPingsOutOption struct {
678
        noopOption
679
        newValue int
680
}
681

682
// Apply is a no-op because the ping interval will be reloaded after options
683
// are applied.
684
func (m *maxPingsOutOption) Apply(server *Server) {
1✔
685
        server.Noticef("Reloaded: ping_max = %d", m.newValue)
1✔
686
}
1✔
687

688
// writeDeadlineOption implements the option interface for the `write_deadline`
689
// setting.
690
type writeDeadlineOption struct {
691
        noopOption
692
        newValue time.Duration
693
}
694

695
// Apply is a no-op because the write deadline will be reloaded after options
696
// are applied.
697
func (w *writeDeadlineOption) Apply(server *Server) {
1✔
698
        server.Noticef("Reloaded: write_deadline = %s", w.newValue)
1✔
699
}
1✔
700

701
// clientAdvertiseOption implements the option interface for the `client_advertise` setting.
702
type clientAdvertiseOption struct {
703
        noopOption
704
        newValue string
705
}
706

707
// Apply the setting by updating the server info and regenerate the infoJSON byte array.
708
func (c *clientAdvertiseOption) Apply(server *Server) {
4✔
709
        server.mu.Lock()
4✔
710
        server.setInfoHostPort()
4✔
711
        server.mu.Unlock()
4✔
712
        server.Noticef("Reload: client_advertise = %s", c.newValue)
4✔
713
}
4✔
714

715
// accountsOption implements the option interface.
716
// Ensure that authorization code is executed if any change in accounts
717
type accountsOption struct {
718
        authOption
719
}
720

721
// Apply is a no-op. Changes will be applied in reloadAuthorization
722
func (a *accountsOption) Apply(s *Server) {
1,356✔
723
        s.Noticef("Reloaded: accounts")
1,356✔
724
}
1,356✔
725

726
// For changes to a server's config.
727
type jetStreamOption struct {
728
        noopOption
729
        newValue bool
730
}
731

732
func (a *jetStreamOption) Apply(s *Server) {
3✔
733
        s.Noticef("Reloaded: JetStream")
3✔
734
}
3✔
735

736
func (jso jetStreamOption) IsJetStreamChange() bool {
3✔
737
        return true
3✔
738
}
3✔
739

740
func (jso jetStreamOption) IsStatszChange() bool {
3✔
741
        return true
3✔
742
}
3✔
743

744
type defaultSentinelOption struct {
745
        noopOption
746
        newValue string
747
}
748

749
func (so *defaultSentinelOption) Apply(s *Server) {
1✔
750
        s.Noticef("Reloaded: default_sentinel = %s", so.newValue)
1✔
751
}
1✔
752

753
type ocspOption struct {
754
        tlsOption
755
        newValue *OCSPConfig
756
}
757

758
func (a *ocspOption) Apply(s *Server) {
2✔
759
        s.Noticef("Reloaded: OCSP")
2✔
760
}
2✔
761

762
type ocspResponseCacheOption struct {
763
        tlsOption
764
        newValue *OCSPResponseCacheConfig
765
}
766

767
func (a *ocspResponseCacheOption) Apply(s *Server) {
1✔
768
        s.Noticef("Reloaded OCSP peer cache")
1✔
769
}
1✔
770

771
// connectErrorReports implements the option interface for the `connect_error_reports`
772
// setting.
773
type connectErrorReports struct {
774
        noopOption
775
        newValue int
776
}
777

778
// Apply is a no-op because the value will be reloaded after options are applied.
779
func (c *connectErrorReports) Apply(s *Server) {
1✔
780
        s.Noticef("Reloaded: connect_error_reports = %v", c.newValue)
1✔
781
}
1✔
782

783
// connectErrorReports implements the option interface for the `connect_error_reports`
784
// setting.
785
type reconnectErrorReports struct {
786
        noopOption
787
        newValue int
788
}
789

790
// Apply is a no-op because the value will be reloaded after options are applied.
791
func (r *reconnectErrorReports) Apply(s *Server) {
1✔
792
        s.Noticef("Reloaded: reconnect_error_reports = %v", r.newValue)
1✔
793
}
1✔
794

795
// maxTracedMsgLenOption implements the option interface for the `max_traced_msg_len` setting.
796
type maxTracedMsgLenOption struct {
797
        noopOption
798
        newValue int
799
}
800

801
// Apply the setting by updating the maximum traced message length.
802
func (m *maxTracedMsgLenOption) Apply(server *Server) {
×
803
        server.mu.Lock()
×
804
        defer server.mu.Unlock()
×
805
        server.opts.MaxTracedMsgLen = m.newValue
×
806
        server.Noticef("Reloaded: max_traced_msg_len = %d", m.newValue)
×
807
}
×
808

809
type mqttAckWaitReload struct {
810
        noopOption
811
        newValue time.Duration
812
}
813

814
func (o *mqttAckWaitReload) Apply(s *Server) {
5✔
815
        s.Noticef("Reloaded: MQTT ack_wait = %v", o.newValue)
5✔
816
}
5✔
817

818
type mqttMaxAckPendingReload struct {
819
        noopOption
820
        newValue uint16
821
}
822

823
func (o *mqttMaxAckPendingReload) Apply(s *Server) {
5✔
824
        s.mqttUpdateMaxAckPending(o.newValue)
5✔
825
        s.Noticef("Reloaded: MQTT max_ack_pending = %v", o.newValue)
5✔
826
}
5✔
827

828
type mqttStreamReplicasReload struct {
829
        noopOption
830
        newValue int
831
}
832

833
func (o *mqttStreamReplicasReload) Apply(s *Server) {
5✔
834
        s.Noticef("Reloaded: MQTT stream_replicas = %v", o.newValue)
5✔
835
}
5✔
836

837
type mqttConsumerReplicasReload struct {
838
        noopOption
839
        newValue int
840
}
841

842
func (o *mqttConsumerReplicasReload) Apply(s *Server) {
5✔
843
        s.Noticef("Reloaded: MQTT consumer_replicas = %v", o.newValue)
5✔
844
}
5✔
845

846
type mqttConsumerMemoryStorageReload struct {
847
        noopOption
848
        newValue bool
849
}
850

851
func (o *mqttConsumerMemoryStorageReload) Apply(s *Server) {
5✔
852
        s.Noticef("Reloaded: MQTT consumer_memory_storage = %v", o.newValue)
5✔
853
}
5✔
854

855
type mqttInactiveThresholdReload struct {
856
        noopOption
857
        newValue time.Duration
858
}
859

860
func (o *mqttInactiveThresholdReload) Apply(s *Server) {
5✔
861
        s.Noticef("Reloaded: MQTT consumer_inactive_threshold = %v", o.newValue)
5✔
862
}
5✔
863

864
type profBlockRateReload struct {
865
        noopOption
866
        newValue int
867
}
868

869
func (o *profBlockRateReload) Apply(s *Server) {
×
870
        s.setBlockProfileRate(o.newValue)
×
871
        s.Noticef("Reloaded: prof_block_rate = %v", o.newValue)
×
872
}
×
873

874
type leafNodeOption struct {
875
        noopOption
876
        tlsFirstChanged    bool
877
        compressionChanged bool
878
        disabledChanged    bool
879
}
880

881
func (l *leafNodeOption) Apply(s *Server) {
46✔
882
        opts := s.getOpts()
46✔
883
        if l.tlsFirstChanged {
50✔
884
                s.Noticef("Reloaded: LeafNode TLS HandshakeFirst value is: %v", opts.LeafNode.TLSHandshakeFirst)
4✔
885
                s.Noticef("Reloaded: LeafNode TLS HandshakeFirstFallback value is: %v", opts.LeafNode.TLSHandshakeFirstFallback)
4✔
886
                for _, r := range opts.LeafNode.Remotes {
5✔
887
                        s.Noticef("Reloaded: LeafNode Remote to %v TLS HandshakeFirst value is: %v", r.URLs, r.TLSHandshakeFirst)
1✔
888
                }
1✔
889
        }
890
        if l.compressionChanged || l.disabledChanged {
91✔
891
                var leafs []*client
45✔
892
                var solicit []*leafNodeCfg
45✔
893
                acceptSideCompOpts := &opts.LeafNode.Compression
45✔
894

45✔
895
                s.mu.RLock()
45✔
896
                // First, update our internal leaf remote configurations with the new
45✔
897
                // compress options.
45✔
898
                // Since changing the remotes (as in adding/removing) is currently not
45✔
899
                // supported, we know that we should have the same number in Options
45✔
900
                // than in leafRemoteCfgs, but to be sure, use the max size.
45✔
901
                max := len(opts.LeafNode.Remotes)
45✔
902
                if l := len(s.leafRemoteCfgs); l < max {
45✔
903
                        max = l
×
904
                }
×
905
                for i := range max {
59✔
906
                        lr := s.leafRemoteCfgs[i]
14✔
907
                        or := opts.LeafNode.Remotes[i]
14✔
908
                        lr.Lock()
14✔
909
                        lr.Compression = or.Compression
14✔
910
                        if lr.Disabled && !or.Disabled {
16✔
911
                                solicit = append(solicit, lr)
2✔
912
                        }
2✔
913
                        lr.Disabled = or.Disabled
14✔
914
                        lr.Unlock()
14✔
915
                }
916

917
                for _, l := range s.leafs {
58✔
918
                        var co *CompressionOpts
13✔
919

13✔
920
                        l.mu.Lock()
13✔
921
                        if r := l.leaf.remote; r != nil {
20✔
922
                                // If newly marked as disabled, collect and ignore the rest.
7✔
923
                                if r.Disabled {
8✔
924
                                        l.flags.set(noReconnect)
1✔
925
                                        leafs = append(leafs, l)
1✔
926
                                        l.mu.Unlock()
1✔
927
                                        continue
1✔
928
                                }
929
                                co = &r.Compression
6✔
930
                        } else {
6✔
931
                                co = acceptSideCompOpts
6✔
932
                        }
6✔
933
                        newMode := co.Mode
12✔
934
                        // Skip leaf connections that are "not supported" (because they
12✔
935
                        // will never do compression) or the ones that have already the
12✔
936
                        // new compression mode.
12✔
937
                        if l.leaf.compression == CompressionNotSupported || l.leaf.compression == newMode {
13✔
938
                                l.mu.Unlock()
1✔
939
                                continue
1✔
940
                        }
941
                        // We need to close the connections if it had compression "off" or the new
942
                        // mode is compression "off", or if the new mode is "accept", because
943
                        // these require negotiation.
944
                        if l.leaf.compression == CompressionOff || newMode == CompressionOff || newMode == CompressionAccept {
13✔
945
                                leafs = append(leafs, l)
2✔
946
                        } else if newMode == CompressionS2Auto {
20✔
947
                                // If the mode is "s2_auto", we need to check if there is really
9✔
948
                                // need to change, and at any rate, we want to save the actual
9✔
949
                                // compression level here, not s2_auto.
9✔
950
                                l.updateS2AutoCompressionLevel(co, &l.leaf.compression)
9✔
951
                        } else {
9✔
952
                                // Simply change the compression writer
×
953
                                l.out.cw = s2.NewWriter(nil, s2WriterOptions(newMode)...)
×
954
                                l.leaf.compression = newMode
×
955
                        }
×
956
                        l.mu.Unlock()
11✔
957
                }
958
                s.mu.RUnlock()
45✔
959
                // Close the connections for which negotiation is required, or that
45✔
960
                // have been disabled.
45✔
961
                for _, l := range leafs {
48✔
962
                        l.closeConnection(ClientClosed)
3✔
963
                }
3✔
964
                if l.compressionChanged {
90✔
965
                        s.Noticef("Reloaded: LeafNode compression settings")
45✔
966
                }
45✔
967
                if l.disabledChanged {
49✔
968
                        if len(leafs) > 0 {
5✔
969
                                s.Noticef("Reloaded: LeafNode(s) disabled")
1✔
970
                        }
1✔
971
                        if len(solicit) > 0 {
6✔
972
                                for _, remote := range solicit {
4✔
973
                                        s.startGoRoutine(func() { s.connectToRemoteLeafNode(remote, true) })
4✔
974
                                }
975
                                s.Noticef("Reloaded: LeafNode(s) enabled")
2✔
976
                        }
977
                }
978
        }
979
}
980

981
type noFastProdStallReload struct {
982
        noopOption
983
        noStall bool
984
}
985

986
func (l *noFastProdStallReload) Apply(s *Server) {
×
987
        var not string
×
988
        if l.noStall {
×
989
                not = "not "
×
990
        }
×
991
        s.Noticef("Reloaded: fast producers will %sbe stalled", not)
×
992
}
993

994
// Compares options and disconnects clients that are no longer listed in pinned certs. Lock must not be held.
995
func (s *Server) recheckPinnedCerts(curOpts *Options, newOpts *Options) {
1,343✔
996
        s.mu.Lock()
1,343✔
997
        disconnectClients := []*client{}
1,343✔
998
        protoToPinned := map[int]PinnedCertSet{}
1,343✔
999
        if !reflect.DeepEqual(newOpts.TLSPinnedCerts, curOpts.TLSPinnedCerts) {
1,344✔
1000
                protoToPinned[NATS] = curOpts.TLSPinnedCerts
1✔
1001
        }
1✔
1002
        if !reflect.DeepEqual(newOpts.MQTT.TLSPinnedCerts, curOpts.MQTT.TLSPinnedCerts) {
1,343✔
1003
                protoToPinned[MQTT] = curOpts.MQTT.TLSPinnedCerts
×
1004
        }
×
1005
        if !reflect.DeepEqual(newOpts.Websocket.TLSPinnedCerts, curOpts.Websocket.TLSPinnedCerts) {
1,343✔
1006
                protoToPinned[WS] = curOpts.Websocket.TLSPinnedCerts
×
1007
        }
×
1008
        for _, c := range s.clients {
3,560✔
1009
                if c.kind != CLIENT {
2,217✔
1010
                        continue
×
1011
                }
1012
                if pinned, ok := protoToPinned[c.clientType()]; ok {
2,217✔
1013
                        if !c.matchesPinnedCert(pinned) {
×
1014
                                disconnectClients = append(disconnectClients, c)
×
1015
                        }
×
1016
                }
1017
        }
1018
        checkClients := func(kind int, clients map[uint64]*client, set PinnedCertSet) {
1,412✔
1019
                for _, c := range clients {
174✔
1020
                        if c.kind == kind && !c.matchesPinnedCert(set) {
105✔
1021
                                disconnectClients = append(disconnectClients, c)
×
1022
                        }
×
1023
                }
1024
        }
1025
        if !reflect.DeepEqual(newOpts.LeafNode.TLSPinnedCerts, curOpts.LeafNode.TLSPinnedCerts) {
1,343✔
1026
                checkClients(LEAF, s.leafs, newOpts.LeafNode.TLSPinnedCerts)
×
1027
        }
×
1028
        if !reflect.DeepEqual(newOpts.Cluster.TLSPinnedCerts, curOpts.Cluster.TLSPinnedCerts) {
1,344✔
1029
                s.forEachRoute(func(c *client) {
1✔
1030
                        if !c.matchesPinnedCert(newOpts.Cluster.TLSPinnedCerts) {
×
1031
                                disconnectClients = append(disconnectClients, c)
×
1032
                        }
×
1033
                })
1034
        }
1035
        if s.gateway.enabled && reflect.DeepEqual(newOpts.Gateway.TLSPinnedCerts, curOpts.Gateway.TLSPinnedCerts) {
1,412✔
1036
                gw := s.gateway
69✔
1037
                gw.RLock()
69✔
1038
                for _, c := range gw.out {
173✔
1039
                        if !c.matchesPinnedCert(newOpts.Gateway.TLSPinnedCerts) {
104✔
1040
                                disconnectClients = append(disconnectClients, c)
×
1041
                        }
×
1042
                }
1043
                checkClients(GATEWAY, gw.in, newOpts.Gateway.TLSPinnedCerts)
69✔
1044
                gw.RUnlock()
69✔
1045
        }
1046
        s.mu.Unlock()
1,343✔
1047
        if len(disconnectClients) > 0 {
1,343✔
1048
                s.Noticef("Disconnect %d clients due to pinned certs reload", len(disconnectClients))
×
1049
                for _, c := range disconnectClients {
×
1050
                        c.closeConnection(TLSHandshakeError)
×
1051
                }
×
1052
        }
1053
}
1054

1055
type proxiesReload struct {
1056
        noopOption
1057
        add []string
1058
        del []string
1059
}
1060

1061
func (p *proxiesReload) Apply(s *Server) {
1✔
1062
        var clients []*client
1✔
1063
        s.mu.Lock()
1✔
1064
        for _, k := range p.del {
2✔
1065
                cc := s.proxiedConns[k]
1✔
1066
                delete(s.proxiedConns, k)
1✔
1067
                if len(cc) > 0 {
2✔
1068
                        for _, c := range cc {
3✔
1069
                                clients = append(clients, c)
2✔
1070
                        }
2✔
1071
                }
1072
        }
1073
        s.processProxiesTrustedKeys()
1✔
1074
        s.mu.Unlock()
1✔
1075
        if len(p.del) > 0 {
2✔
1076
                for _, c := range clients {
3✔
1077
                        c.setAuthError(ErrAuthProxyNotTrusted)
2✔
1078
                        c.authViolation()
2✔
1079
                }
2✔
1080
                s.Noticef("Reloaded: proxies trusted keys %q were removed", p.del)
1✔
1081
        }
1082
        if len(p.add) > 0 {
2✔
1083
                s.Noticef("Reloaded: proxies trusted keys %q were added", p.add)
1✔
1084
        }
1✔
1085
}
1086

1087
// Reload reads the current configuration file and calls out to ReloadOptions
1088
// to apply the changes. This returns an error if the server was not started
1089
// with a config file or an option which doesn't support hot-swapping was changed.
1090
func (s *Server) Reload() error {
1,356✔
1091
        s.mu.Lock()
1,356✔
1092
        configFile := s.configFile
1,356✔
1093
        s.mu.Unlock()
1,356✔
1094
        if configFile == "" {
1,357✔
1095
                return errors.New("can only reload config when a file is provided using -c or --config")
1✔
1096
        }
1✔
1097

1098
        newOpts, err := ProcessConfigFile(configFile)
1,355✔
1099
        if err != nil {
1,358✔
1100
                // TODO: Dump previous good config to a .bak file?
3✔
1101
                return err
3✔
1102
        }
3✔
1103
        return s.ReloadOptions(newOpts)
1,352✔
1104
}
1105

1106
// ReloadOptions applies any supported options from the provided Options
1107
// type. This returns an error if an option which doesn't support
1108
// hot-swapping was changed.
1109
// The provided Options type should not be re-used afterwards.
1110
// Either use Options.Clone() to pass a copy, or make a new one.
1111
func (s *Server) ReloadOptions(newOpts *Options) error {
1,353✔
1112
        s.reloadMu.Lock()
1,353✔
1113
        defer s.reloadMu.Unlock()
1,353✔
1114

1,353✔
1115
        s.mu.Lock()
1,353✔
1116

1,353✔
1117
        curOpts := s.getOpts()
1,353✔
1118

1,353✔
1119
        // Wipe trusted keys if needed when we have an operator.
1,353✔
1120
        if len(curOpts.TrustedOperators) > 0 && len(curOpts.TrustedKeys) > 0 {
1,363✔
1121
                curOpts.TrustedKeys = nil
10✔
1122
        }
10✔
1123

1124
        clientOrgPort := curOpts.Port
1,353✔
1125
        clusterOrgPort := curOpts.Cluster.Port
1,353✔
1126
        gatewayOrgPort := curOpts.Gateway.Port
1,353✔
1127
        leafnodesOrgPort := curOpts.LeafNode.Port
1,353✔
1128
        websocketOrgPort := curOpts.Websocket.Port
1,353✔
1129
        mqttOrgPort := curOpts.MQTT.Port
1,353✔
1130

1,353✔
1131
        s.mu.Unlock()
1,353✔
1132

1,353✔
1133
        // In case "-cluster ..." was provided through the command line, this will
1,353✔
1134
        // properly set the Cluster.Host/Port etc...
1,353✔
1135
        if l := curOpts.Cluster.ListenStr; l != _EMPTY_ {
1,353✔
1136
                newOpts.Cluster.ListenStr = l
×
1137
                overrideCluster(newOpts)
×
1138
        }
×
1139

1140
        // Apply flags over config file settings.
1141
        newOpts = MergeOptions(newOpts, FlagSnapshot)
1,353✔
1142

1,353✔
1143
        // Need more processing for boolean flags...
1,353✔
1144
        if FlagSnapshot != nil {
1,407✔
1145
                applyBoolFlags(newOpts, FlagSnapshot)
54✔
1146
        }
54✔
1147

1148
        setBaselineOptions(newOpts)
1,353✔
1149

1,353✔
1150
        // setBaselineOptions sets Port to 0 if set to -1 (RANDOM port)
1,353✔
1151
        // If that's the case, set it to the saved value when the accept loop was
1,353✔
1152
        // created.
1,353✔
1153
        if newOpts.Port == 0 {
2,695✔
1154
                newOpts.Port = clientOrgPort
1,342✔
1155
        }
1,342✔
1156
        // We don't do that for cluster, so check against -1.
1157
        if newOpts.Cluster.Port == -1 {
1,463✔
1158
                newOpts.Cluster.Port = clusterOrgPort
110✔
1159
        }
110✔
1160
        if newOpts.Gateway.Port == -1 {
1,420✔
1161
                newOpts.Gateway.Port = gatewayOrgPort
67✔
1162
        }
67✔
1163
        if newOpts.LeafNode.Port == -1 {
1,372✔
1164
                newOpts.LeafNode.Port = leafnodesOrgPort
19✔
1165
        }
19✔
1166
        if newOpts.Websocket.Port == -1 {
1,355✔
1167
                newOpts.Websocket.Port = websocketOrgPort
2✔
1168
        }
2✔
1169
        if newOpts.MQTT.Port == -1 {
1,361✔
1170
                newOpts.MQTT.Port = mqttOrgPort
8✔
1171
        }
8✔
1172

1173
        if err := s.reloadOptions(curOpts, newOpts); err != nil {
1,363✔
1174
                return err
10✔
1175
        }
10✔
1176

1177
        s.recheckPinnedCerts(curOpts, newOpts)
1,343✔
1178

1,343✔
1179
        s.mu.Lock()
1,343✔
1180
        s.configTime = time.Now().UTC()
1,343✔
1181
        s.updateVarzConfigReloadableFields(s.varz)
1,343✔
1182
        s.mu.Unlock()
1,343✔
1183
        return nil
1,343✔
1184
}
1185
func applyBoolFlags(newOpts, flagOpts *Options) {
54✔
1186
        // Reset fields that may have been set to `true` in
54✔
1187
        // MergeOptions() when some of the flags default to `true`
54✔
1188
        // but have not been explicitly set and therefore value
54✔
1189
        // from config file should take precedence.
54✔
1190
        for name, val := range newOpts.inConfig {
92✔
1191
                f := reflect.ValueOf(newOpts).Elem()
38✔
1192
                names := strings.Split(name, ".")
38✔
1193
                for _, name := range names {
80✔
1194
                        f = f.FieldByName(name)
42✔
1195
                }
42✔
1196
                f.SetBool(val)
38✔
1197
        }
1198
        // Now apply value (true or false) from flags that have
1199
        // been explicitly set in command line
1200
        for name, val := range flagOpts.inCmdLine {
94✔
1201
                f := reflect.ValueOf(newOpts).Elem()
40✔
1202
                names := strings.Split(name, ".")
40✔
1203
                for _, name := range names {
83✔
1204
                        f = f.FieldByName(name)
43✔
1205
                }
43✔
1206
                f.SetBool(val)
40✔
1207
        }
1208
}
1209

1210
// reloadOptions reloads the server config with the provided options. If an
1211
// option that doesn't support hot-swapping is changed, this returns an error.
1212
func (s *Server) reloadOptions(curOpts, newOpts *Options) error {
1,353✔
1213
        // Apply to the new options some of the options that may have been set
1,353✔
1214
        // that can't be configured in the config file (this can happen in
1,353✔
1215
        // applications starting NATS Server programmatically).
1,353✔
1216
        newOpts.CustomClientAuthentication = curOpts.CustomClientAuthentication
1,353✔
1217
        newOpts.CustomRouterAuthentication = curOpts.CustomRouterAuthentication
1,353✔
1218

1,353✔
1219
        changed, err := s.diffOptions(newOpts)
1,353✔
1220
        if err != nil {
1,361✔
1221
                return err
8✔
1222
        }
8✔
1223

1224
        if len(changed) != 0 {
2,690✔
1225
                if err := validateOptions(newOpts); err != nil {
1,347✔
1226
                        return err
2✔
1227
                }
2✔
1228
        }
1229

1230
        // Create a context that is used to pass special info that we may need
1231
        // while applying the new options.
1232
        ctx := reloadContext{oldClusterPerms: curOpts.Cluster.Permissions}
1,343✔
1233
        s.setOpts(newOpts)
1,343✔
1234
        s.applyOptions(&ctx, changed)
1,343✔
1235
        return nil
1,343✔
1236
}
1237

1238
// For the purpose of comparing, impose a order on slice data types where order does not matter
1239
func imposeOrder(value any) error {
315,262✔
1240
        switch value := value.(type) {
315,262✔
1241
        case []*Account:
2,700✔
1242
                slices.SortFunc(value, func(i, j *Account) int { return cmp.Compare(i.Name, j.Name) })
8,118✔
1243
                for _, a := range value {
9,489✔
1244
                        slices.SortFunc(a.imports.streams, func(i, j *streamImport) int { return cmp.Compare(i.acc.Name, j.acc.Name) })
6,793✔
1245
                }
1246
        case []*User:
2,700✔
1247
                slices.SortFunc(value, func(i, j *User) int { return cmp.Compare(i.Username, j.Username) })
8,163✔
1248
        case []*NkeyUser:
2,700✔
1249
                slices.SortFunc(value, func(i, j *NkeyUser) int { return cmp.Compare(i.Nkey, j.Nkey) })
2,702✔
1250
        case []*url.URL:
2,690✔
1251
                slices.SortFunc(value, func(i, j *url.URL) int { return cmp.Compare(i.String(), j.String()) })
2,712✔
1252
        case []string:
2,690✔
1253
                slices.Sort(value)
2,690✔
1254
        case []*jwt.OperatorClaims:
2,690✔
1255
                slices.SortFunc(value, func(i, j *jwt.OperatorClaims) int { return cmp.Compare(i.Issuer, j.Issuer) })
2,690✔
1256
        case GatewayOpts:
2,696✔
1257
                slices.SortFunc(value.Gateways, func(i, j *RemoteGatewayOpts) int { return cmp.Compare(i.Name, j.Name) })
2,700✔
1258
        case WebsocketOpts:
2,690✔
1259
                slices.Sort(value.AllowedOrigins)
2,690✔
1260
        case string, bool, uint8, uint16, uint64, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet,
1261
                *URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList,
1262
                *OCSPConfig, map[string]string, JSLimitOpts, StoreCipher, *OCSPResponseCacheConfig, *ProxiesConfig, WriteTimeoutPolicy:
288,314✔
1263
                // explicitly skipped types
1264
        case *AuthCallout:
2,700✔
1265
        case JSTpmOpts:
2,692✔
1266
        default:
×
1267
                // this will fail during unit tests
×
1268
                return fmt.Errorf("OnReload, sort or explicitly skip type: %s",
×
1269
                        reflect.TypeOf(value))
×
1270
        }
1271
        return nil
315,262✔
1272
}
1273

1274
// diffOptions returns a slice containing options which have been changed. If
1275
// an option that doesn't support hot-swapping is changed, this returns an
1276
// error.
1277
func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
1,353✔
1278
        var (
1,353✔
1279
                oldConfig = reflect.ValueOf(s.getOpts()).Elem()
1,353✔
1280
                newConfig = reflect.ValueOf(newOpts).Elem()
1,353✔
1281
                diffOpts  = []option{}
1,353✔
1282

1,353✔
1283
                // Need to keep track of whether JS is being disabled
1,353✔
1284
                // to prevent changing limits at runtime.
1,353✔
1285
                jsEnabled           = s.JetStreamEnabled()
1,353✔
1286
                disableJS           bool
1,353✔
1287
                jsMemLimitsChanged  bool
1,353✔
1288
                jsFileLimitsChanged bool
1,353✔
1289
                jsStoreDirChanged   bool
1,353✔
1290
        )
1,353✔
1291
        for i := 0; i < oldConfig.NumField(); i++ {
176,469✔
1292
                field := oldConfig.Type().Field(i)
175,116✔
1293
                // field.PkgPath is empty for exported fields, and is not for unexported ones.
175,116✔
1294
                // We skip the unexported fields.
175,116✔
1295
                if field.PkgPath != _EMPTY_ {
192,601✔
1296
                        continue
17,485✔
1297
                }
1298
                var (
157,631✔
1299
                        oldValue = oldConfig.Field(i).Interface()
157,631✔
1300
                        newValue = newConfig.Field(i).Interface()
157,631✔
1301
                )
157,631✔
1302
                if err := imposeOrder(oldValue); err != nil {
157,631✔
1303
                        return nil, err
×
1304
                }
×
1305
                if err := imposeOrder(newValue); err != nil {
157,631✔
1306
                        return nil, err
×
1307
                }
×
1308

1309
                optName := strings.ToLower(field.Name)
157,631✔
1310
                // accounts and users (referencing accounts) will always differ as accounts
157,631✔
1311
                // contain internal state, say locks etc..., so we don't bother here.
157,631✔
1312
                // This also avoids races with atomic stats counters
157,631✔
1313
                if optName != "accounts" && optName != "users" {
312,562✔
1314
                        if changed := !reflect.DeepEqual(oldValue, newValue); !changed {
308,005✔
1315
                                // Check to make sure we are running JetStream if we think we should be.
153,074✔
1316
                                if optName == "jetstream" && newValue.(bool) {
153,100✔
1317
                                        if !jsEnabled {
26✔
1318
                                                diffOpts = append(diffOpts, &jetStreamOption{newValue: true})
×
1319
                                        }
×
1320
                                }
1321
                                continue
153,074✔
1322
                        }
1323
                }
1324
                switch optName {
4,557✔
1325
                case "traceverbose":
2✔
1326
                        diffOpts = append(diffOpts, &traceVerboseOption{newValue: newValue.(bool)})
2✔
1327
                case "traceheaders":
×
1328
                        diffOpts = append(diffOpts, &traceHeadersOption{newValue: newValue.(bool)})
×
1329
                case "trace":
3✔
1330
                        diffOpts = append(diffOpts, &traceOption{newValue: newValue.(bool)})
3✔
1331
                case "debug":
3✔
1332
                        diffOpts = append(diffOpts, &debugOption{newValue: newValue.(bool)})
3✔
1333
                case "logtime":
1✔
1334
                        diffOpts = append(diffOpts, &logtimeOption{newValue: newValue.(bool)})
1✔
1335
                case "logtimeutc":
1✔
1336
                        diffOpts = append(diffOpts, &logtimeUTCOption{newValue: newValue.(bool)})
1✔
1337
                case "logfile":
5✔
1338
                        diffOpts = append(diffOpts, &logfileOption{newValue: newValue.(string)})
5✔
1339
                case "syslog":
1✔
1340
                        diffOpts = append(diffOpts, &syslogOption{newValue: newValue.(bool)})
1✔
1341
                case "remotesyslog":
1✔
1342
                        diffOpts = append(diffOpts, &remoteSyslogOption{newValue: newValue.(string)})
1✔
1343
                case "tlsconfig":
24✔
1344
                        diffOpts = append(diffOpts, &tlsOption{newValue: newValue.(*tls.Config)})
24✔
1345
                case "tlstimeout":
×
1346
                        diffOpts = append(diffOpts, &tlsTimeoutOption{newValue: newValue.(float64)})
×
1347
                case "tlspinnedcerts":
1✔
1348
                        diffOpts = append(diffOpts, &tlsPinnedCertOption{newValue: newValue.(PinnedCertSet)})
1✔
1349
                case "tlshandshakefirst":
2✔
1350
                        diffOpts = append(diffOpts, &tlsHandshakeFirst{newValue: newValue.(bool)})
2✔
1351
                case "tlshandshakefirstfallback":
2✔
1352
                        diffOpts = append(diffOpts, &tlsHandshakeFirstFallback{newValue: newValue.(time.Duration)})
2✔
1353
                case "username":
4✔
1354
                        diffOpts = append(diffOpts, &usernameOption{})
4✔
1355
                case "password":
4✔
1356
                        diffOpts = append(diffOpts, &passwordOption{})
4✔
1357
                case "tags":
1✔
1358
                        diffOpts = append(diffOpts, &tagsOption{})
1✔
1359
                case "metadata":
1✔
1360
                        diffOpts = append(diffOpts, &metadataOption{})
1✔
1361
                case "authorization":
3✔
1362
                        diffOpts = append(diffOpts, &authorizationOption{})
3✔
1363
                case "authtimeout":
4✔
1364
                        diffOpts = append(diffOpts, &authTimeoutOption{newValue: newValue.(float64)})
4✔
1365
                case "users":
1,350✔
1366
                        diffOpts = append(diffOpts, &usersOption{})
1,350✔
1367
                case "nkeys":
1✔
1368
                        diffOpts = append(diffOpts, &nkeysOption{})
1✔
1369
                case "cluster":
181✔
1370
                        newClusterOpts := newValue.(ClusterOpts)
181✔
1371
                        oldClusterOpts := oldValue.(ClusterOpts)
181✔
1372
                        if err := validateClusterOpts(oldClusterOpts, newClusterOpts); err != nil {
183✔
1373
                                return nil, err
2✔
1374
                        }
2✔
1375
                        co := &clusterOption{
179✔
1376
                                newValue:        newClusterOpts,
179✔
1377
                                permsChanged:    !reflect.DeepEqual(newClusterOpts.Permissions, oldClusterOpts.Permissions),
179✔
1378
                                compressChanged: !compressOptsEqual(&oldClusterOpts.Compression, &newClusterOpts.Compression),
179✔
1379
                        }
179✔
1380
                        co.diffPoolAndAccounts(&oldClusterOpts)
179✔
1381
                        // If there are added accounts, first make sure that we can look them up.
179✔
1382
                        // If we can't let's fail the reload.
179✔
1383
                        for _, acc := range co.accsAdded {
195✔
1384
                                if _, err := s.LookupAccount(acc); err != nil {
16✔
1385
                                        return nil, fmt.Errorf("unable to add account %q to the list of dedicated routes: %v", acc, err)
×
1386
                                }
×
1387
                        }
1388
                        // If pool_size has been set to negative (but was not before), then let's
1389
                        // add the system account to the list of removed accounts (we don't have
1390
                        // to check if already there, duplicates are ok in that case).
1391
                        if newClusterOpts.PoolSize < 0 && oldClusterOpts.PoolSize >= 0 {
182✔
1392
                                if sys := s.SystemAccount(); sys != nil {
6✔
1393
                                        co.accsRemoved = append(co.accsRemoved, sys.GetName())
3✔
1394
                                }
3✔
1395
                        }
1396
                        diffOpts = append(diffOpts, co)
179✔
1397
                case "routes":
5✔
1398
                        add, remove := diffRoutes(oldValue.([]*url.URL), newValue.([]*url.URL))
5✔
1399
                        diffOpts = append(diffOpts, &routesOption{add: add, remove: remove})
5✔
1400
                case "maxconn":
56✔
1401
                        diffOpts = append(diffOpts, &maxConnOption{newValue: newValue.(int)})
56✔
1402
                case "pidfile":
2✔
1403
                        diffOpts = append(diffOpts, &pidFileOption{newValue: newValue.(string)})
2✔
1404
                case "portsfiledir":
1✔
1405
                        diffOpts = append(diffOpts, &portsFileDirOption{newValue: newValue.(string), oldValue: oldValue.(string)})
1✔
1406
                case "maxcontrolline":
2✔
1407
                        diffOpts = append(diffOpts, &maxControlLineOption{newValue: newValue.(int32)})
2✔
1408
                case "maxpayload":
3✔
1409
                        diffOpts = append(diffOpts, &maxPayloadOption{newValue: newValue.(int32)})
3✔
1410
                case "pinginterval":
3✔
1411
                        diffOpts = append(diffOpts, &pingIntervalOption{newValue: newValue.(time.Duration)})
3✔
1412
                case "maxpingsout":
1✔
1413
                        diffOpts = append(diffOpts, &maxPingsOutOption{newValue: newValue.(int)})
1✔
1414
                case "writedeadline":
1✔
1415
                        diffOpts = append(diffOpts, &writeDeadlineOption{newValue: newValue.(time.Duration)})
1✔
1416
                case "clientadvertise":
4✔
1417
                        cliAdv := newValue.(string)
4✔
1418
                        if cliAdv != "" {
7✔
1419
                                // Validate ClientAdvertise syntax
3✔
1420
                                if _, _, err := parseHostPort(cliAdv, 0); err != nil {
3✔
1421
                                        return nil, fmt.Errorf("invalid ClientAdvertise value of %s, err=%v", cliAdv, err)
×
1422
                                }
×
1423
                        }
1424
                        diffOpts = append(diffOpts, &clientAdvertiseOption{newValue: cliAdv})
4✔
1425
                case "accounts":
1,350✔
1426
                        diffOpts = append(diffOpts, &accountsOption{})
1,350✔
1427
                case "resolver", "accountresolver", "accountsresolver":
11✔
1428
                        // We can't move from no resolver to one. So check for that.
11✔
1429
                        if (oldValue == nil && newValue != nil) ||
11✔
1430
                                (oldValue != nil && newValue == nil) {
11✔
1431
                                return nil, fmt.Errorf("config reload does not support moving to or from an account resolver")
×
1432
                        }
×
1433
                        diffOpts = append(diffOpts, &accountsOption{})
11✔
1434
                case "accountresolvertlsconfig":
2✔
1435
                        diffOpts = append(diffOpts, &accountsOption{})
2✔
1436
                case "gateway":
63✔
1437
                        // Not supported for now, but report warning if configuration of gateway
63✔
1438
                        // is actually changed so that user knows that it won't take effect.
63✔
1439

63✔
1440
                        // Any deep-equal is likely to fail for when there is a TLSConfig. so
63✔
1441
                        // remove for the test.
63✔
1442
                        tmpOld := oldValue.(GatewayOpts)
63✔
1443
                        tmpNew := newValue.(GatewayOpts)
63✔
1444
                        tmpOld.TLSConfig = nil
63✔
1445
                        tmpNew.TLSConfig = nil
63✔
1446
                        tmpOld.tlsConfigOpts = nil
63✔
1447
                        tmpNew.tlsConfigOpts = nil
63✔
1448

63✔
1449
                        // Need to do the same for remote gateways' TLS configs.
63✔
1450
                        // But we can't just set remotes' TLSConfig to nil otherwise this
63✔
1451
                        // would lose the real TLS configuration.
63✔
1452
                        tmpOld.Gateways = copyRemoteGWConfigsWithoutTLSConfig(tmpOld.Gateways)
63✔
1453
                        tmpNew.Gateways = copyRemoteGWConfigsWithoutTLSConfig(tmpNew.Gateways)
63✔
1454

63✔
1455
                        // If there is really a change prevents reload.
63✔
1456
                        if !reflect.DeepEqual(tmpOld, tmpNew) {
64✔
1457
                                // See TODO(ik) note below about printing old/new values.
1✔
1458
                                return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v",
1✔
1459
                                        field.Name, oldValue, newValue)
1✔
1460
                        }
1✔
1461
                case "leafnode":
47✔
1462
                        // Similar to gateways
47✔
1463
                        tmpOld := oldValue.(LeafNodeOpts)
47✔
1464
                        tmpNew := newValue.(LeafNodeOpts)
47✔
1465
                        tmpOld.TLSConfig = nil
47✔
1466
                        tmpNew.TLSConfig = nil
47✔
1467
                        tmpOld.tlsConfigOpts = nil
47✔
1468
                        tmpNew.tlsConfigOpts = nil
47✔
1469
                        // We will allow TLSHandshakeFirst to be config reloaded. First,
47✔
1470
                        // we just want to detect if there was a change in the leafnodes{}
47✔
1471
                        // block, and if not, we will check the remotes.
47✔
1472
                        handshakeFirstChanged := tmpOld.TLSHandshakeFirst != tmpNew.TLSHandshakeFirst ||
47✔
1473
                                tmpOld.TLSHandshakeFirstFallback != tmpNew.TLSHandshakeFirstFallback
47✔
1474
                        // If changed, set them (in the temporary variables) to false so that the
47✔
1475
                        // rest of the comparison does not fail.
47✔
1476
                        if handshakeFirstChanged {
50✔
1477
                                tmpOld.TLSHandshakeFirst, tmpNew.TLSHandshakeFirst = false, false
3✔
1478
                                tmpOld.TLSHandshakeFirstFallback, tmpNew.TLSHandshakeFirstFallback = 0, 0
3✔
1479
                        } else if len(tmpOld.Remotes) == len(tmpNew.Remotes) {
91✔
1480
                                // Since we don't support changes in the remotes, we will do a
44✔
1481
                                // simple pass to see if there was a change of this field.
44✔
1482
                                for i := 0; i < len(tmpOld.Remotes); i++ {
58✔
1483
                                        if tmpOld.Remotes[i].TLSHandshakeFirst != tmpNew.Remotes[i].TLSHandshakeFirst {
15✔
1484
                                                handshakeFirstChanged = true
1✔
1485
                                                break
1✔
1486
                                        }
1487
                                }
1488
                        }
1489
                        // We also support config reload for compression. Check if it changed before
1490
                        // blanking them out for the deep-equal check at the end.
1491
                        compressionChanged := !compressOptsEqual(&tmpOld.Compression, &tmpNew.Compression)
47✔
1492
                        if compressionChanged {
85✔
1493
                                tmpOld.Compression, tmpNew.Compression = CompressionOpts{}, CompressionOpts{}
38✔
1494
                        } else if len(tmpOld.Remotes) == len(tmpNew.Remotes) {
56✔
1495
                                // Same that for tls first check, do the remotes now.
9✔
1496
                                for i := range len(tmpOld.Remotes) {
17✔
1497
                                        if !compressOptsEqual(&tmpOld.Remotes[i].Compression, &tmpNew.Remotes[i].Compression) {
16✔
1498
                                                compressionChanged = true
8✔
1499
                                                break
8✔
1500
                                        }
1501
                                }
1502
                        }
1503
                        // Check if the "disabled" option of each remote has changed.
1504
                        var disabledChanged bool
47✔
1505
                        for i := range len(tmpOld.Remotes) {
57✔
1506
                                if tmpOld.Remotes[i].Disabled != tmpNew.Remotes[i].Disabled {
14✔
1507
                                        disabledChanged = true
4✔
1508
                                        break
4✔
1509
                                }
1510
                        }
1511

1512
                        // Need to do the same for remote leafnodes' TLS configs.
1513
                        // But we can't just set remotes' TLSConfig to nil otherwise this
1514
                        // would lose the real TLS configuration.
1515
                        tmpOld.Remotes = copyRemoteLNConfigForReloadCompare(tmpOld.Remotes)
47✔
1516
                        tmpNew.Remotes = copyRemoteLNConfigForReloadCompare(tmpNew.Remotes)
47✔
1517

47✔
1518
                        // Special check for leafnode remotes changes which are not supported right now.
47✔
1519
                        leafRemotesChanged := func(a, b LeafNodeOpts) bool {
94✔
1520
                                if len(a.Remotes) != len(b.Remotes) {
47✔
1521
                                        return true
×
1522
                                }
×
1523

1524
                                // Check whether all remotes URLs are still the same.
1525
                                for _, oldRemote := range a.Remotes {
61✔
1526
                                        var found bool
14✔
1527

14✔
1528
                                        if oldRemote.LocalAccount == _EMPTY_ {
14✔
1529
                                                oldRemote.LocalAccount = globalAccountName
×
1530
                                        }
×
1531

1532
                                        for _, newRemote := range b.Remotes {
33✔
1533
                                                // Bind to global account in case not defined.
19✔
1534
                                                if newRemote.LocalAccount == _EMPTY_ {
30✔
1535
                                                        newRemote.LocalAccount = globalAccountName
11✔
1536
                                                }
11✔
1537

1538
                                                if reflect.DeepEqual(oldRemote, newRemote) {
33✔
1539
                                                        found = true
14✔
1540
                                                        break
14✔
1541
                                                }
1542
                                        }
1543
                                        if !found {
14✔
1544
                                                return true
×
1545
                                        }
×
1546
                                }
1547

1548
                                return false
47✔
1549
                        }
1550

1551
                        // First check whether remotes changed at all. If they did not,
1552
                        // skip them in the complete equal check.
1553
                        if !leafRemotesChanged(tmpOld, tmpNew) {
94✔
1554
                                tmpOld.Remotes = nil
47✔
1555
                                tmpNew.Remotes = nil
47✔
1556
                        }
47✔
1557

1558
                        // Special check for auth users to detect changes.
1559
                        // If anything is off will fall through and fail below.
1560
                        // If we detect they are semantically the same we nil them out
1561
                        // to pass the check below.
1562
                        if tmpOld.Users != nil || tmpNew.Users != nil {
48✔
1563
                                if len(tmpOld.Users) == len(tmpNew.Users) {
2✔
1564
                                        oua := make(map[string]*User, len(tmpOld.Users))
1✔
1565
                                        nua := make(map[string]*User, len(tmpOld.Users))
1✔
1566
                                        for _, u := range tmpOld.Users {
2✔
1567
                                                oua[u.Username] = u
1✔
1568
                                        }
1✔
1569
                                        for _, u := range tmpNew.Users {
2✔
1570
                                                nua[u.Username] = u
1✔
1571
                                        }
1✔
1572
                                        same := true
1✔
1573
                                        for uname, u := range oua {
2✔
1574
                                                // If we can not find new one with same name, drop through to fail.
1✔
1575
                                                nu, ok := nua[uname]
1✔
1576
                                                if !ok {
1✔
1577
                                                        same = false
×
1578
                                                        break
×
1579
                                                }
1580
                                                // If username or password or account different break.
1581
                                                if u.Username != nu.Username || u.Password != nu.Password || u.Account.GetName() != nu.Account.GetName() {
1✔
1582
                                                        same = false
×
1583
                                                        break
×
1584
                                                }
1585
                                        }
1586
                                        // We can nil out here.
1587
                                        if same {
2✔
1588
                                                tmpOld.Users, tmpNew.Users = nil, nil
1✔
1589
                                        }
1✔
1590
                                }
1591
                        }
1592

1593
                        // If there is really a change prevents reload.
1594
                        if !reflect.DeepEqual(tmpOld, tmpNew) {
47✔
1595
                                // See TODO(ik) note below about printing old/new values.
×
1596
                                return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v",
×
1597
                                        field.Name, oldValue, newValue)
×
1598
                        }
×
1599

1600
                        diffOpts = append(diffOpts, &leafNodeOption{
47✔
1601
                                tlsFirstChanged:    handshakeFirstChanged,
47✔
1602
                                compressionChanged: compressionChanged,
47✔
1603
                                disabledChanged:    disabledChanged,
47✔
1604
                        })
47✔
1605
                case "jetstream":
3✔
1606
                        new := newValue.(bool)
3✔
1607
                        old := oldValue.(bool)
3✔
1608
                        if new != old {
6✔
1609
                                diffOpts = append(diffOpts, &jetStreamOption{newValue: new})
3✔
1610
                        }
3✔
1611

1612
                        // Mark whether JS will be disabled.
1613
                        disableJS = !new
3✔
1614
                case "storedir":
3✔
1615
                        new := newValue.(string)
3✔
1616
                        old := oldValue.(string)
3✔
1617
                        modified := new != old
3✔
1618

3✔
1619
                        // Check whether JS is being disabled and/or storage dir attempted to change.
3✔
1620
                        if jsEnabled && modified {
5✔
1621
                                if new == _EMPTY_ {
4✔
1622
                                        // This means that either JS is being disabled or it is using an temp dir.
2✔
1623
                                        // Allow the change but error in case JS was not disabled.
2✔
1624
                                        jsStoreDirChanged = true
2✔
1625
                                } else {
2✔
1626
                                        return nil, fmt.Errorf("config reload not supported for jetstream storage directory")
×
1627
                                }
×
1628
                        }
1629
                case "jetstreammaxmemory", "jetstreammaxstore":
6✔
1630
                        old := oldValue.(int64)
6✔
1631
                        new := newValue.(int64)
6✔
1632

6✔
1633
                        // Check whether JS is being disabled and/or limits are being changed.
6✔
1634
                        var (
6✔
1635
                                modified  = new != old
6✔
1636
                                fromUnset = old == -1
6✔
1637
                                fromSet   = !fromUnset
6✔
1638
                                toUnset   = new == -1
6✔
1639
                                toSet     = !toUnset
6✔
1640
                        )
6✔
1641
                        if jsEnabled && modified {
10✔
1642
                                // Cannot change limits from dynamic storage at runtime.
4✔
1643
                                switch {
4✔
1644
                                case fromSet && toUnset:
4✔
1645
                                        // Limits changed but it may mean that JS is being disabled,
4✔
1646
                                        // keep track of the change and error in case it is not.
4✔
1647
                                        switch optName {
4✔
1648
                                        case "jetstreammaxmemory":
2✔
1649
                                                jsMemLimitsChanged = true
2✔
1650
                                        case "jetstreammaxstore":
2✔
1651
                                                jsFileLimitsChanged = true
2✔
1652
                                        default:
×
1653
                                                return nil, fmt.Errorf("config reload not supported for jetstream max memory and store")
×
1654
                                        }
1655
                                case fromUnset && toSet:
×
1656
                                        // Prevent changing from dynamic max memory / file at runtime.
×
1657
                                        return nil, fmt.Errorf("config reload not supported for jetstream dynamic max memory and store")
×
1658
                                default:
×
1659
                                        return nil, fmt.Errorf("config reload not supported for jetstream max memory and store")
×
1660
                                }
1661
                        }
1662
                case "jetstreammetacompact", "jetstreammetacompactsize":
2✔
1663
                        // Allowed at runtime but monitorCluster looks at s.opts directly, so no further work needed here.
1664
                case "websocket":
×
1665
                        // Similar to gateways
×
1666
                        tmpOld := oldValue.(WebsocketOpts)
×
1667
                        tmpNew := newValue.(WebsocketOpts)
×
1668
                        tmpOld.TLSConfig, tmpOld.tlsConfigOpts = nil, nil
×
1669
                        tmpNew.TLSConfig, tmpNew.tlsConfigOpts = nil, nil
×
1670
                        // If there is really a change prevents reload.
×
1671
                        if !reflect.DeepEqual(tmpOld, tmpNew) {
×
1672
                                // See TODO(ik) note below about printing old/new values.
×
1673
                                return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v",
×
1674
                                        field.Name, oldValue, newValue)
×
1675
                        }
×
1676
                case "mqtt":
5✔
1677
                        diffOpts = append(diffOpts, &mqttAckWaitReload{newValue: newValue.(MQTTOpts).AckWait})
5✔
1678
                        diffOpts = append(diffOpts, &mqttMaxAckPendingReload{newValue: newValue.(MQTTOpts).MaxAckPending})
5✔
1679
                        diffOpts = append(diffOpts, &mqttStreamReplicasReload{newValue: newValue.(MQTTOpts).StreamReplicas})
5✔
1680
                        diffOpts = append(diffOpts, &mqttConsumerReplicasReload{newValue: newValue.(MQTTOpts).ConsumerReplicas})
5✔
1681
                        diffOpts = append(diffOpts, &mqttConsumerMemoryStorageReload{newValue: newValue.(MQTTOpts).ConsumerMemoryStorage})
5✔
1682
                        diffOpts = append(diffOpts, &mqttInactiveThresholdReload{newValue: newValue.(MQTTOpts).ConsumerInactiveThreshold})
5✔
1683

5✔
1684
                        // Nil out/set to 0 the options that we allow to be reloaded so that
5✔
1685
                        // we only fail reload if some that we don't support are changed.
5✔
1686
                        tmpOld := oldValue.(MQTTOpts)
5✔
1687
                        tmpNew := newValue.(MQTTOpts)
5✔
1688
                        tmpOld.TLSConfig, tmpOld.tlsConfigOpts, tmpOld.AckWait, tmpOld.MaxAckPending, tmpOld.StreamReplicas, tmpOld.ConsumerReplicas, tmpOld.ConsumerMemoryStorage = nil, nil, 0, 0, 0, 0, false
5✔
1689
                        tmpOld.ConsumerInactiveThreshold = 0
5✔
1690
                        tmpNew.TLSConfig, tmpNew.tlsConfigOpts, tmpNew.AckWait, tmpNew.MaxAckPending, tmpNew.StreamReplicas, tmpNew.ConsumerReplicas, tmpNew.ConsumerMemoryStorage = nil, nil, 0, 0, 0, 0, false
5✔
1691
                        tmpNew.ConsumerInactiveThreshold = 0
5✔
1692

5✔
1693
                        if !reflect.DeepEqual(tmpOld, tmpNew) {
5✔
1694
                                // See TODO(ik) note below about printing old/new values.
×
1695
                                return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v",
×
1696
                                        field.Name, oldValue, newValue)
×
1697
                        }
×
1698
                        tmpNew.AckWait = newValue.(MQTTOpts).AckWait
5✔
1699
                        tmpNew.MaxAckPending = newValue.(MQTTOpts).MaxAckPending
5✔
1700
                        tmpNew.StreamReplicas = newValue.(MQTTOpts).StreamReplicas
5✔
1701
                        tmpNew.ConsumerReplicas = newValue.(MQTTOpts).ConsumerReplicas
5✔
1702
                        tmpNew.ConsumerMemoryStorage = newValue.(MQTTOpts).ConsumerMemoryStorage
5✔
1703
                        tmpNew.ConsumerInactiveThreshold = newValue.(MQTTOpts).ConsumerInactiveThreshold
5✔
1704
                case "connecterrorreports":
1✔
1705
                        diffOpts = append(diffOpts, &connectErrorReports{newValue: newValue.(int)})
1✔
1706
                case "reconnecterrorreports":
1✔
1707
                        diffOpts = append(diffOpts, &reconnectErrorReports{newValue: newValue.(int)})
1✔
1708
                case "nolog", "nosigs":
366✔
1709
                        // Ignore NoLog and NoSigs options since they are not parsed and only used in
366✔
1710
                        // testing.
366✔
1711
                        continue
366✔
1712
                case "disableshortfirstping":
×
1713
                        newOpts.DisableShortFirstPing = oldValue.(bool)
×
1714
                        continue
×
1715
                case "maxtracedmsglen":
×
1716
                        diffOpts = append(diffOpts, &maxTracedMsgLenOption{newValue: newValue.(int)})
×
1717
                case "port":
3✔
1718
                        // check to see if newValue == 0 and continue if so.
3✔
1719
                        if newValue == 0 {
3✔
1720
                                // ignore RANDOM_PORT
×
1721
                                continue
×
1722
                        }
1723
                        fallthrough
3✔
1724
                case "noauthuser":
10✔
1725
                        if oldValue != _EMPTY_ && newValue == _EMPTY_ {
17✔
1726
                                for _, user := range newOpts.Users {
15✔
1727
                                        if user.Username == oldValue {
8✔
1728
                                                return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v",
×
1729
                                                        field.Name, oldValue, newValue)
×
1730
                                        }
×
1731
                                }
1732
                        } else {
3✔
1733
                                return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v",
3✔
1734
                                        field.Name, oldValue, newValue)
3✔
1735
                        }
3✔
1736
                case "defaultsentinel":
1✔
1737
                        diffOpts = append(diffOpts, &defaultSentinelOption{newValue: newValue.(string)})
1✔
1738
                case "systemaccount":
1,007✔
1739
                        if oldValue != DEFAULT_SYSTEM_ACCOUNT || newValue != _EMPTY_ {
1,007✔
1740
                                return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v",
×
1741
                                        field.Name, oldValue, newValue)
×
1742
                        }
×
1743
                case "ocspconfig":
2✔
1744
                        diffOpts = append(diffOpts, &ocspOption{newValue: newValue.(*OCSPConfig)})
2✔
1745
                case "ocspcacheconfig":
1✔
1746
                        diffOpts = append(diffOpts, &ocspResponseCacheOption{newValue: newValue.(*OCSPResponseCacheConfig)})
1✔
1747
                case "profblockrate":
×
1748
                        new := newValue.(int)
×
1749
                        old := oldValue.(int)
×
1750
                        if new != old {
×
1751
                                diffOpts = append(diffOpts, &profBlockRateReload{newValue: new})
×
1752
                        }
×
1753
                case "configdigest":
×
1754
                        // skip changes in config digest, this is handled already while
×
1755
                        // processing the config.
×
1756
                        continue
×
1757
                case "nofastproducerstall":
×
1758
                        diffOpts = append(diffOpts, &noFastProdStallReload{noStall: newValue.(bool)})
×
1759
                case "proxies":
1✔
1760
                        new := newValue.(*ProxiesConfig)
1✔
1761
                        old := oldValue.(*ProxiesConfig)
1✔
1762
                        if add, del := diffProxiesTrustedKeys(old.Trusted, new.Trusted); len(add) > 0 || len(del) > 0 {
2✔
1763
                                diffOpts = append(diffOpts, &proxiesReload{add: add, del: del})
1✔
1764
                        }
1✔
1765
                default:
2✔
1766
                        // TODO(ik): Implement String() on those options to have a nice print.
2✔
1767
                        // %v is difficult to figure what's what, %+v print private fields and
2✔
1768
                        // would print passwords. Tried json.Marshal but it is too verbose for
2✔
1769
                        // the URL array.
2✔
1770

2✔
1771
                        // Bail out if attempting to reload any unsupported options.
2✔
1772
                        return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v",
2✔
1773
                                field.Name, oldValue, newValue)
2✔
1774
                }
1775
        }
1776

1777
        // If not disabling JS but limits have changed then it is an error.
1778
        if !disableJS {
2,688✔
1779
                if jsMemLimitsChanged || jsFileLimitsChanged {
1,343✔
1780
                        return nil, fmt.Errorf("config reload not supported for jetstream max memory and max store")
×
1781
                }
×
1782
                if jsStoreDirChanged {
1,343✔
1783
                        return nil, fmt.Errorf("config reload not supported for jetstream storage dir")
×
1784
                }
×
1785
        }
1786

1787
        return diffOpts, nil
1,345✔
1788
}
1789

1790
func copyRemoteGWConfigsWithoutTLSConfig(current []*RemoteGatewayOpts) []*RemoteGatewayOpts {
126✔
1791
        l := len(current)
126✔
1792
        if l == 0 {
168✔
1793
                return nil
42✔
1794
        }
42✔
1795
        rgws := make([]*RemoteGatewayOpts, 0, l)
84✔
1796
        for _, rcfg := range current {
168✔
1797
                cp := *rcfg
84✔
1798
                cp.TLSConfig = nil
84✔
1799
                cp.tlsConfigOpts = nil
84✔
1800
                rgws = append(rgws, &cp)
84✔
1801
        }
84✔
1802
        return rgws
84✔
1803
}
1804

1805
func copyRemoteLNConfigForReloadCompare(current []*RemoteLeafOpts) []*RemoteLeafOpts {
94✔
1806
        l := len(current)
94✔
1807
        if l == 0 {
170✔
1808
                return nil
76✔
1809
        }
76✔
1810
        rlns := make([]*RemoteLeafOpts, 0, l)
18✔
1811
        for _, rcfg := range current {
46✔
1812
                cp := *rcfg
28✔
1813
                cp.TLSConfig = nil
28✔
1814
                cp.tlsConfigOpts = nil
28✔
1815
                cp.TLSHandshakeFirst = false
28✔
1816
                // This is set only when processing a CONNECT, so reset here so that we
28✔
1817
                // don't fail the DeepEqual comparison.
28✔
1818
                cp.TLS = false
28✔
1819
                // For now, remove DenyImports/Exports since those get modified at runtime
28✔
1820
                // to add JS APIs.
28✔
1821
                cp.DenyImports, cp.DenyExports = nil, nil
28✔
1822
                // Remove compression mode
28✔
1823
                cp.Compression = CompressionOpts{}
28✔
1824
                // Reset disabled status
28✔
1825
                cp.Disabled = false
28✔
1826
                rlns = append(rlns, &cp)
28✔
1827
        }
28✔
1828
        return rlns
18✔
1829
}
1830

1831
func (s *Server) applyOptions(ctx *reloadContext, opts []option) {
1,343✔
1832
        var (
1,343✔
1833
                reloadLogging      = false
1,343✔
1834
                reloadAuth         = false
1,343✔
1835
                reloadClusterPerms = false
1,343✔
1836
                reloadClientTrcLvl = false
1,343✔
1837
                reloadJetstream    = false
1,343✔
1838
                jsEnabled          = false
1,343✔
1839
                isStatszChange     = false
1,343✔
1840
                co                 *clusterOption
1,343✔
1841
        )
1,343✔
1842
        for _, opt := range opts {
4,447✔
1843
                opt.Apply(s)
3,104✔
1844
                if opt.IsLoggingChange() {
3,121✔
1845
                        reloadLogging = true
17✔
1846
                }
17✔
1847
                if opt.IsTraceLevelChange() {
3,109✔
1848
                        reloadClientTrcLvl = true
5✔
1849
                }
5✔
1850
                if opt.IsAuthChange() {
5,992✔
1851
                        reloadAuth = true
2,888✔
1852
                }
2,888✔
1853
                if opt.IsClusterPoolSizeOrAccountsChange() {
3,147✔
1854
                        co = opt.(*clusterOption)
43✔
1855
                }
43✔
1856
                if opt.IsClusterPermsChange() {
3,153✔
1857
                        reloadClusterPerms = true
49✔
1858
                }
49✔
1859
                if opt.IsJetStreamChange() {
3,107✔
1860
                        reloadJetstream = true
3✔
1861
                        jsEnabled = opt.(*jetStreamOption).newValue
3✔
1862
                }
3✔
1863
                if opt.IsStatszChange() {
3,109✔
1864
                        isStatszChange = true
5✔
1865
                }
5✔
1866
        }
1867

1868
        if reloadLogging {
1,348✔
1869
                s.ConfigureLogger()
5✔
1870
        }
5✔
1871
        if reloadClientTrcLvl {
1,346✔
1872
                s.reloadClientTraceLevel()
3✔
1873
        }
3✔
1874
        if reloadAuth {
2,686✔
1875
                s.reloadAuthorization()
1,343✔
1876
        }
1,343✔
1877
        if reloadClusterPerms {
1,392✔
1878
                s.reloadClusterPermissions(ctx.oldClusterPerms)
49✔
1879
        }
49✔
1880
        newOpts := s.getOpts()
1,343✔
1881
        // If we need to reload cluster pool/per-account, then co will be not nil
1,343✔
1882
        if co != nil {
1,386✔
1883
                s.reloadClusterPoolAndAccounts(co, newOpts)
43✔
1884
        }
43✔
1885
        if reloadJetstream {
1,346✔
1886
                if !jsEnabled {
5✔
1887
                        s.DisableJetStream()
2✔
1888
                } else if !s.JetStreamEnabled() {
4✔
1889
                        if err := s.restartJetStream(); err != nil {
1✔
1890
                                s.Warnf("Can't start JetStream: %v", err)
×
1891
                        }
×
1892
                }
1893
                // Make sure to reset the internal loop's version of JS.
1894
                s.resetInternalLoopInfo()
3✔
1895
        }
1896
        if isStatszChange {
1,348✔
1897
                s.sendStatszUpdate()
5✔
1898
        }
5✔
1899

1900
        // For remote gateways and leafnodes, make sure that their TLS configuration
1901
        // is updated (since the config is "captured" early and changes would otherwise
1902
        // not be visible).
1903
        if s.gateway.enabled {
1,412✔
1904
                s.gateway.updateRemotesTLSConfig(newOpts)
69✔
1905
        }
69✔
1906
        if len(newOpts.LeafNode.Remotes) > 0 {
1,352✔
1907
                s.updateRemoteLeafNodesTLSConfig(newOpts)
9✔
1908
        }
9✔
1909

1910
        // Always restart OCSP monitoring on reload.
1911
        if err := s.reloadOCSP(); err != nil {
1,344✔
1912
                s.Warnf("Can't restart OCSP features: %v", err)
1✔
1913
        }
1✔
1914
        var cd string
1,343✔
1915
        if newOpts.configDigest != "" {
2,685✔
1916
                cd = fmt.Sprintf("(%s)", newOpts.configDigest)
1,342✔
1917
        }
1,342✔
1918
        s.Noticef("Reloaded server configuration %s", cd)
1,343✔
1919
}
1920

1921
// This will send a reset to the internal send loop.
1922
func (s *Server) resetInternalLoopInfo() {
3✔
1923
        var resetCh chan struct{}
3✔
1924
        s.mu.Lock()
3✔
1925
        if s.sys != nil {
6✔
1926
                // can't hold the lock as go routine reading it may be waiting for lock as well
3✔
1927
                resetCh = s.sys.resetCh
3✔
1928
        }
3✔
1929
        s.mu.Unlock()
3✔
1930

3✔
1931
        if resetCh != nil {
6✔
1932
                resetCh <- struct{}{}
3✔
1933
        }
3✔
1934
}
1935

1936
// Update all cached debug and trace settings for every client
1937
func (s *Server) reloadClientTraceLevel() {
3✔
1938
        opts := s.getOpts()
3✔
1939

3✔
1940
        if opts.NoLog {
3✔
1941
                return
×
1942
        }
×
1943

1944
        // Create a list of all clients.
1945
        // Update their trace level when not holding server or gateway lock
1946

1947
        s.mu.Lock()
3✔
1948
        clientCnt := 1 + len(s.clients) + len(s.grTmpClients) + s.numRoutes() + len(s.leafs)
3✔
1949
        s.mu.Unlock()
3✔
1950

3✔
1951
        s.gateway.RLock()
3✔
1952
        clientCnt += len(s.gateway.in) + len(s.gateway.outo)
3✔
1953
        s.gateway.RUnlock()
3✔
1954

3✔
1955
        clients := make([]*client, 0, clientCnt)
3✔
1956

3✔
1957
        s.mu.Lock()
3✔
1958
        if s.eventsEnabled() {
6✔
1959
                clients = append(clients, s.sys.client)
3✔
1960
        }
3✔
1961

1962
        cMaps := []map[uint64]*client{s.clients, s.grTmpClients, s.leafs}
3✔
1963
        for _, m := range cMaps {
12✔
1964
                for _, c := range m {
9✔
1965
                        clients = append(clients, c)
×
1966
                }
×
1967
        }
1968
        s.forEachRoute(func(c *client) {
3✔
1969
                clients = append(clients, c)
×
1970
        })
×
1971
        s.mu.Unlock()
3✔
1972

3✔
1973
        s.gateway.RLock()
3✔
1974
        for _, c := range s.gateway.in {
3✔
1975
                clients = append(clients, c)
×
1976
        }
×
1977
        clients = append(clients, s.gateway.outo...)
3✔
1978
        s.gateway.RUnlock()
3✔
1979

3✔
1980
        for _, c := range clients {
6✔
1981
                // client.trace is commonly read while holding the lock
3✔
1982
                c.mu.Lock()
3✔
1983
                c.setTraceLevel()
3✔
1984
                c.mu.Unlock()
3✔
1985
        }
3✔
1986
}
1987

1988
// reloadAuthorization reconfigures the server authorization settings,
1989
// disconnects any clients who are no longer authorized, and removes any
1990
// unauthorized subscriptions.
1991
func (s *Server) reloadAuthorization() {
1,344✔
1992
        // This map will contain the names of accounts that have their streams
1,344✔
1993
        // import configuration changed.
1,344✔
1994
        var awcsti map[string]struct{}
1,344✔
1995
        checkJetStream := false
1,344✔
1996
        opts := s.getOpts()
1,344✔
1997
        s.mu.Lock()
1,344✔
1998

1,344✔
1999
        deletedAccounts := make(map[string]*Account)
1,344✔
2000

1,344✔
2001
        // This can not be changed for now so ok to check server's trustedKeys unlocked.
1,344✔
2002
        // If plain configured accounts, process here.
1,344✔
2003
        if s.trustedKeys == nil {
2,676✔
2004
                // Make a map of the configured account names so we figure out the accounts
1,332✔
2005
                // that should be removed later on.
1,332✔
2006
                configAccs := make(map[string]struct{}, len(opts.Accounts))
1,332✔
2007
                for _, acc := range opts.Accounts {
4,723✔
2008
                        configAccs[acc.GetName()] = struct{}{}
3,391✔
2009
                }
3,391✔
2010
                // Now range over existing accounts and keep track of the ones deleted
2011
                // so some cleanup can be made after releasing the server lock.
2012
                s.accounts.Range(func(k, v any) bool {
6,285✔
2013
                        an, acc := k.(string), v.(*Account)
4,953✔
2014
                        // Exclude default and system account from this test since those
4,953✔
2015
                        // may not actually be in opts.Accounts.
4,953✔
2016
                        if an == DEFAULT_GLOBAL_ACCOUNT || an == DEFAULT_SYSTEM_ACCOUNT {
7,525✔
2017
                                return true
2,572✔
2018
                        }
2,572✔
2019
                        // Check check if existing account is still in opts.Accounts.
2020
                        if _, ok := configAccs[an]; !ok {
2,384✔
2021
                                deletedAccounts[an] = acc
3✔
2022
                                s.accounts.Delete(k)
3✔
2023
                        }
3✔
2024
                        return true
2,381✔
2025
                })
2026
                // This will update existing and add new ones.
2027
                awcsti, _ = s.configureAccounts(true)
1,332✔
2028
                s.configureAuthorization()
1,332✔
2029
                // Double check any JetStream configs.
1,332✔
2030
                checkJetStream = s.getJetStream() != nil
1,332✔
2031
        } else if opts.AccountResolver != nil {
24✔
2032
                s.configureResolver()
12✔
2033
                if _, ok := s.accResolver.(*MemAccResolver); ok {
21✔
2034
                        // Check preloads so we can issue warnings etc if needed.
9✔
2035
                        s.checkResolvePreloads()
9✔
2036
                        // With a memory resolver we want to do something similar to configured accounts.
9✔
2037
                        // We will walk the accounts and delete them if they are no longer present via fetch.
9✔
2038
                        // If they are present we will force a claim update to process changes.
9✔
2039
                        s.accounts.Range(func(k, v any) bool {
38✔
2040
                                acc := v.(*Account)
29✔
2041
                                // Skip global account.
29✔
2042
                                if acc == s.gacc {
38✔
2043
                                        return true
9✔
2044
                                }
9✔
2045
                                accName := acc.GetName()
20✔
2046
                                // Release server lock for following actions
20✔
2047
                                s.mu.Unlock()
20✔
2048
                                accClaims, claimJWT, _ := s.fetchAccountClaims(accName)
20✔
2049
                                if accClaims != nil {
38✔
2050
                                        if err := s.updateAccountWithClaimJWT(acc, claimJWT); err != nil {
18✔
2051
                                                s.Noticef("Reloaded: deleting account [bad claims]: %q", accName)
×
2052
                                                s.accounts.Delete(k)
×
2053
                                        }
×
2054
                                } else {
2✔
2055
                                        s.Noticef("Reloaded: deleting account [removed]: %q", accName)
2✔
2056
                                        s.accounts.Delete(k)
2✔
2057
                                }
2✔
2058
                                // Regrab server lock.
2059
                                s.mu.Lock()
20✔
2060
                                return true
20✔
2061
                        })
2062
                }
2063
        }
2064

2065
        var (
1,344✔
2066
                cclientsa [64]*client
1,344✔
2067
                cclients  = cclientsa[:0]
1,344✔
2068
                clientsa  [64]*client
1,344✔
2069
                clients   = clientsa[:0]
1,344✔
2070
                routesa   [64]*client
1,344✔
2071
                routes    = routesa[:0]
1,344✔
2072
        )
1,344✔
2073

1,344✔
2074
        // Gather clients that changed accounts. We will close them and they
1,344✔
2075
        // will reconnect, doing the right thing.
1,344✔
2076
        for _, client := range s.clients {
3,579✔
2077
                if s.clientHasMovedToDifferentAccount(client) {
2,246✔
2078
                        cclients = append(cclients, client)
11✔
2079
                } else {
2,235✔
2080
                        clients = append(clients, client)
2,224✔
2081
                }
2,224✔
2082
        }
2083
        s.forEachRoute(func(route *client) {
1,806✔
2084
                routes = append(routes, route)
462✔
2085
        })
462✔
2086
        // Check here for any system/internal clients which will not be in the servers map of normal clients.
2087
        if s.sys != nil && s.sys.account != nil && !opts.NoSystemAccount {
2,659✔
2088
                s.accounts.Store(s.sys.account.Name, s.sys.account)
1,315✔
2089
        }
1,315✔
2090

2091
        s.accounts.Range(func(k, v any) bool {
6,328✔
2092
                acc := v.(*Account)
4,984✔
2093
                acc.mu.RLock()
4,984✔
2094
                // Check for sysclients accounting, ignore the system account.
4,984✔
2095
                if acc.sysclients > 0 && (s.sys == nil || s.sys.account != acc) {
4,997✔
2096
                        for c := range acc.clients {
103✔
2097
                                if c.kind != CLIENT && c.kind != LEAF {
171✔
2098
                                        clients = append(clients, c)
81✔
2099
                                }
81✔
2100
                        }
2101
                }
2102
                acc.mu.RUnlock()
4,984✔
2103
                return true
4,984✔
2104
        })
2105

2106
        var resetCh chan struct{}
1,344✔
2107
        if s.sys != nil {
2,659✔
2108
                // can't hold the lock as go routine reading it may be waiting for lock as well
1,315✔
2109
                resetCh = s.sys.resetCh
1,315✔
2110
        }
1,315✔
2111
        s.mu.Unlock()
1,344✔
2112

1,344✔
2113
        // Clear some timers and remove service import subs for deleted accounts.
1,344✔
2114
        for _, acc := range deletedAccounts {
1,347✔
2115
                acc.mu.Lock()
3✔
2116
                clearTimer(&acc.etmr)
3✔
2117
                clearTimer(&acc.ctmr)
3✔
2118
                for _, se := range acc.exports.services {
3✔
2119
                        se.clearResponseThresholdTimer()
×
2120
                }
×
2121
                acc.mu.Unlock()
3✔
2122
                acc.removeAllServiceImportSubs()
3✔
2123
        }
2124

2125
        if resetCh != nil {
2,659✔
2126
                resetCh <- struct{}{}
1,315✔
2127
        }
1,315✔
2128

2129
        // Close clients that have moved accounts
2130
        for _, client := range cclients {
1,355✔
2131
                client.closeConnection(ClientClosed)
11✔
2132
        }
11✔
2133

2134
        for _, c := range clients {
3,649✔
2135
                // Disconnect any unauthorized clients.
2,305✔
2136
                // Ignore internal clients.
2,305✔
2137
                if (c.kind == CLIENT || c.kind == LEAF) && !s.isClientAuthorized(c) {
2,311✔
2138
                        c.authViolation()
6✔
2139
                        continue
6✔
2140
                }
2141
                // Check to make sure account is correct.
2142
                c.swapAccountAfterReload()
2,299✔
2143
                // Remove any unauthorized subscriptions and check for account imports.
2,299✔
2144
                c.processSubsOnConfigReload(awcsti)
2,299✔
2145
        }
2146

2147
        for _, route := range routes {
1,806✔
2148
                // Disconnect any unauthorized routes.
462✔
2149
                // Do this only for routes that were accepted, not initiated
462✔
2150
                // because in the later case, we don't have the user name/password
462✔
2151
                // of the remote server.
462✔
2152
                if !route.isSolicitedRoute() && !s.isRouterAuthorized(route) {
465✔
2153
                        route.setNoReconnect()
3✔
2154
                        route.authViolation()
3✔
2155
                }
3✔
2156
        }
2157

2158
        if res := s.AccountResolver(); res != nil {
1,356✔
2159
                res.Reload()
12✔
2160
        }
12✔
2161

2162
        // We will double check all JetStream configs on a reload.
2163
        if checkJetStream {
1,370✔
2164
                if err := s.enableJetStreamAccounts(); err != nil {
26✔
2165
                        s.Errorf(err.Error())
×
2166
                }
×
2167
        }
2168

2169
        // Check that publish retained messages sources are still allowed to publish.
2170
        // Do this after dealing with JetStream.
2171
        s.mqttCheckPubRetainedPerms()
1,344✔
2172
}
2173

2174
// Returns true if given client current account has changed (or user
2175
// no longer exist) in the new config, false if the user did not
2176
// change accounts.
2177
// Server lock is held on entry.
2178
func (s *Server) clientHasMovedToDifferentAccount(c *client) bool {
2,235✔
2179
        var (
2,235✔
2180
                nu *NkeyUser
2,235✔
2181
                u  *User
2,235✔
2182
        )
2,235✔
2183
        c.mu.Lock()
2,235✔
2184
        defer c.mu.Unlock()
2,235✔
2185
        if c.opts.Nkey != _EMPTY_ {
2,237✔
2186
                if s.nkeys != nil {
4✔
2187
                        nu = s.nkeys[c.opts.Nkey]
2✔
2188
                }
2✔
2189
        } else if c.opts.Username != _EMPTY_ {
4,418✔
2190
                if s.users != nil {
4,367✔
2191
                        u = s.users[c.opts.Username]
2,182✔
2192
                }
2,182✔
2193
        } else {
48✔
2194
                return false
48✔
2195
        }
48✔
2196
        // Get the current account name
2197
        var curAccName string
2,187✔
2198
        if c.acc != nil {
4,374✔
2199
                curAccName = c.acc.Name
2,187✔
2200
        }
2,187✔
2201
        if nu != nil && nu.Account != nil {
2,189✔
2202
                return curAccName != nu.Account.Name
2✔
2203
        } else if u != nil && u.Account != nil {
4,367✔
2204
                return curAccName != u.Account.Name
2,180✔
2205
        }
2,180✔
2206
        // user/nkey no longer exists.
2207
        return true
5✔
2208
}
2209

2210
// reloadClusterPermissions reconfigures the cluster's permssions
2211
// and set the permissions to all existing routes, sending an
2212
// update INFO protocol so that remote can resend their local
2213
// subs if needed, and sending local subs matching cluster's
2214
// import subjects.
2215
func (s *Server) reloadClusterPermissions(oldPerms *RoutePermissions) {
49✔
2216
        s.mu.Lock()
49✔
2217
        newPerms := s.getOpts().Cluster.Permissions
49✔
2218
        routes := make(map[uint64]*client, s.numRoutes())
49✔
2219
        // Get all connected routes
49✔
2220
        s.forEachRoute(func(route *client) {
242✔
2221
                route.mu.Lock()
193✔
2222
                routes[route.cid] = route
193✔
2223
                route.mu.Unlock()
193✔
2224
        })
193✔
2225
        // If new permissions is nil, then clear routeInfo import/export
2226
        if newPerms == nil {
51✔
2227
                s.routeInfo.Import = nil
2✔
2228
                s.routeInfo.Export = nil
2✔
2229
        } else {
49✔
2230
                s.routeInfo.Import = newPerms.Import
47✔
2231
                s.routeInfo.Export = newPerms.Export
47✔
2232
        }
47✔
2233
        infoJSON := generateInfoJSON(&s.routeInfo)
49✔
2234
        s.mu.Unlock()
49✔
2235

49✔
2236
        // Close connections for routes that don't understand async INFO.
49✔
2237
        for _, route := range routes {
242✔
2238
                route.mu.Lock()
193✔
2239
                close := route.opts.Protocol < RouteProtoInfo
193✔
2240
                cid := route.cid
193✔
2241
                route.mu.Unlock()
193✔
2242
                if close {
201✔
2243
                        route.closeConnection(RouteRemoved)
8✔
2244
                        delete(routes, cid)
8✔
2245
                }
8✔
2246
        }
2247

2248
        // If there are no route left, we are done
2249
        if len(routes) == 0 {
50✔
2250
                return
1✔
2251
        }
1✔
2252

2253
        // Fake clients to test cluster permissions
2254
        oldPermsTester := &client{}
48✔
2255
        oldPermsTester.setRoutePermissions(oldPerms)
48✔
2256
        newPermsTester := &client{}
48✔
2257
        newPermsTester.setRoutePermissions(newPerms)
48✔
2258

48✔
2259
        var (
48✔
2260
                _localSubs       [4096]*subscription
48✔
2261
                subsNeedSUB      = map[*client][]*subscription{}
48✔
2262
                subsNeedUNSUB    = map[*client][]*subscription{}
48✔
2263
                deleteRoutedSubs []*subscription
48✔
2264
        )
48✔
2265

48✔
2266
        getRouteForAccount := func(accName string, poolIdx int) *client {
257✔
2267
                for _, r := range routes {
727✔
2268
                        r.mu.Lock()
518✔
2269
                        ok := (poolIdx >= 0 && poolIdx == r.route.poolIdx) || (string(r.route.accName) == accName) || r.route.noPool
518✔
2270
                        r.mu.Unlock()
518✔
2271
                        if ok {
727✔
2272
                                return r
209✔
2273
                        }
209✔
2274
                }
2275
                return nil
×
2276
        }
2277

2278
        // First set the new permissions on all routes.
2279
        for _, route := range routes {
233✔
2280
                route.mu.Lock()
185✔
2281
                route.setRoutePermissions(newPerms)
185✔
2282
                route.mu.Unlock()
185✔
2283
        }
185✔
2284

2285
        // Then, go over all accounts and gather local subscriptions that need to be
2286
        // sent over as SUB or removed as UNSUB, and routed subscriptions that need
2287
        // to be dropped due to export permissions.
2288
        s.accounts.Range(func(_, v any) bool {
257✔
2289
                acc := v.(*Account)
209✔
2290
                acc.mu.RLock()
209✔
2291
                accName, sl, poolIdx := acc.Name, acc.sl, acc.routePoolIdx
209✔
2292
                acc.mu.RUnlock()
209✔
2293
                // Get the route handling this account. If no route or sublist, bail out.
209✔
2294
                route := getRouteForAccount(accName, poolIdx)
209✔
2295
                if route == nil || sl == nil {
209✔
2296
                        return true
×
2297
                }
×
2298
                localSubs := _localSubs[:0]
209✔
2299
                sl.localSubs(&localSubs, false)
209✔
2300

209✔
2301
                // Go through all local subscriptions
209✔
2302
                for _, sub := range localSubs {
2,787✔
2303
                        // Get all subs that can now be imported
2,578✔
2304
                        subj := string(sub.subject)
2,578✔
2305
                        couldImportThen := oldPermsTester.canImport(subj)
2,578✔
2306
                        canImportNow := newPermsTester.canImport(subj)
2,578✔
2307
                        if canImportNow {
3,919✔
2308
                                // If we could not before, then will need to send a SUB protocol.
1,341✔
2309
                                if !couldImportThen {
1,362✔
2310
                                        subsNeedSUB[route] = append(subsNeedSUB[route], sub)
21✔
2311
                                }
21✔
2312
                        } else if couldImportThen {
1,257✔
2313
                                // We were previously able to import this sub, but now
20✔
2314
                                // we can't so we need to send an UNSUB protocol
20✔
2315
                                subsNeedUNSUB[route] = append(subsNeedUNSUB[route], sub)
20✔
2316
                        }
20✔
2317
                }
2318
                deleteRoutedSubs = deleteRoutedSubs[:0]
209✔
2319
                route.mu.Lock()
209✔
2320
                pa, _, hasSubType := route.getRoutedSubKeyInfo()
209✔
2321
                for key, sub := range route.subs {
265✔
2322
                        // If this is not a pinned-account route, we need to get the
56✔
2323
                        // account name from the key to see if we collect this sub.
56✔
2324
                        if !pa {
102✔
2325
                                if an := getAccNameFromRoutedSubKey(sub, key, hasSubType); an != accName {
67✔
2326
                                        continue
21✔
2327
                                }
2328
                        }
2329
                        // If we can't export, we need to drop the subscriptions that
2330
                        // we have on behalf of this route.
2331
                        // Need to make a string cast here since canExport call sl.Match()
2332
                        subj := string(sub.subject)
35✔
2333
                        if !route.canExport(subj) {
42✔
2334
                                // We can use bytesToString() here.
7✔
2335
                                delete(route.subs, bytesToString(sub.sid))
7✔
2336
                                deleteRoutedSubs = append(deleteRoutedSubs, sub)
7✔
2337
                        }
7✔
2338
                }
2339
                route.mu.Unlock()
209✔
2340
                // Remove as a batch all the subs that we have removed from each route.
209✔
2341
                sl.RemoveBatch(deleteRoutedSubs)
209✔
2342
                return true
209✔
2343
        })
2344

2345
        // Send an update INFO, which will allow remote server to show
2346
        // our current route config in monitoring and resend subscriptions
2347
        // that we now possibly allow with a change of Export permissions.
2348
        for _, route := range routes {
233✔
2349
                route.mu.Lock()
185✔
2350
                route.enqueueProto(infoJSON)
185✔
2351
                // Now send SUB and UNSUB protocols as needed.
185✔
2352
                if subs, ok := subsNeedSUB[route]; ok && len(subs) > 0 {
198✔
2353
                        route.sendRouteSubProtos(subs, false, nil)
13✔
2354
                }
13✔
2355
                if unsubs, ok := subsNeedUNSUB[route]; ok && len(unsubs) > 0 {
196✔
2356
                        route.sendRouteUnSubProtos(unsubs, false, nil)
11✔
2357
                }
11✔
2358
                route.mu.Unlock()
185✔
2359
        }
2360
}
2361

2362
func (s *Server) reloadClusterPoolAndAccounts(co *clusterOption, opts *Options) {
43✔
2363
        s.mu.Lock()
43✔
2364
        // Prevent adding new routes until we are ready to do so.
43✔
2365
        s.routesReject = true
43✔
2366
        var ch chan struct{}
43✔
2367
        // For accounts that have been added to the list of dedicated routes,
43✔
2368
        // send a protocol to their current assigned routes to allow the
43✔
2369
        // other side to prepare for the changes.
43✔
2370
        if len(co.accsAdded) > 0 {
56✔
2371
                protosSent := 0
13✔
2372
                s.accAddedReqID = nuid.Next()
13✔
2373
                for _, an := range co.accsAdded {
29✔
2374
                        if s.accRoutes == nil {
18✔
2375
                                s.accRoutes = make(map[string]map[string]*client)
2✔
2376
                        }
2✔
2377
                        // In case a config reload was first done on another server,
2378
                        // we may have already switched this account to a dedicated route.
2379
                        // But we still want to send the protocol over the routes that
2380
                        // would have otherwise handled it.
2381
                        if _, ok := s.accRoutes[an]; !ok {
23✔
2382
                                s.accRoutes[an] = make(map[string]*client)
7✔
2383
                        }
7✔
2384
                        if a, ok := s.accounts.Load(an); ok {
32✔
2385
                                acc := a.(*Account)
16✔
2386
                                acc.mu.Lock()
16✔
2387
                                sl := acc.sl
16✔
2388
                                // Get the current route pool index before calling setRouteInfo.
16✔
2389
                                rpi := acc.routePoolIdx
16✔
2390
                                // Switch to per-account route if not already done.
16✔
2391
                                if rpi >= 0 {
23✔
2392
                                        s.setRouteInfo(acc)
7✔
2393
                                } else {
16✔
2394
                                        // If it was transitioning, make sure we set it to the state
9✔
2395
                                        // that indicates that it has a dedicated route
9✔
2396
                                        if rpi == accTransitioningToDedicatedRoute {
18✔
2397
                                                acc.routePoolIdx = accDedicatedRoute
9✔
2398
                                        }
9✔
2399
                                        // Otherwise get the route pool index it would have been before
2400
                                        // the move so we can send the protocol to those routes.
2401
                                        rpi = computeRoutePoolIdx(s.routesPoolSize, acc.Name)
9✔
2402
                                }
2403
                                acc.mu.Unlock()
16✔
2404
                                // Generate the INFO protocol to send indicating that this account
16✔
2405
                                // is being moved to a dedicated route.
16✔
2406
                                ri := Info{
16✔
2407
                                        RoutePoolSize: s.routesPoolSize,
16✔
2408
                                        RouteAccount:  an,
16✔
2409
                                        RouteAccReqID: s.accAddedReqID,
16✔
2410
                                }
16✔
2411
                                proto := generateInfoJSON(&ri)
16✔
2412
                                // Since v2.11.0, we support remotes with a different pool size
16✔
2413
                                // (for rolling upgrades), so we need to use the remote route
16✔
2414
                                // pool index (based on the remote configured pool size) since
16✔
2415
                                // the remote subscriptions will be attached to the route at
16✔
2416
                                // that index, not at our account's route pool index. However,
16✔
2417
                                // we are going to send the protocol through the route that
16✔
2418
                                // handles this account from our pool size perspective (that
16✔
2419
                                // would be the route at index `rpi`).
16✔
2420
                                removeSubsAndSendProto := func(r *client, doSubs, doProto bool) {
41✔
2421
                                        r.mu.Lock()
25✔
2422
                                        defer r.mu.Unlock()
25✔
2423
                                        // Exclude routes to servers that don't support pooling.
25✔
2424
                                        if r.route.noPool {
27✔
2425
                                                return
2✔
2426
                                        }
2✔
2427
                                        if doSubs {
46✔
2428
                                                if subs := r.removeRemoteSubsForAcc(an); len(subs) > 0 {
28✔
2429
                                                        sl.RemoveBatch(subs)
5✔
2430
                                                }
5✔
2431
                                        }
2432
                                        if doProto {
46✔
2433
                                                r.enqueueProto(proto)
23✔
2434
                                                protosSent++
23✔
2435
                                        }
23✔
2436
                                }
2437
                                for remote, conns := range s.routes {
43✔
2438
                                        r := conns[rpi]
27✔
2439
                                        // The route connection at this index is currently not up,
27✔
2440
                                        // so we won't be able to send the protocol, so move to the
27✔
2441
                                        // next remote.
27✔
2442
                                        if r == nil {
29✔
2443
                                                continue
2✔
2444
                                        }
2445
                                        doSubs := true
25✔
2446
                                        // Check the remote's route pool size and if different than
25✔
2447
                                        // ours, remove the subs on that other route.
25✔
2448
                                        remotePoolSize, ok := s.remoteRoutePoolSize[remote]
25✔
2449
                                        if ok && remotePoolSize != s.routesPoolSize {
25✔
2450
                                                // This is the remote's route pool index for this account
×
2451
                                                rrpi := computeRoutePoolIdx(remotePoolSize, an)
×
2452
                                                if rr := conns[rrpi]; rr != nil {
×
2453
                                                        removeSubsAndSendProto(rr, true, false)
×
2454
                                                        // Indicate that we have already remove the subs.
×
2455
                                                        doSubs = false
×
2456
                                                }
×
2457
                                        }
2458
                                        // Now send the protocol from the route that handles the
2459
                                        // account from this server perspective.
2460
                                        removeSubsAndSendProto(r, doSubs, true)
25✔
2461
                                }
2462
                        }
2463
                }
2464
                if protosSent > 0 {
24✔
2465
                        s.accAddedCh = make(chan struct{}, protosSent)
11✔
2466
                        ch = s.accAddedCh
11✔
2467
                }
11✔
2468
        }
2469
        // Collect routes that need to be closed.
2470
        routes := make(map[*client]struct{})
43✔
2471
        // Collect the per-account routes that need to be closed.
43✔
2472
        if len(co.accsRemoved) > 0 {
52✔
2473
                for _, an := range co.accsRemoved {
25✔
2474
                        if remotes, ok := s.accRoutes[an]; ok && remotes != nil {
32✔
2475
                                for _, r := range remotes {
27✔
2476
                                        if r != nil {
22✔
2477
                                                r.setNoReconnect()
11✔
2478
                                                routes[r] = struct{}{}
11✔
2479
                                        }
11✔
2480
                                }
2481
                        }
2482
                }
2483
        }
2484
        // If the pool size has changed, we need to close all pooled routes.
2485
        if co.poolSizeChanged {
71✔
2486
                s.forEachNonPerAccountRoute(func(r *client) {
59✔
2487
                        routes[r] = struct{}{}
31✔
2488
                })
31✔
2489
        }
2490
        // If there are routes to close, we need to release the server lock.
2491
        // Same if we need to wait on responses from the remotes when
2492
        // processing new per-account routes.
2493
        if len(routes) > 0 || len(ch) > 0 {
55✔
2494
                s.mu.Unlock()
12✔
2495

12✔
2496
                for done := false; !done && len(ch) > 0; {
12✔
2497
                        select {
×
2498
                        case <-ch:
×
2499
                        case <-time.After(2 * time.Second):
×
2500
                                s.Warnf("Timed out waiting for confirmation from all routes regarding per-account routes changes")
×
2501
                                done = true
×
2502
                        }
2503
                }
2504

2505
                for r := range routes {
53✔
2506
                        r.closeConnection(RouteRemoved)
41✔
2507
                }
41✔
2508

2509
                s.mu.Lock()
12✔
2510
        }
2511
        // Clear the accAddedCh/ReqID fields in case they were set.
2512
        s.accAddedReqID, s.accAddedCh = _EMPTY_, nil
43✔
2513
        // Now that per-account routes that needed to be closed are closed,
43✔
2514
        // remove them from s.accRoutes. Doing so before would prevent
43✔
2515
        // removeRoute() to do proper cleanup because the route would not
43✔
2516
        // be found in s.accRoutes.
43✔
2517
        for _, an := range co.accsRemoved {
59✔
2518
                delete(s.accRoutes, an)
16✔
2519
                // Do not lookup and call setRouteInfo() on the accounts here.
16✔
2520
                // We need first to set the new s.routesPoolSize value and
16✔
2521
                // anyway, there is no need to do here if the pool size has
16✔
2522
                // changed (since it will be called for all accounts).
16✔
2523
        }
16✔
2524
        // We have already added the accounts to s.accRoutes that needed to
2525
        // be added.
2526

2527
        // We should always have at least the system account with a dedicated route,
2528
        // but in case we have a configuration that disables pooling and without
2529
        // a system account, possibly set the accRoutes to nil.
2530
        if len(opts.Cluster.PinnedAccounts) == 0 {
61✔
2531
                s.accRoutes = nil
18✔
2532
        }
18✔
2533
        // Now deal with pool size updates.
2534
        if ps := opts.Cluster.PoolSize; ps > 0 {
68✔
2535
                s.routesPoolSize = ps
25✔
2536
                s.routeInfo.RoutePoolSize = ps
25✔
2537
        } else {
43✔
2538
                s.routesPoolSize = 1
18✔
2539
                s.routeInfo.RoutePoolSize = 0
18✔
2540
        }
18✔
2541
        // If the pool size has changed, we need to recompute all accounts' route
2542
        // pool index. Note that the added/removed accounts will be reset there
2543
        // too, but that's ok (we could use a map to exclude them, but not worth it).
2544
        if co.poolSizeChanged {
71✔
2545
                s.accounts.Range(func(_, v any) bool {
107✔
2546
                        acc := v.(*Account)
79✔
2547
                        acc.mu.Lock()
79✔
2548
                        s.setRouteInfo(acc)
79✔
2549
                        acc.mu.Unlock()
79✔
2550
                        return true
79✔
2551
                })
79✔
2552
        } else if len(co.accsRemoved) > 0 {
20✔
2553
                // For accounts that no longer have a dedicated route, we need to send
5✔
2554
                // the subsriptions on the existing pooled routes for those accounts.
5✔
2555
                for _, an := range co.accsRemoved {
13✔
2556
                        if a, ok := s.accounts.Load(an); ok {
16✔
2557
                                acc := a.(*Account)
8✔
2558
                                acc.mu.Lock()
8✔
2559
                                // First call this which will assign a new route pool index.
8✔
2560
                                s.setRouteInfo(acc)
8✔
2561
                                // Get the value so we can send the subscriptions interest
8✔
2562
                                // on all routes with this pool index.
8✔
2563
                                rpi := acc.routePoolIdx
8✔
2564
                                acc.mu.Unlock()
8✔
2565
                                s.forEachRouteIdx(rpi, func(r *client) bool {
24✔
2566
                                        // We have the guarantee that if the route exists, it
16✔
2567
                                        // is not a new one that would have been created when
16✔
2568
                                        // we released the server lock if some routes needed
16✔
2569
                                        // to be closed, because we have set s.routesReject
16✔
2570
                                        // to `true` at the top of this function.
16✔
2571
                                        s.sendSubsToRoute(r, rpi, an)
16✔
2572
                                        return true
16✔
2573
                                })
16✔
2574
                        }
2575
                }
2576
        }
2577
        // Allow routes to be accepted now.
2578
        s.routesReject = false
43✔
2579
        // If there is a pool size change or added accounts, solicit routes now.
43✔
2580
        if co.poolSizeChanged || len(co.accsAdded) > 0 {
81✔
2581
                s.solicitRoutes(opts.Routes, co.accsAdded)
38✔
2582
        }
38✔
2583
        s.mu.Unlock()
43✔
2584
}
2585

2586
// validateClusterOpts ensures the new ClusterOpts does not change some of the
2587
// fields that do not support reload.
2588
func validateClusterOpts(old, new ClusterOpts) error {
181✔
2589
        if old.Host != new.Host {
181✔
2590
                return fmt.Errorf("config reload not supported for cluster host: old=%s, new=%s",
×
2591
                        old.Host, new.Host)
×
2592
        }
×
2593
        if old.Port != new.Port {
183✔
2594
                return fmt.Errorf("config reload not supported for cluster port: old=%d, new=%d",
2✔
2595
                        old.Port, new.Port)
2✔
2596
        }
2✔
2597
        // Validate Cluster.Advertise syntax
2598
        if new.Advertise != "" {
183✔
2599
                if _, _, err := parseHostPort(new.Advertise, 0); err != nil {
4✔
2600
                        return fmt.Errorf("invalid Cluster.Advertise value of %s, err=%v", new.Advertise, err)
×
2601
                }
×
2602
        }
2603
        return nil
179✔
2604
}
2605

2606
// diffRoutes diffs the old routes and the new routes and returns the ones that
2607
// should be added and removed from the server.
2608
func diffRoutes(old, new []*url.URL) (add, remove []*url.URL) {
5✔
2609
        // Find routes to remove.
5✔
2610
removeLoop:
5✔
2611
        for _, oldRoute := range old {
11✔
2612
                for _, newRoute := range new {
10✔
2613
                        if urlsAreEqual(oldRoute, newRoute) {
5✔
2614
                                continue removeLoop
1✔
2615
                        }
2616
                }
2617
                remove = append(remove, oldRoute)
5✔
2618
        }
2619

2620
        // Find routes to add.
2621
addLoop:
5✔
2622
        for _, newRoute := range new {
9✔
2623
                for _, oldRoute := range old {
8✔
2624
                        if urlsAreEqual(oldRoute, newRoute) {
5✔
2625
                                continue addLoop
1✔
2626
                        }
2627
                }
2628
                add = append(add, newRoute)
3✔
2629
        }
2630

2631
        return add, remove
5✔
2632
}
2633

2634
func diffProxiesTrustedKeys(old, new []*ProxyConfig) ([]string, []string) {
1✔
2635
        var add []string
1✔
2636
        var del []string
1✔
2637
        // Both "old" and "new" lists should be small...
1✔
2638
        for _, op := range old {
3✔
2639
                if !slices.ContainsFunc(new, func(pc *ProxyConfig) bool {
6✔
2640
                        return pc.Key == op.Key
4✔
2641
                }) {
5✔
2642
                        del = append(del, op.Key)
1✔
2643
                }
1✔
2644
        }
2645
        for _, np := range new {
3✔
2646
                if !slices.ContainsFunc(old, func(pc *ProxyConfig) bool {
6✔
2647
                        return pc.Key == np.Key
4✔
2648
                }) {
5✔
2649
                        add = append(add, np.Key)
1✔
2650
                }
1✔
2651
        }
2652
        return add, del
1✔
2653
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc