2323import java .util .Collections ;
2424import java .util .Map ;
2525import java .util .function .Predicate ;
26+ import java .util .regex .Pattern ;
2627
2728import com .google .common .util .concurrent .RateLimiter ;
2829
3334import org .apache .cassandra .config .DurationSpec ;
3435import org .apache .cassandra .db .ColumnFamilyStore ;
3536import org .apache .cassandra .io .sstable .format .SSTableReader ;
37+ import org .apache .cassandra .schema .SchemaConstants ;
3638
3739import static java .lang .String .format ;
40+ import static org .apache .cassandra .schema .SchemaConstants .FILENAME_LENGTH ;
3841
3942public class SnapshotOptions
4043{
@@ -88,7 +91,7 @@ public static SnapshotOptions userSnapshot(String tag, Map<String, String> optio
8891 return builder .build ();
8992 }
9093
91- public String getSnapshotName (Instant creationTime )
94+ public static String getSnapshotName (SnapshotType type , String tag , Instant creationTime )
9295 {
9396 // Diagnostic snapshots have very specific naming convention hence we are keeping it.
9497 // Repair snapshots rely on snapshots having name of their repair session ids
@@ -189,10 +192,47 @@ public Builder rateLimiter(RateLimiter rateLimiter)
189192 }
190193
191194 public SnapshotOptions build ()
195+ {
196+ validateTag (tag );
197+ validateTTL (ephemeral , ttl );
198+
199+ if (rateLimiter == null )
200+ rateLimiter = DatabaseDescriptor .getSnapshotRateLimiter ();
201+
202+ return new SnapshotOptions (this );
203+ }
204+
205+ private void validateTag (String tag )
192206 {
193207 if (tag == null || tag .isEmpty ())
194208 throw new RuntimeException ("You must supply a snapshot name." );
195209
210+ // Pre-generate snapshot name for the sake of the validation.
211+ // getSnapshotName logic does not return raw "tag" as snapshot name every time,
212+ // it e.g. prepends timestamp and type for system snapshots, and we need to validate it as a whole.
213+ // If, for example, tag would be less than max allowed FILENAME_LENGTH,
214+ // we might in fact produce a snapshot name longer than FILENAME_LENGTH if we prepended a timestamp to it.
215+ String resolvedSnapshotname = SnapshotOptions .getSnapshotName (type , tag , Instant .now ());
216+
217+ // the length of valid snapshot name has to be less than or equal to FILENAME_LEGTH - that is 255 -
218+ // we are following the max length as it is in SchemaConstants for table name.
219+ if (resolvedSnapshotname .length () > SchemaConstants .FILENAME_LENGTH )
220+ {
221+ throw new IllegalArgumentException (format ("Snapshot name must not be more than %d characters long for " +
222+ "resolved snapshot name (got %d characters for \" %s\" )" ,
223+ FILENAME_LENGTH , resolvedSnapshotname .length (), resolvedSnapshotname ));
224+ }
225+
226+ // "-" is important, we are already generating system snapshots with "-" in them so this has to stay
227+ // \\w are just "words" consisting of [a-zA-Z0-9_]
228+ if (!Pattern .compile ("[\\ w-]+" ).matcher (resolvedSnapshotname ).matches ())
229+ {
230+ throw new IllegalArgumentException ("Snapshot name contains illegal characters: " + resolvedSnapshotname );
231+ }
232+ }
233+
234+ private void validateTTL (boolean ephemeral , DurationSpec .IntSecondsBound ttl )
235+ {
196236 if (ttl != null )
197237 {
198238 int minAllowedTtlSecs = CassandraRelevantProperties .SNAPSHOT_MIN_ALLOWED_TTL_SECONDS .getInt ();
@@ -202,12 +242,8 @@ public SnapshotOptions build()
202242
203243 if (ephemeral && ttl != null )
204244 throw new IllegalStateException (format ("can not take ephemeral snapshot (%s) while ttl is specified too" , tag ));
205-
206- if (rateLimiter == null )
207- rateLimiter = DatabaseDescriptor .getSnapshotRateLimiter ();
208-
209- return new SnapshotOptions (this );
210245 }
246+
211247 }
212248
213249 @ Override
0 commit comments