Repository: cloudspannerecosystem/harbourbridge
Pull request #385 (2022-10-10 10:05:18)

create bucket and generate resources
Added changes to create a default bucket for migration and use it for connection profile, placing session file, etc. This PR also has code changes to display generated resources at the end of migration. These resources include spanner database, gcs bucket created and dataflow and datastream jobs.


Patch:
diff --git a/cmd/utils.go b/cmd/utils.go
index 473b234..a2d550f 100644
--- a/cmd/utils.go
+++ b/cmd/utils.go
@@ -131,7 +131,7 @@ func migrateSchema(ctx context.Context, targetProfile profiles.TargetProfile, so
 		err = fmt.Errorf("can't create/update database: %v", err)
 		return err
 	}
-	conv.Audit.Progress.SetProgressMessageAndUpdate("Schema migration complete.", completionPercentage)
+	conv.Audit.Progress.UpdateProgress("Schema migration complete.", completionPercentage, internal.SchemaMigrationComplete)
 	return nil
 }
 
@@ -169,7 +169,7 @@ func migrateSchemaAndData(ctx context.Context, targetProfile profiles.TargetProf
 		err = fmt.Errorf("can't create/update database: %v", err)
 		return nil, err
 	}
-	conv.Audit.Progress.SetProgressMessageAndUpdate("Schema migration complete.", completionPercentage)
+	conv.Audit.Progress.UpdateProgress("Schema migration complete.", completionPercentage, internal.SchemaMigrationComplete)
 	bw, err := conversion.DataConv(ctx, sourceProfile, targetProfile, ioHelper, client, conv, true, cmd.WriteLimit)
 	if err != nil {
 		err = fmt.Errorf("can't finish data conversion for db %s: %v", dbURI, err)
@@ -182,5 +182,6 @@ func migrateSchemaAndData(ctx context.Context, targetProfile profiles.TargetProf
 			return bw, err
 		}
 	}
+	conv.Audit.Progress.UpdateProgress("Data migration complete.", completionPercentage, internal.DataMigrationComplete)
 	return bw, nil
 }
diff --git a/common/utils/utils.go b/common/utils/utils.go
index a3dc670..d08f46e 100644
--- a/common/utils/utils.go
+++ b/common/utils/utils.go
@@ -216,6 +216,39 @@ func WriteToGCS(filePath, fileName, data string) error {
 	return nil
 }
 
+func CreateGCSBucket(bucketName, projectID string) error {
+	ctx := context.Background()
+
+	client, err := storage.NewClient(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to create GCS client: %v", err)
+	}
+	defer client.Close()
+	bucket := client.Bucket(bucketName)
+	buckets := client.Buckets(ctx, projectID)
+	for {
+		if bucketName == "" {
+			return fmt.Errorf("bucketName entered is empty %v", bucketName)
+		}
+		attrs, err := buckets.Next()
+		// Assume bucket not found if at Iterator end and create
+		if err == iterator.Done {
+			// Create bucket
+			if err := bucket.Create(ctx, projectID, nil); err != nil {
+				return fmt.Errorf("failed to create bucket: %v", err)
+			}
+			fmt.Printf("Created new GCS bucket: %v\n", bucketName)
+			return nil
+		}
+		if err != nil {
+			return fmt.Errorf("issues setting up Bucket(%q).Objects(): %v. Double check project id", attrs.Name, err)
+		}
+		if attrs.Name == bucketName {
+			return nil
+		}
+	}
+}
+
 // GetProject returns the cloud project we should use for accessing Spanner.
 // Use environment variable GCLOUD_PROJECT if it is set.
 // Otherwise, use the default project returned from gcloud.
