@@ -20,32 +20,100 @@ namespace NuGet.Services.DatabaseMigration
2020{
2121 public class Job : JsonConfigurationJob
2222 {
23- private const string MigrationTargetDatabaseArgument = "MigrationTargetDatabase" ;
23+ public int ExitCode { get ; set ; }
2424
2525 private string _migrationTargetDatabase ;
2626 private IMigrationContextFactory _migrationContextFactory ;
2727
28+ private const string MigrationTargetDatabaseArgument = "MigrationTargetDatabase" ;
29+ // There is a Gallery migration file which doens't exist in the local migration folder;
30+ // Need to skip this migration file for the validation check.
31+ private const string SkipGalleryDatabaseMigrationFile = "201304262247205_CuratedPackagesUniqueIndex" ;
32+
2833 public Job ( IMigrationContextFactory migrationContextFactory )
2934 {
3035 _migrationContextFactory = migrationContextFactory ;
3136 }
3237
3338 public override void Init ( IServiceContainer serviceContainer , IDictionary < string , string > jobArgsDictionary )
3439 {
35- base . Init ( serviceContainer , jobArgsDictionary ) ;
36- _migrationTargetDatabase = JobConfigurationManager . GetArgument ( jobArgsDictionary , MigrationTargetDatabaseArgument ) ;
40+ try
41+ {
42+ base . Init ( serviceContainer , jobArgsDictionary ) ;
43+ _migrationTargetDatabase = JobConfigurationManager . GetArgument ( jobArgsDictionary , MigrationTargetDatabaseArgument ) ;
44+ }
45+ catch ( Exception )
46+ {
47+ ExitCode = 1 ;
48+ throw ;
49+ }
3750 }
3851
3952 public override async Task Run ( )
4053 {
4154 Logger . LogInformation ( "Initializing database migration context..." ) ;
42- var migrationContext = await _migrationContextFactory . CreateMigrationContextAsync ( _migrationTargetDatabase , _serviceProvider ) ;
4355
44- ExecuteDatabaseMigration ( migrationContext . GetDbMigrator ,
45- migrationContext . SqlConnection ,
46- migrationContext . SqlConnectionAccessToken ) ;
56+ IMigrationContext migrationContext = null ;
57+ try
58+ {
59+ migrationContext = await _migrationContextFactory . CreateMigrationContextAsync ( _migrationTargetDatabase , _serviceProvider ) ;
4760
48- migrationContext . SqlConnection . Dispose ( ) ;
61+ ExecuteDatabaseMigration ( migrationContext . GetDbMigrator ,
62+ migrationContext . SqlConnection ,
63+ migrationContext . SqlConnectionAccessToken ) ;
64+ }
65+ catch ( Exception )
66+ {
67+ ExitCode = 1 ;
68+ throw ;
69+ }
70+ finally
71+ {
72+ migrationContext ? . SqlConnection ? . Dispose ( ) ;
73+ }
74+ }
75+
76+ public void CheckIsValidMigration ( List < string > databaseMigrations , List < string > localMigrations )
77+ {
78+ if ( databaseMigrations == null )
79+ {
80+ throw new ArgumentNullException ( nameof ( databaseMigrations ) ) ;
81+ }
82+ if ( localMigrations == null )
83+ {
84+ throw new ArgumentNullException ( nameof ( localMigrations ) ) ;
85+ }
86+ if ( databaseMigrations . Count == 0 )
87+ {
88+ throw new InvalidOperationException ( "Migration validation failed: Unexpected empty history of database migrations." ) ;
89+ }
90+ if ( localMigrations . Count == 0 )
91+ {
92+ throw new InvalidOperationException ( "Migration validation failed: Unexpected empty history of local migrations." ) ;
93+ }
94+
95+ var databaseMigrationsCursor = 0 ;
96+ var localMigrationsCursor = 0 ;
97+ while ( databaseMigrationsCursor < databaseMigrations . Count &&
98+ localMigrationsCursor < localMigrations . Count )
99+ {
100+ if ( _migrationTargetDatabase != null &&
101+ _migrationTargetDatabase . Equals ( MigrationTargetDatabaseArgumentNames . GalleryDatabase ) &&
102+ databaseMigrations [ databaseMigrationsCursor ] . Equals ( SkipGalleryDatabaseMigrationFile ) )
103+ {
104+ databaseMigrationsCursor ++ ;
105+ }
106+ else
107+ {
108+ if ( ! databaseMigrations [ databaseMigrationsCursor ] . Equals ( localMigrations [ localMigrationsCursor ] ) )
109+ {
110+ throw new InvalidOperationException ( $ "Migration validation failed: Mismatch local migration file: { localMigrations [ localMigrationsCursor ] } .") ;
111+ }
112+
113+ localMigrationsCursor ++ ;
114+ databaseMigrationsCursor ++ ;
115+ }
116+ }
49117 }
50118
51119 private void ExecuteDatabaseMigration ( Func < DbMigrator > getMigrator , SqlConnection sqlConnection , string accessToken )
@@ -56,32 +124,48 @@ private void ExecuteDatabaseMigration(Func<DbMigrator> getMigrator, SqlConnectio
56124 OverwriteSqlConnection ( migrator , sqlConnection , accessToken ) ;
57125 OverwriteSqlConnection ( migratorForScripting , sqlConnection , accessToken ) ;
58126
59- Logger . LogInformation ( "Target database is: {DataSource}/{Database}" , sqlConnection . DataSource , sqlConnection . Database ) ;
127+ var sqlConnectionDataSource = sqlConnection . DataSource ;
128+ var sqlConnectionDatabase = sqlConnection . Database ;
129+
130+ Logger . LogInformation ( "Target database is: {DataSource}/{Database}." , sqlConnectionDataSource , sqlConnectionDatabase ) ;
60131 var pendingMigrations = migrator . GetPendingMigrations ( ) ;
61132 if ( pendingMigrations . Count ( ) > 0 )
62133 {
63- Logger . LogInformation ( "Applying pending migrations: \n {PendingMigrations}" , String . Join ( "\n " , pendingMigrations ) ) ;
134+ var databaseMigrations = migrator . GetDatabaseMigrations ( ) . ToList ( ) ;
135+ databaseMigrations . Reverse ( ) ;
136+ var localMigrations = migrator . GetLocalMigrations ( ) . ToList ( ) ;
137+ try
138+ {
139+ CheckIsValidMigration ( databaseMigrations , localMigrations ) ;
140+ }
141+ catch ( Exception e )
142+ {
143+ Logger . LogError ( 0 , e , "Validation check of database migrations failed." ) ;
144+ throw ;
145+ }
146+
147+ Logger . LogInformation ( "Applying pending migrations: \n {PendingMigrations}." , String . Join ( "\n " , pendingMigrations ) ) ;
64148
65149 var migratorScripter = new MigratorScriptingDecorator ( migratorForScripting ) ;
66150 var migrationScripts = migratorScripter . ScriptUpdate ( sourceMigration : null , targetMigration : null ) ;
67- Logger . LogInformation ( "Applying explicit migration SQL scripts: \n {migrationScripts}" , migrationScripts ) ;
151+ Logger . LogInformation ( "Applying explicit migration SQL scripts: \n {migrationScripts}. " , migrationScripts ) ;
68152
69153 try
70154 {
71155 Logger . LogInformation ( "Executing migrations..." ) ;
72156
73157 migrator . Update ( ) ;
74158
75- Logger . LogInformation ( "Finished executing {pendingMigrationsCount} migrations successfully on the target database {DataSource}/{Database}" ,
159+ Logger . LogInformation ( "Finished executing {pendingMigrationsCount} migrations successfully on the target database {DataSource}/{Database}. " ,
76160 pendingMigrations . Count ( ) ,
77- sqlConnection . DataSource ,
78- sqlConnection . Database ) ;
161+ sqlConnectionDataSource ,
162+ sqlConnectionDatabase ) ;
79163 }
80164 catch ( Exception e )
81165 {
82- Logger . LogError ( 0 , e , "Failed to execute migrations on the target database {DataSource}/{Database}" ,
83- sqlConnection . DataSource ,
84- sqlConnection . Database ) ;
166+ Logger . LogError ( 0 , e , "Failed to execute migrations on the target database {DataSource}/{Database}. " ,
167+ sqlConnectionDataSource ,
168+ sqlConnectionDatabase ) ;
85169 throw ;
86170 }
87171 }
0 commit comments