Repository: nats-io/nats-server
Pull request #4345 (2023-07-28 16:16:44)

Prevent configuring `first_seq` on mirrors
This prevents configurations where mirrors are mixed with `first_seq`s of greater than zero.

Signed-off-by: Neil Twigg <neil@nats.io>


Patch:
diff --git a/server/errors.json b/server/errors.json
index da48ace5..f6af9522 100644
--- a/server/errors.json
+++ b/server/errors.json
@@ -299,6 +299,16 @@
     "url": "",
     "deprecates": ""
   },
+  {
+    "constant": "JSMirrorWithFirstSeqErr",
+    "code": 400,
+    "error_code": 10143,
+    "description": "stream mirrors can not have first sequence configured",
+    "comment": "",
+    "help": "",
+    "url": "",
+    "deprecates": ""
+  },
   {
     "constant": "JSNotEnabledErr",
     "code": 503,
diff --git a/server/jetstream_errors_generated.go b/server/jetstream_errors_generated.go
index 497337f8..619d8af4 100644
--- a/server/jetstream_errors_generated.go
+++ b/server/jetstream_errors_generated.go
@@ -230,6 +230,9 @@ const (
 	// JSMirrorMaxMessageSizeTooBigErr stream mirror must have max message size >= source
 	JSMirrorMaxMessageSizeTooBigErr ErrorIdentifier = 10030
 
+	// JSMirrorWithFirstSeqErr stream mirrors can not have first sequence configured
+	JSMirrorWithFirstSeqErr ErrorIdentifier = 10143
+
 	// JSMirrorWithSourcesErr stream mirrors can not also contain other sources
 	JSMirrorWithSourcesErr ErrorIdentifier = 10031
 
@@ -506,6 +509,7 @@ var (
 		JSMirrorConsumerSetupFailedErrF:            {Code: 500, ErrCode: 10029, Description: "{err}"},
 		JSMirrorInvalidStreamName:                  {Code: 400, ErrCode: 10142, Description: "mirrored stream name is invalid"},
 		JSMirrorMaxMessageSizeTooBigErr:            {Code: 400, ErrCode: 10030, Description: "stream mirror must have max message size >= source"},
+		JSMirrorWithFirstSeqErr:                    {Code: 400, ErrCode: 10143, Description: "stream mirrors can not have first sequence configured"},
 		JSMirrorWithSourcesErr:                     {Code: 400, ErrCode: 10031, Description: "stream mirrors can not also contain other sources"},
 		JSMirrorWithStartSeqAndTimeErr:             {Code: 400, ErrCode: 10032, Description: "stream mirrors can not have both start seq and start time configured"},
 		JSMirrorWithSubjectFiltersErr:              {Code: 400, ErrCode: 10033, Description: "stream mirrors can not contain filtered subjects"},
@@ -1413,6 +1417,16 @@ func NewJSMirrorMaxMessageSizeTooBigError(opts ...ErrorOption) *ApiError {
 	return ApiErrors[JSMirrorMaxMessageSizeTooBigErr]
 }
 
+// NewJSMirrorWithFirstSeqError creates a new JSMirrorWithFirstSeqErr error: "stream mirrors can not have first sequence configured"
+func NewJSMirrorWithFirstSeqError(opts ...ErrorOption) *ApiError {
+	eopts := parseOpts(opts)
+	if ae, ok := eopts.err.(*ApiError); ok {
+		return ae
+	}
+
+	return ApiErrors[JSMirrorWithFirstSeqErr]
+}
+
 // NewJSMirrorWithSourcesError creates a new JSMirrorWithSourcesErr error: "stream mirrors can not also contain other sources"
 func NewJSMirrorWithSourcesError(opts ...ErrorOption) *ApiError {
 	eopts := parseOpts(opts)
diff --git a/server/stream.go b/server/stream.go
index 93602998..536cfe48 100644
--- a/server/stream.go
+++ b/server/stream.go
@@ -1159,6 +1159,9 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account) (StreamConfi
 
 	// Do some pre-checking for mirror config to avoid cycles in clustered mode.
 	if cfg.Mirror != nil {
+		if cfg.FirstSeq > 0 {
+			return StreamConfig{}, NewJSMirrorWithFirstSeqError()
+		}
 		if len(cfg.Subjects) > 0 {
 			return StreamConfig{}, NewJSMirrorWithSubjectsError()
 


Test patch:
diff --git a/server/jetstream_test.go b/server/jetstream_test.go
index baa20b12..bda40a25 100644
--- a/server/jetstream_test.go
+++ b/server/jetstream_test.go
@@ -18134,6 +18134,22 @@ func TestJetStreamMirrorUpdatesNotSupported(t *testing.T) {
 	require_Error(t, err, NewJSStreamMirrorNotUpdatableError())
 }
 
+func TestJetStreamMirrorFirstSeqNotSupported(t *testing.T) {
+	s := RunBasicJetStreamServer(t)
+	defer s.Shutdown()
+
+	_, err := s.gacc.addStream(&StreamConfig{Name: "SOURCE"})
+	require_NoError(t, err)
+
+	cfg := &StreamConfig{
+		Name:     "M",
+		Mirror:   &StreamSource{Name: "SOURCE"},
+		FirstSeq: 123,
+	}
+	_, err = s.gacc.addStream(cfg)
+	require_Error(t, err, NewJSMirrorWithFirstSeqError())
+}
+
 func TestJetStreamDirectGetBySubject(t *testing.T) {
 	conf := createConfFile(t, []byte(`
 		listen: 127.0.0.1:-1