diff --git a/conversion/conversion.go b/conversion/conversion.go
index d44fecd..11bace8 100644
--- a/conversion/conversion.go
+++ b/conversion/conversion.go
@@ -179,7 +179,7 @@ func performSnapshotMigration(config writer.BatchWriterConfig, conv *internal.Co
 	common.SetRowStats(conv, infoSchema)
 	totalRows := conv.Rows()
 	if !conv.Audit.DryRun {
-		conv.Audit.Progress = *internal.NewProgress(totalRows, "Writing data to Spanner", internal.Verbose(), false)
+		conv.Audit.Progress = *internal.NewProgress(totalRows, "Writing data to Spanner", internal.Verbose(), false, int(internal.DataWriteInProgress))
 	}
 	batchWriter := populateDataConv(conv, config, client)
 	common.ProcessData(conv, infoSchema)
@@ -242,7 +242,7 @@ func schemaFromDump(driver string, targetDb string, ioHelper *utils.IOStreams) (
 	ioHelper.BytesRead = n
 	conv := internal.MakeConv()
 	conv.TargetDb = targetDb
-	p := internal.NewProgress(n, "Generating schema", internal.Verbose(), false)
+	p := internal.NewProgress(n, "Generating schema", internal.Verbose(), false, int(internal.SchemaCreationInProgress))
 	r := internal.NewReader(bufio.NewReader(f), p)
 	conv.SetSchemaMode() // Build schema and ignore data in dump.
 	conv.SetDataSink(nil)
@@ -277,7 +277,7 @@ func dataFromDump(driver string, config writer.BatchWriterConfig, ioHelper *util
 	}
 	totalRows := conv.Rows()
 
-	conv.Audit.Progress = *internal.NewProgress(totalRows, "Writing data to Spanner", internal.Verbose(), false)
+	conv.Audit.Progress = *internal.NewProgress(totalRows, "Writing data to Spanner", internal.Verbose(), false, int(internal.DataWriteInProgress))
 	r := internal.NewReader(bufio.NewReader(ioHelper.SeekableIn), nil)
 	batchWriter := populateDataConv(conv, config, client)
 	ProcessDump(driver, conv, r)
@@ -325,7 +325,7 @@ func dataFromCSV(ctx context.Context, sourceProfile profiles.SourceProfile, targ
 	}
 
 	totalRows := conv.Rows()
-	conv.Audit.Progress = *internal.NewProgress(totalRows, "Writing data to Spanner", internal.Verbose(), false)
+	conv.Audit.Progress = *internal.NewProgress(totalRows, "Writing data to Spanner", internal.Verbose(), false, int(internal.DataWriteInProgress))
 	batchWriter := populateDataConv(conv, config, client)
 	err = csv.ProcessCSV(conv, tables, sourceProfile.Csv.NullStr, delimiter)
 	if err != nil {
@@ -611,7 +611,7 @@ However, setting it to a very high value might lead to exceeding the admin quota
 Recommended value is between 20-30.`)
 	}
 	msg := fmt.Sprintf("Updating schema of database %s with foreign key constraints ...", dbURI)
-	conv.Audit.Progress = *internal.NewProgress(int64(len(fkStmts)), msg, internal.Verbose(), true)
+	conv.Audit.Progress = *internal.NewProgress(int64(len(fkStmts)), msg, internal.Verbose(), true, int(internal.ForeignKeyUpdateInProgress))
 
 	workers := make(chan int, MaxWorkers)
 	for i := 1; i <= MaxWorkers; i++ {
diff --git a/internal/convert.go b/internal/convert.go
index 834efe6..ea7f388 100644
--- a/internal/convert.go
+++ b/internal/convert.go
@@ -165,6 +165,8 @@ type streamingStats struct {
 	DroppedRecords   map[string]map[string]int64 // Tablewise count of records successfully converted but failed to written on Spanner, broken down by record type.
 	SampleBadRecords []string                    // Records that generated errors during conversion.
 	SampleBadWrites  []string                    // Records that faced errors while writing to Cloud Spanner.
+	DataStreamName   string
+	DataflowJobId    string
 }
 
 // MakeConv returns a default-configured Conv.
diff --git a/internal/progress.go b/internal/progress.go
index 008cfcd..c0dcadf 100644
--- a/internal/progress.go
+++ b/internal/progress.go
@@ -35,11 +35,25 @@ type Progress struct {
 	message    string // Name of task being monitored.
 	verbose    bool   // If true, print detailed info about each progress step.
 	fractional bool   // If true, report progress in fractions instead of percentages.
+	ProgressStatus
 }
 
+// ProgressStatus specifies a stage of migration.
+type ProgressStatus int
+
+// Defines the progress statuses that we track
+const (
+	DefaultStatus ProgressStatus = iota
+	SchemaMigrationComplete
+	SchemaCreationInProgress
+	DataMigrationComplete
+	DataWriteInProgress
+	ForeignKeyUpdateInProgress
+)
+
 // NewProgress creates and returns a Progress instance.
-func NewProgress(total int64, message string, verbose, fractional bool) *Progress {
-	p := &Progress{total, 0, 0, message, verbose, fractional}
+func NewProgress(total int64, message string, verbose, fractional bool, progressStatus int) *Progress {
+	p := &Progress{total, 0, 0, message, verbose, fractional, ProgressStatus(progressStatus)}
 	if total == 0 {
 		p.pct = 100
 	}
@@ -120,11 +134,12 @@ func (p *Progress) reportFraction(firstCall bool) {
 	}
 }
 
-func (p *Progress) ReportProgress() (int, string) {
-	return int(p.pct), p.message
+func (p *Progress) ReportProgress() (int, int) {
+	return int(p.pct), int(p.ProgressStatus)
 }
 
-func (p *Progress) SetProgressMessageAndUpdate(message string, pct int) {
+func (p *Progress) UpdateProgress(message string, pct int, progressStatus ProgressStatus) {
 	p.message = message
 	p.pct = pct
+	p.ProgressStatus = progressStatus
 }
diff --git a/sources/mysql/infoschema.go b/sources/mysql/infoschema.go
index 0d021e7..d05299f 100644
--- a/sources/mysql/infoschema.go
+++ b/sources/mysql/infoschema.go
@@ -17,7 +17,6 @@ package mysql
 import (
 	"context"
 	"database/sql"
-	"encoding/json"
 	"fmt"
 	"sort"
 	"strings"
@@ -26,7 +25,6 @@ import (
 	_ "github.com/go-sql-driver/mysql" // The driver should be used via the database/sql package.
 	_ "github.com/lib/pq"
 
-	"github.com/cloudspannerecosystem/harbourbridge/common/utils"
 	"github.com/cloudspannerecosystem/harbourbridge/internal"
 	"github.com/cloudspannerecosystem/harbourbridge/profiles"
 	"github.com/cloudspannerecosystem/harbourbridge/schema"
@@ -352,20 +350,8 @@ func (isi InfoSchemaImpl) StartChangeDataCapture(ctx context.Context, conv *inte
 // performing a streaming migration.
 func (isi InfoSchemaImpl) StartStreamingMigration(ctx context.Context, client *sp.Client, conv *internal.Conv, streamingInfo map[string]interface{}) error {
 	streamingCfg, _ := streamingInfo["streamingCfg"].(streaming.StreamingCfg)
-	convJSON, err := json.MarshalIndent(conv, "", " ")
-	if err != nil {
-		err = fmt.Errorf("can't encode session state to JSON: %v", err)
-		return err
-	}
-	fmt.Printf("Writing session file to GCS...")
-	err = utils.WriteToGCS(streamingCfg.TmpDir, "session.json", string(convJSON))
-	if err != nil {
-		err = fmt.Errorf("error writing session file to GCS: %v", err)
-		return err
-	}
-	fmt.Println("Done")
 
-	err = streaming.StartDataflow(ctx, isi.SourceProfile, isi.TargetProfile, streamingCfg)
+	err := streaming.StartDataflow(ctx, isi.SourceProfile, isi.TargetProfile, streamingCfg, conv)
 	if err != nil {
 		err = fmt.Errorf("error starting dataflow: %v", err)
 		return err
diff --git a/sources/oracle/infoschema.go b/sources/oracle/infoschema.go
index 119ead2..7670426 100644
--- a/sources/oracle/infoschema.go
+++ b/sources/oracle/infoschema.go
@@ -420,7 +420,7 @@ func (isi InfoSchemaImpl) StartChangeDataCapture(ctx context.Context, conv *inte
 // performing a streaming migration.
 func (isi InfoSchemaImpl) StartStreamingMigration(ctx context.Context, client *sp.Client, conv *internal.Conv, streamingInfo map[string]interface{}) error {
 	streamingCfg, _ := streamingInfo["streamingCfg"].(streaming.StreamingCfg)
-	err := streaming.StartDataflow(ctx, isi.SourceProfile, isi.TargetProfile, streamingCfg)
+	err := streaming.StartDataflow(ctx, isi.SourceProfile, isi.TargetProfile, streamingCfg, conv)
 	if err != nil {
 		return err
 	}
diff --git a/streaming/streaming.go b/streaming/streaming.go
index 59f6cda..90b9959 100644
--- a/streaming/streaming.go
+++ b/streaming/streaming.go
@@ -30,6 +30,7 @@ import (
 
 	"github.com/cloudspannerecosystem/harbourbridge/common/constants"
 	"github.com/cloudspannerecosystem/harbourbridge/common/utils"
+	"github.com/cloudspannerecosystem/harbourbridge/internal"
 	"github.com/cloudspannerecosystem/harbourbridge/profiles"
 )
 
@@ -256,7 +257,7 @@ func LaunchStream(ctx context.Context, sourceProfile profiles.SourceProfile, pro
 }
 
 // LaunchDataflowJob populates the parameters from the streaming config and triggers a Dataflow job.
-func LaunchDataflowJob(ctx context.Context, targetProfile profiles.TargetProfile, streamingCfg StreamingCfg) error {
+func LaunchDataflowJob(ctx context.Context, targetProfile profiles.TargetProfile, streamingCfg StreamingCfg, conv *internal.Conv) error {
 	project, instance, dbName, _ := targetProfile.GetResourceIds(ctx, time.Now(), "", nil)
 	dataflowCfg := streamingCfg.DataflowCfg
 	datastreamCfg := streamingCfg.DatastreamCfg
@@ -314,6 +315,8 @@ func LaunchDataflowJob(ctx context.Context, targetProfile profiles.TargetProfile
 		fmt.Printf("flexTemplateRequest: %+v\n", req)
 		return fmt.Errorf("unable to launch template: %v", err)
 	}
+	conv.Audit.StreamingStats.DataStreamName = datastreamCfg.StreamId
+	conv.Audit.StreamingStats.DataflowJobId = respDf.Job.Id
 	fullStreamName := fmt.Sprintf("projects/%s/locations/%s/streams/%s", project, datastreamCfg.StreamLocation, datastreamCfg.StreamId)
 	dfJobDetails := fmt.Sprintf("project: %s, location: %s, name: %s, id: %s", project, respDf.Job.Location, respDf.Job.Name, respDf.Job.Id)
 	fmt.Println("\n------------------------------------------\n" +
@@ -346,8 +349,8 @@ func StartDatastream(ctx context.Context, sourceProfile profiles.SourceProfile,
 	return streamingCfg, nil
 }
 
-func StartDataflow(ctx context.Context, sourceProfile profiles.SourceProfile, targetProfile profiles.TargetProfile, streamingCfg StreamingCfg) error {
-	err := LaunchDataflowJob(ctx, targetProfile, streamingCfg)
+func StartDataflow(ctx context.Context, sourceProfile profiles.SourceProfile, targetProfile profiles.TargetProfile, streamingCfg StreamingCfg, conv *internal.Conv) error {
+	err := LaunchDataflowJob(ctx, targetProfile, streamingCfg, conv)
 	if err != nil {
 		return fmt.Errorf("error launching dataflow: %v", err)
 	}
diff --git a/ui/src/app/app.constants.ts b/ui/src/app/app.constants.ts
index 608ff69..0ffe2f6 100644
--- a/ui/src/app/app.constants.ts
+++ b/ui/src/app/app.constants.ts
@@ -56,7 +56,6 @@ export enum MigrationDetails {
   IsMigrationDetailSet = "isMigrationDetailSet",
   IsMigrationInProgress ='isMigrationInProgress',
   HasDataMigrationStarted ='hasDataMigrationStarted',
-  HasDataMigrationCompleted = 'hasDataMigrationCompleted',
   HasSchemaMigrationStarted = 'hasSchemaMigrationStarted',
   SchemaProgressMessage = 'schemaProgressMessage',
   DataProgressMessage = 'dataProgressMessage',
@@ -78,3 +77,11 @@ export const Profile = {
   NewConnProfile: 'new',
   ExistingConnProfile: 'existing',
 }
+
+export enum ProgressStatus {
+	SchemaMigrationComplete = 1,
+	SchemaCreationInProgress = 2,
+	DataMigrationComplete = 3,
+	DataWriteInProgress = 4,
+	ForeignKeyUpdateInProgress = 5
+}
diff --git a/ui/src/app/components/connection-profile-form/connection-profile-form.component.html b/ui/src/app/components/connection-profile-form/connection-profile-form.component.html
index 00e1151..fdc1ab8 100644
--- a/ui/src/app/components/connection-profile-form/connection-profile-form.component.html
+++ b/ui/src/app/components/connection-profile-form/connection-profile-form.component.html
@@ -52,12 +52,6 @@
                     check_circle
                 </mat-icon>
             </div>
-            <div *ngIf="!isSource">
-                <mat-form-field appearance="outline">
-                    <mat-label>Bucket name</mat-label>
-                    <input matInput placeholder="Bucket name" type="text" formControlName="bucket" required="true" />
-                </mat-form-field>
-            </div>
         </div>
 
         <div mat-dialog-actions class="buttons-container">
diff --git a/ui/src/app/components/connection-profile-form/connection-profile-form.component.ts b/ui/src/app/components/connection-profile-form/connection-profile-form.component.ts
index e42336a..42f0d0b 100644
--- a/ui/src/app/components/connection-profile-form/connection-profile-form.component.ts
+++ b/ui/src/app/components/connection-profile-form/connection-profile-form.component.ts
@@ -42,7 +42,6 @@ export class ConnectionProfileFormComponent implements OnInit {
       profileOption: ['', Validators.required],
       newProfile: [],
       existingProfile: [],
-      bucket: [],
     })
     if (this.selectedRegion != '') {
       this.getConnectionProfilesAndIps(localStorage.getItem(TargetDetails.Region) as string)
@@ -54,15 +53,11 @@ export class ConnectionProfileFormComponent implements OnInit {
     if (this.selectedOption == Profile.NewConnProfile) {
       this.connectionProfileForm.get('newProfile')?.setValidators([Validators.required])
       this.connectionProfileForm.controls['existingProfile'].clearValidators()
-      this.connectionProfileForm.get('bucket')?.setValidators([Validators.required])
-      this.connectionProfileForm.controls['bucket'].updateValueAndValidity()
       this.connectionProfileForm.controls['newProfile'].updateValueAndValidity()
       this.connectionProfileForm.controls['existingProfile'].updateValueAndValidity()
     } else {
       this.connectionProfileForm.controls['newProfile'].clearValidators()
       this.connectionProfileForm.get('existingProfile')?.addValidators([Validators.required])
-      this.connectionProfileForm.get('bucket')?.clearValidators()
-      this.connectionProfileForm.controls['bucket'].updateValueAndValidity()
       this.connectionProfileForm.controls['newProfile'].updateValueAndValidity()
       this.connectionProfileForm.controls['existingProfile'].updateValueAndValidity()
     }
@@ -73,8 +68,7 @@ export class ConnectionProfileFormComponent implements OnInit {
       Id: formValue.newProfile,
       Region: this.selectedRegion,
       IsSource: this.isSource,
-      ValidateOnly: true,
-      Bucket: formValue.bucket
+      ValidateOnly: true
     }
     this.fetch.createConnectionProfile(payload).subscribe({
       next: () => {
@@ -82,6 +76,7 @@ export class ConnectionProfileFormComponent implements OnInit {
       },
       error: (err: any) => {
         this.testSuccess = false
+        console.log(err)
         this.errorMsg = err
       },
     })
@@ -93,8 +88,7 @@ export class ConnectionProfileFormComponent implements OnInit {
         Id: formValue.newProfile,
         Region: this.selectedRegion,
         IsSource: this.isSource,
-        ValidateOnly: false,
-        Bucket: formValue.bucket
+        ValidateOnly: false
       }
       this.fetch.createConnectionProfile(payload).subscribe({
         next: () => {
diff --git a/ui/src/app/components/prepare-migration/prepare-migration.component.html b/ui/src/app/components/prepare-migration/prepare-migration.component.html
index fa3f1e2..20f5897 100644
--- a/ui/src/app/components/prepare-migration/prepare-migration.component.html
+++ b/ui/src/app/components/prepare-migration/prepare-migration.component.html
@@ -115,6 +115,19 @@
         <mat-progress-bar mode="determinate" [value]="dataMigrationProgress"></mat-progress-bar>
         <span> {{this.dataProgressMessage}}</span>
     </div>
+    <div *ngIf="isResourceGenerated">
+        <h3> Generated Resources:</h3>
+        <li>Spanner database: <a [href]="resourcesGenerated.DatabaseUrl">{{resourcesGenerated.DatabaseName}}</a></li>
+        <li>GCS bucket: <a [href]="resourcesGenerated.BucketUrl">{{resourcesGenerated.BucketName}}</a></li>
+        <span *ngIf="resourcesGenerated.DataStreamJobName!==''">
+            <li> Datastream job: <a
+                    [href]="resourcesGenerated.DataStreamJobUrl">{{resourcesGenerated.DataStreamJobName}}</a></li>
+        </span>
+        <span *ngIf="resourcesGenerated.DataflowJobName!==''">
+            <li>Dataflow job: <a [href]="resourcesGenerated.DataflowJobUrl">{{resourcesGenerated.DataflowJobName}}</a>
+            </li>
+        </span>
+    </div>
     <button mat-raised-button type="submit" color="primary" (click)="migrate()" class="migrate"
         [disabled]="!((isTargetDetailSet && (selectedMigrationType ==='lowdt') && isSourceConnectionProfileSet && isTargetConnectionProfileSet) || (isTargetDetailSet && (selectedMigrationType ==='bulk'))) || isMigrationInProgress">Migrate</button>
 </div>
\ No newline at end of file
diff --git a/ui/src/app/components/prepare-migration/prepare-migration.component.ts b/ui/src/app/components/prepare-migration/prepare-migration.component.ts
index 3e033c9..7e4cdd9 100644
--- a/ui/src/app/components/prepare-migration/prepare-migration.component.ts
+++ b/ui/src/app/components/prepare-migration/prepare-migration.component.ts
@@ -5,8 +5,8 @@ import { FetchService } from 'src/app/services/fetch/fetch.service'
 import { SnackbarService } from 'src/app/services/snackbar/snackbar.service'
 import ITargetDetails from 'src/app/model/target-details'
 import { ISessionSummary } from 'src/app/model/conv'
-import IMigrationDetails, { IProgress } from 'src/app/model/migrate'
-import { InputType, MigrationDetails, MigrationModes, MigrationTypes, SourceDbNames, TargetDetails } from 'src/app/app.constants'
+import IMigrationDetails, { IGeneratedResources, IProgress } from 'src/app/model/migrate'
+import { InputType, MigrationDetails, MigrationModes, MigrationTypes, ProgressStatus, SourceDbNames, TargetDetails } from 'src/app/app.constants'
 import { interval, Subscription } from 'rxjs'
 import { DataService } from 'src/app/services/data/data.service'
 import { ThisReceiver } from '@angular/compiler'
@@ -34,17 +34,27 @@ export class PrepareMigrationComponent implements OnInit {
   isMigrationDetailSet: boolean = false
   isStreamingSupported: boolean = false
   hasDataMigrationStarted: boolean = false
-  hasDataMigrationCompleted: boolean = false
   hasSchemaMigrationStarted: boolean = false
   selectedMigrationMode: string = MigrationModes.schemaOnly
   connectionType: string = InputType.DirectConnect
   selectedMigrationType: string = MigrationTypes.bulkMigration
   isMigrationInProgress: boolean = false
+  isResourceGenerated: boolean = false
   errorMessage: string = ''
   schemaProgressMessage: string = 'Schema migration in progress...'
   dataProgressMessage: string = 'Data migration in progress...'
   dataMigrationProgress: number = 0
   schemaMigrationProgress: number = 0
+  resourcesGenerated: IGeneratedResources = {
+    DatabaseName: '',
+    DatabaseUrl: '',
+    BucketName: '',
+    BucketUrl: '',
+    DataStreamJobName: '',
+    DataStreamJobUrl: '',
+    DataflowJobName: '',
+    DataflowJobUrl: ''
+  }
 
   targetDetails: ITargetDetails = {
     TargetDB: localStorage.getItem(TargetDetails.TargetDB) as string,
@@ -118,9 +128,6 @@ export class PrepareMigrationComponent implements OnInit {
     if (localStorage.getItem(MigrationDetails.HasDataMigrationStarted) != null) {
       this.hasDataMigrationStarted = (localStorage.getItem(MigrationDetails.HasDataMigrationStarted) as string === 'true')
     }
-    if (localStorage.getItem(MigrationDetails.HasDataMigrationCompleted) != null) {
-      this.hasDataMigrationCompleted = (localStorage.getItem(MigrationDetails.HasDataMigrationCompleted) as string === 'true')
-    }
     if (localStorage.getItem(MigrationDetails.DataMigrationProgress) != null) {
       this.dataMigrationProgress = parseInt(localStorage.getItem(MigrationDetails.DataMigrationProgress) as string)
     }
@@ -144,7 +151,6 @@ export class PrepareMigrationComponent implements OnInit {
     localStorage.removeItem(MigrationDetails.IsMigrationInProgress)
     localStorage.removeItem(MigrationDetails.HasSchemaMigrationStarted)
     localStorage.removeItem(MigrationDetails.HasDataMigrationStarted)
-    localStorage.removeItem(MigrationDetails.HasDataMigrationCompleted)
     localStorage.removeItem(MigrationDetails.DataMigrationProgress)
     localStorage.removeItem(MigrationDetails.SchemaMigrationProgress)
     localStorage.removeItem(MigrationDetails.DataProgressMessage)
@@ -229,40 +235,43 @@ export class PrepareMigrationComponent implements OnInit {
 
 
   subscribeMigrationProgress() {
+    var displayStreamingMsg = false
     this.subscription = interval(5000).subscribe((x => {
       this.fetch.getProgress().subscribe({
         next: (res: IProgress) => {
           if (res.ErrorMessage == '') {
             // Checking for completion of schema migration
-            if (res.Message.startsWith('Schema migration complete')) {
+            if (res.ProgressStatus == ProgressStatus.SchemaMigrationComplete) {
               localStorage.setItem(MigrationDetails.SchemaMigrationProgress, "100")
               this.schemaMigrationProgress = parseInt(localStorage.getItem(MigrationDetails.SchemaMigrationProgress) as string)
-              if (this.selectedMigrationMode == MigrationModes.schemaOnly || this.selectedMigrationType == MigrationTypes.lowDowntimeMigration) {
+              if (this.selectedMigrationMode == MigrationModes.schemaOnly) {
                 this.markMigrationComplete()
+              } else if (this.selectedMigrationType == MigrationTypes.lowDowntimeMigration) {
+                this.markSchemaMigrationComplete()
+                this.hasDataMigrationStarted = false
+                localStorage.setItem(MigrationDetails.HasDataMigrationStarted, this.hasDataMigrationStarted.toString())
+                if (!displayStreamingMsg) {
+                  this.snack.openSnackBarWithoutTimeout('Setting up dataflow and datastream jobs', 'Close')
+                  displayStreamingMsg = true
+                }
+              } else {
+                this.markSchemaMigrationComplete()
               }
             }
+            else if (res.ProgressStatus == ProgressStatus.DataMigrationComplete) {
+              this.markMigrationComplete()
+            }
             // Checking for data migration in progree
-            else if (res.Message.startsWith('Writing data to Spanner')) {
+            else if (res.ProgressStatus == ProgressStatus.DataWriteInProgress) {
               this.markSchemaMigrationComplete()
               localStorage.setItem(MigrationDetails.DataMigrationProgress, res.Progress.toString())
               this.dataMigrationProgress = parseInt(localStorage.getItem(MigrationDetails.DataMigrationProgress) as string)
-              if (this.hasDataMigrationCompleted) {
-                this.markMigrationComplete()
-              }
-              if (res.Progress == 100) {
-                localStorage.setItem(MigrationDetails.HasDataMigrationCompleted, "true")
-                this.hasDataMigrationCompleted = localStorage.getItem(MigrationDetails.HasDataMigrationCompleted) as string === 'true'
-
-              }
             }
             // Checking for foreign key update in progress
-            else if (res.Message.startsWith('Updating schema of database')) {
+            else if (res.ProgressStatus == ProgressStatus.ForeignKeyUpdateInProgress) {
               this.markSchemaMigrationComplete()
               this.dataMigrationProgress = 100
               localStorage.setItem(MigrationDetails.DataMigrationProgress, this.dataMigrationProgress.toString())
-              if (res.Progress == 100) {
-                this.markMigrationComplete()
-              }
             }
           } else {
             this.errorMessage = res.ErrorMessage;
@@ -297,17 +306,39 @@ export class PrepareMigrationComponent implements OnInit {
     this.isMigrationInProgress = !this.isMigrationInProgress
     this.dataProgressMessage = "Data migration completed successfully!"
     this.schemaProgressMessage = "Schema migration completed successfully!"
+    this.schemaMigrationProgress = 100
+    this.dataMigrationProgress = 100
+    this.fetch.getGeneratedResources().subscribe({
+      next: (res: IGeneratedResources) => {
+        this.isResourceGenerated = true
+        this.resourcesGenerated = res
+      },
+      error: (err: any) => {
+        this.snack.openSnackBar(err.error, 'Close')
+      },
+    })
     this.clearLocalStorage()
+    this.refreshPrerequisites()
   }
   resetValues() {
     this.isMigrationInProgress = !this.isMigrationInProgress
     this.hasSchemaMigrationStarted = false
     this.hasDataMigrationStarted = false
-    this.hasDataMigrationCompleted = false
     this.dataMigrationProgress = 0
     this.schemaMigrationProgress = 0
     this.schemaProgressMessage = "Schema migration in progress..."
     this.dataProgressMessage = "Data migration in progress..."
+    this.isResourceGenerated = false
+    this.resourcesGenerated = {
+      DatabaseName: '',
+      DatabaseUrl: '',
+      BucketName: '',
+      BucketUrl: '',
+      DataStreamJobName: '',
+      DataStreamJobUrl: '',
+      DataflowJobName: '',
+      DataflowJobUrl: ''
+    }
     this.initializeLocalStorage()
   }
   initializeLocalStorage() {
@@ -316,7 +347,6 @@ export class PrepareMigrationComponent implements OnInit {
     localStorage.setItem(MigrationDetails.IsMigrationInProgress, this.isMigrationInProgress.toString())
     localStorage.setItem(MigrationDetails.HasSchemaMigrationStarted, this.hasSchemaMigrationStarted.toString())
     localStorage.setItem(MigrationDetails.HasDataMigrationStarted, this.hasDataMigrationStarted.toString())
-    localStorage.setItem(MigrationDetails.HasDataMigrationCompleted, this.hasDataMigrationCompleted.toString())
     localStorage.setItem(MigrationDetails.DataMigrationProgress, this.dataMigrationProgress.toString())
     localStorage.setItem(MigrationDetails.SchemaMigrationProgress, this.schemaMigrationProgress.toString())
     localStorage.setItem(MigrationDetails.SchemaProgressMessage, this.schemaProgressMessage)
diff --git a/ui/src/app/model/migrate.ts b/ui/src/app/model/migrate.ts
index 1e12934..973ebde 100644
--- a/ui/src/app/model/migrate.ts
+++ b/ui/src/app/model/migrate.ts
@@ -9,5 +9,16 @@ export default interface IMigrationDetails {
 export interface IProgress {
     Progress: number
     ErrorMessage: string
-    Message: string
+    ProgressStatus: number
+}
+
+export interface IGeneratedResources {
+    DatabaseName: string
+    DatabaseUrl: string
+    BucketName: string
+    BucketUrl: string
+    DataStreamJobName: string
+    DataStreamJobUrl: string
+    DataflowJobName: string
+    DataflowJobUrl: string
 }
\ No newline at end of file
diff --git a/ui/src/app/model/profile.ts b/ui/src/app/model/profile.ts
index d87148a..ce9e8ac 100644
--- a/ui/src/app/model/profile.ts
+++ b/ui/src/app/model/profile.ts
@@ -8,5 +8,4 @@ export interface ICreateConnectionProfile{
     Region: string
     ValidateOnly: boolean
     IsSource: boolean
-    Bucket: string
 }
\ No newline at end of file
diff --git a/ui/src/app/services/fetch/fetch.service.ts b/ui/src/app/services/fetch/fetch.service.ts
index 03faa0a..885f374 100644
--- a/ui/src/app/services/fetch/fetch.service.ts
+++ b/ui/src/app/services/fetch/fetch.service.ts
@@ -7,7 +7,7 @@ import IConv, { ICreateIndex, IInterleaveStatus, IPrimaryKey, ISessionSummary }
 import IDumpConfig from '../../model/dump-config'
 import ISessionConfig from '../../model/session-config'
 import ISpannerConfig from '../../model/spanner-config'
-import IMigrationDetails, { IProgress } from 'src/app/model/migrate'
+import IMigrationDetails, { IGeneratedResources, IProgress } from 'src/app/model/migrate'
 import IConnectionProfile, { ICreateConnectionProfile } from 'src/app/model/profile'
 
 
@@ -57,6 +57,10 @@ export class FetchService {
     return this.http.get<IConnectionProfile[]>(`${this.url}/GetConnectionProfiles?region=${region}&source=${isSource}`)
   }
 
+  getGeneratedResources() {
+    return this.http.get<IGeneratedResources>(`${this.url}/GetGeneratedResources`)
+  }
+
   getStaticIps(region:string) {
     return this.http.get<string[]>(`${this.url}/GetStaticIps?region=${region}`)
   }
diff --git a/webv2/profile/profile.go b/webv2/profile/profile.go
index 1ffa5f6..86823cb 100644
--- a/webv2/profile/profile.go
+++ b/webv2/profile/profile.go
@@ -8,11 +8,14 @@ import (
 	"log"
 	"net/http"
 	"strconv"
+	"strings"
 
 	datastream "cloud.google.com/go/datastream/apiv1"
 	"github.com/cloudspannerecosystem/harbourbridge/common/constants"
+	"github.com/cloudspannerecosystem/harbourbridge/common/utils"
 	"github.com/cloudspannerecosystem/harbourbridge/webv2/helpers"
 	"github.com/cloudspannerecosystem/harbourbridge/webv2/session"
+	"github.com/google/uuid"
 	"google.golang.org/api/iterator"
 	datastreampb "google.golang.org/genproto/googleapis/cloud/datastream/v1"
 )
@@ -31,7 +34,7 @@ func GetBucket(project, location, profileName string) (string, error) {
 		return "", fmt.Errorf("could not get connection profile: %v", err)
 	}
 	gcsProfile := res.Profile.(*datastreampb.ConnectionProfile_GcsProfile).GcsProfile
-	return "gs://" + gcsProfile.GetBucket() + gcsProfile.GetRootPath(), nil
+	return gcsProfile.GetBucket() + gcsProfile.GetRootPath(), nil
 }
 
 func ListConnectionProfiles(w http.ResponseWriter, r *http.Request) {
@@ -44,6 +47,10 @@ func ListConnectionProfiles(w http.ResponseWriter, r *http.Request) {
 	sessionState := session.GetSessionState()
 	region := r.FormValue("region")
 	source := r.FormValue("source") == "true"
+	if !source {
+		sessionState.Conv.Audit.MigrationRequestId = "HB-" + uuid.New().String()
+		sessionState.Bucket = strings.ToLower(sessionState.Conv.Audit.MigrationRequestId) + "/"
+	}
 	databaseType, err := helpers.GetSourceDatabaseFromDriver(sessionState.Driver)
 	if err != nil {
 		http.Error(w, fmt.Sprintf("Error while getting source database: %v", err), http.StatusBadRequest)
@@ -140,7 +147,14 @@ func CreateConnectionProfile(w http.ResponseWriter, r *http.Request) {
 		},
 		ValidateOnly: details.ValidateOnly,
 	}
-	setConnectionProfile(details.IsSource, *sessionState, req, databaseType, details.Bucket)
+	if !details.IsSource {
+		err = utils.CreateGCSBucket(strings.ToLower(sessionState.Conv.Audit.MigrationRequestId), sessionState.GCPProjectID)
+		if err != nil {
+			http.Error(w, fmt.Sprintf("Error while creating bucket: %v", err), http.StatusBadRequest)
+			return
+		}
+	}
+	setConnectionProfile(details.IsSource, *sessionState, req, databaseType)
 	op, err := dsClient.CreateConnectionProfile(ctx, req)
 	if err != nil {
 		http.Error(w, fmt.Sprintf("Error while creating connection profile: %v", err), http.StatusBadRequest)
@@ -154,7 +168,7 @@ func CreateConnectionProfile(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func setConnectionProfile(isSource bool, sessionState session.SessionState, req *datastreampb.CreateConnectionProfileRequest, databaseType, bucket string) {
+func setConnectionProfile(isSource bool, sessionState session.SessionState, req *datastreampb.CreateConnectionProfileRequest, databaseType string) {
 	if isSource {
 		port, _ := strconv.ParseInt((sessionState.SourceDBConnDetails.Port), 10, 32)
 		if databaseType == constants.MYSQL {
@@ -179,7 +193,7 @@ func setConnectionProfile(isSource bool, sessionState session.SessionState, req
 	} else {
 		req.ConnectionProfile.Profile = &datastreampb.ConnectionProfile_GcsProfile{
 			GcsProfile: &datastreampb.GcsProfile{
-				Bucket:   bucket,
+				Bucket:   strings.ToLower(sessionState.Conv.Audit.MigrationRequestId),
 				RootPath: "/",
 			},
 		}
@@ -192,7 +206,6 @@ type connectionProfileReq struct {
 	Region       string
 	ValidateOnly bool
 	IsSource     bool
-	Bucket       string
 }
 
 type connectionProfile struct {
diff --git a/webv2/routes.go b/webv2/routes.go
index 8c9a93e..9205b63 100644
--- a/webv2/routes.go
+++ b/webv2/routes.go
@@ -81,6 +81,7 @@ func getRoutes() *mux.Router {
 	router.HandleFunc("/GetSourceDestinationSummary", getSourceDestinationSummary).Methods("GET")
 	router.HandleFunc("/GetProgress", updateProgress).Methods("GET")
 	router.HandleFunc("/GetLatestSessionDetails", fetchLastLoadedSessionDetails).Methods("GET")
+	router.HandleFunc("/GetGeneratedResources", getGeneratedResources).Methods("GET")
 
 	// Connection profiles
 	router.HandleFunc("/GetConnectionProfiles", profile.ListConnectionProfiles).Methods("GET")
diff --git a/webv2/session/types.go b/webv2/session/types.go
index adb53d2..28255bb 100644
--- a/webv2/session/types.go
+++ b/webv2/session/types.go
@@ -50,6 +50,9 @@ type SessionState struct {
 	IsOffline           bool                // True if the connection to remote metadata database is invalid
 	GCPProjectID        string
 	SpannerInstanceID   string
+	Region              string
+	SpannerDatabaseName string
+	Bucket              string
 	SessionMetadata     SessionMetadata
 	Error               error
 	Counter
diff --git a/webv2/web.go b/webv2/web.go
index 3696b2e..8b3d7f9 100644
--- a/webv2/web.go
+++ b/webv2/web.go
@@ -108,9 +108,9 @@ type sessionSummary struct {
 }
 
 type progressDetails struct {
-	Progress     int
-	ErrorMessage string
-	Message      string
+	Progress       int
+	ErrorMessage   string
+	ProgressStatus int
 }
 
 type migrationDetails struct {
@@ -1160,7 +1160,7 @@ func updateProgress(w http.ResponseWriter, r *http.Request) {
 		detail.ErrorMessage = sessionState.Error.Error()
 	} else {
 		detail.ErrorMessage = ""
-		detail.Progress, detail.Message = sessionState.Conv.Audit.Progress.ReportProgress()
+		detail.Progress, detail.ProgressStatus = sessionState.Conv.Audit.Progress.ReportProgress()
 	}
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(detail)
@@ -1187,16 +1187,20 @@ func migrate(w http.ResponseWriter, r *http.Request) {
 	sessionState.Error = nil
 	ctx := context.Background()
 	sessionState.Conv.Audit.Progress = internal.Progress{}
-	sessionState.Conv.Audit.MigrationRequestId = "HB-" + uuid.New().String()
 	sourceProfile, targetProfile, ioHelper, dbName, err := getSourceAndTargetProfiles(sessionState, details)
 	if err != nil {
 		log.Println("can't get source and target profile")
 		http.Error(w, fmt.Sprintf("Can't get source and target profiles: %v", err), http.StatusBadRequest)
 		return
 	}
+	err = writeSessionFile(sessionState)
+	if err != nil {
+		log.Println("can't write session file")
+		http.Error(w, fmt.Sprintf("Can't write session file to GCS: %v", err), http.StatusBadRequest)
+		return
+	}
 	sessionState.Conv.ResetStats()
 	sessionState.Conv.Audit.Progress = internal.Progress{}
-	sessionState.Conv.Audit.Progress.SetProgressMessageAndUpdate("Migration in progress", 0)
 	if details.MigrationMode == helpers.SCHEMA_ONLY {
 		log.Println("Starting schema only migration")
 		sessionState.Conv.Audit.MigrationType = migration.MigrationData_SCHEMA_ONLY.Enum()
@@ -1222,9 +1226,29 @@ func migrate(w http.ResponseWriter, r *http.Request) {
 	log.Println("migration completed", "method", r.Method, "path", r.URL.Path, "remoteaddr", r.RemoteAddr)
 }
 
+func getGeneratedResources(w http.ResponseWriter, r *http.Request) {
+	var generatedResources GeneratedResources
+	sessionState := session.GetSessionState()
+	generatedResources.DatabaseName = sessionState.SpannerDatabaseName
+	generatedResources.DatabaseUrl = fmt.Sprintf("https://pantheon.corp.google.com/spanner/instances/%v/databases/%v/details/tables?project=%v", sessionState.SpannerInstanceID, sessionState.SpannerDatabaseName, sessionState.GCPProjectID)
+	generatedResources.BucketName = sessionState.Bucket
+	generatedResources.BucketUrl = fmt.Sprintf("https://pantheon.corp.google.com/storage/browser/%v", sessionState.Bucket)
+	if sessionState.Conv.Audit.StreamingStats.DataStreamName != "" {
+		generatedResources.DataStreamJobName = sessionState.Conv.Audit.StreamingStats.DataStreamName
+		generatedResources.DataStreamJobUrl = fmt.Sprintf("https://pantheon.corp.google.com/datastream/streams/locations/%v/instances/%v?project=%v", sessionState.Region, sessionState.Conv.Audit.StreamingStats.DataStreamName, sessionState.GCPProjectID)
+	}
+	if sessionState.Conv.Audit.StreamingStats.DataflowJobId != "" {
+		generatedResources.DataflowJobName = sessionState.Conv.Audit.StreamingStats.DataflowJobId
+		generatedResources.DataflowJobUrl = fmt.Sprintf("https://pantheon.corp.google.com/dataflow/jobs/%v/%v?project=%v", sessionState.Region, sessionState.Conv.Audit.StreamingStats.DataflowJobId, sessionState.GCPProjectID)
+	}
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(generatedResources)
+}
+
 func getSourceAndTargetProfiles(sessionState *session.SessionState, details migrationDetails) (profiles.SourceProfile, profiles.TargetProfile, utils.IOStreams, string, error) {
 	var (
 		sourceProfileString string
+		err                 error
 	)
 	sourceDBConnectionDetails := sessionState.SourceDBConnDetails
 	if sourceDBConnectionDetails.ConnectionType == helpers.DUMP_MODE {
@@ -1234,14 +1258,23 @@ func getSourceAndTargetProfiles(sessionState *session.SessionState, details migr
 			sourceDBConnectionDetails.Host, sourceDBConnectionDetails.Port, sourceDBConnectionDetails.User,
 			sourceDBConnectionDetails.Password, sessionState.DbName)
 	}
+	sessionState.SpannerDatabaseName = details.TargetDetails.TargetDB
 	targetProfileString := fmt.Sprintf("project=%v,instance=%v,dbName=%v", sessionState.GCPProjectID, sessionState.SpannerInstanceID, details.TargetDetails.TargetDB)
 	if details.TargetDetails.Region != "" {
+		sessionState.Region = details.TargetDetails.Region
 		fileName := sessionState.Conv.Audit.MigrationRequestId + "-streaming.json"
-		err := createStreamingCfgFile(sessionState.GCPProjectID, details.TargetDetails, fileName)
+		sessionState.Bucket, err = profile.GetBucket(sessionState.GCPProjectID, sessionState.Region, details.TargetDetails.TargetConnectionProfileName)
 		if err != nil {
-			return profiles.SourceProfile{}, profiles.TargetProfile{}, utils.IOStreams{}, "", fmt.Errorf("error while craeting streaming config file: %v", err)
+			return profiles.SourceProfile{}, profiles.TargetProfile{}, utils.IOStreams{}, "", fmt.Errorf("error while getting target bucket: %v", err)
+		}
+		err = createStreamingCfgFile(sessionState.GCPProjectID, details.TargetDetails, fileName, sessionState.Bucket)
+		if err != nil {
+			return profiles.SourceProfile{}, profiles.TargetProfile{}, utils.IOStreams{}, "", fmt.Errorf("error while creating streaming config file: %v", err)
 		}
 		sourceProfileString = sourceProfileString + fmt.Sprintf(",streamingCfg=%v", fileName)
+	} else {
+		sessionState.Conv.Audit.MigrationRequestId = "HB-" + uuid.New().String()
+		sessionState.Bucket = strings.ToLower(sessionState.Conv.Audit.MigrationRequestId) + "/"
 	}
 	source, err := helpers.GetSourceDatabaseFromDriver(sessionState.Driver)
 	if err != nil {
@@ -1256,11 +1289,25 @@ func getSourceAndTargetProfiles(sessionState *session.SessionState, details migr
 	return sourceProfile, targetProfile, ioHelper, dbName, nil
 }
 
-func createStreamingCfgFile(projectId string, targetDetails targetDetails, fileName string) error {
-	bucket, err := profile.GetBucket(projectId, targetDetails.Region, targetDetails.TargetConnectionProfileName)
+func writeSessionFile(sessionState *session.SessionState) error {
+
+	err := utils.CreateGCSBucket(strings.ToLower(sessionState.Bucket[:len(sessionState.Bucket)-1]), sessionState.GCPProjectID)
+	if err != nil {
+		return fmt.Errorf("error while creating bucket: %v", err)
+	}
+
+	convJSON, err := json.MarshalIndent(sessionState.Conv, "", " ")
+	if err != nil {
+		return fmt.Errorf("can't encode session state to JSON: %v", err)
+	}
+	err = utils.WriteToGCS("gs://"+sessionState.Bucket, "session.json", string(convJSON))
 	if err != nil {
-		return fmt.Errorf("error while getting bucket details: %v", err)
+		return fmt.Errorf("error while writing to GCS: %v", err)
 	}
+	return nil
+}
+
+func createStreamingCfgFile(projectId string, targetDetails targetDetails, fileName, bucket string) error {
 	data := StreamingCfg{
 		DatastreamCfg: DatastreamCfg{
 			StreamId:          "",
@@ -1279,7 +1326,7 @@ func createStreamingCfgFile(projectId string, targetDetails targetDetails, fileN
 			JobName:  "",
 			Location: targetDetails.Region,
 		},
-		TmpDir: bucket,
+		TmpDir: "gs://" + bucket,
 	}
 
 	file, err := json.MarshalIndent(data, "", " ")
@@ -1530,6 +1577,17 @@ type typeIssue struct {
 	Brief string
 }
 
+type GeneratedResources struct {
+	DatabaseName      string
+	DatabaseUrl       string
+	BucketName        string
+	BucketUrl         string
+	DataStreamJobName string
+	DataStreamJobUrl  string
+	DataflowJobName   string
+	DataflowJobUrl    string
+}
+
 func addTypeToList(convertedType string, spType string, issues []internal.SchemaIssue, l []typeIssue) []typeIssue {
 	if convertedType == spType {
 		if len(issues) > 0 {


Test patch:
diff --git a/internal/progress_test.go b/internal/progress_test.go
index 231d6d2..f4f7ec8 100644
--- a/internal/progress_test.go
+++ b/internal/progress_test.go
@@ -23,7 +23,7 @@ import (
 
 func TestProgress(t *testing.T) {
 	total := int64(4321)
-	p := NewProgress(total, "Progress", false, false)
+	p := NewProgress(total, "Progress", false, false, int(DefaultStatus))
 	assert.Equal(t, 0, p.pct)
 	time.Sleep(500 * time.Millisecond)
 	for _, v := range []int64{1000, 2000, 3000} {
@@ -37,12 +37,12 @@ func TestProgress(t *testing.T) {
 	p.MaybeReport(5000)
 	assert.Equal(t, 100, p.pct) // Never exceed 100%.
 	// Test corner case where total is 0.
-	p = NewProgress(0, "Progress", false, false)
+	p = NewProgress(0, "Progress", false, false, int(DefaultStatus))
 	assert.Equal(t, 100, p.pct)
 }
 
 func TestDone(t *testing.T) {
-	p := NewProgress(567, "Progress", false, false)
+	p := NewProgress(567, "Progress", false, false, int(DefaultStatus))
 	assert.Equal(t, 0, p.pct)
 	p.Done()
 	assert.Equal(t, 100, p.pct)