@@ -5,18 +5,18 @@ const kc = new k8s.KubeConfig()
5
5
kc . loadFromDefault ( )
6
6
7
7
const k8sApi = kc . makeApiClient ( k8s . CustomObjectsApi )
8
-
8
+ const ORDERER_IMAGE_TAG = '3.0.0'
9
+ const PEER_IMAGE_TAG = '3.0.0'
9
10
async function updateOrdererTag ( ordererNames : string [ ] , namespace : string = 'default' ) {
10
11
for ( const ordererName of ordererNames ) {
11
12
try {
12
13
// Get the current FabricOrdererNode
13
14
const res = await k8sApi . getNamespacedCustomObject ( 'hlf.kungfusoftware.es' , 'v1alpha1' , namespace , 'fabricorderernodes' , ordererName )
14
15
15
16
const orderer = res . body as any
16
- console . log ( orderer )
17
- // Update the tag to 3.0.0-beta
17
+ // Update the tag to 3.0.0
18
18
if ( orderer . spec && orderer . spec . image ) {
19
- orderer . spec . tag = '3.0.0-beta'
19
+ orderer . spec . tag = ORDERER_IMAGE_TAG
20
20
} else {
21
21
console . error ( `Unable to update tag for orderer ${ ordererName } : image spec not found` )
22
22
continue
@@ -27,7 +27,7 @@ async function updateOrdererTag(ordererNames: string[], namespace: string = 'def
27
27
headers : { 'Content-Type' : 'application/merge-patch+json' } ,
28
28
} )
29
29
30
- console . log ( `Successfully updated tag for orderer ${ ordererName } to 3.0.0-beta ` )
30
+ console . log ( `Successfully updated tag for orderer ${ ordererName } to ${ ORDERER_IMAGE_TAG } ` )
31
31
} catch ( err ) {
32
32
console . error ( `Error updating orderer ${ ordererName } :` , err )
33
33
}
@@ -43,7 +43,7 @@ async function getOrderersFromClusterBelow30(namespace: string): Promise<any[]>
43
43
try {
44
44
const res = await k8sApi . listNamespacedCustomObject ( 'hlf.kungfusoftware.es' , 'v1alpha1' , namespace , 'fabricorderernodes' )
45
45
const ordererList = ( res . body as any ) . items
46
- return ordererList . filter ( ( orderer : any ) => orderer . spec . image . tag !== '3.0.0-beta' )
46
+ return ordererList . filter ( ( orderer : any ) => orderer . spec . image . tag !== ORDERER_IMAGE_TAG )
47
47
} catch ( err ) {
48
48
console . error ( 'Error fetching orderers from cluster:' , err )
49
49
return [ ]
@@ -74,7 +74,7 @@ async function updateOrderers(orderers: { name: string; namespace: string }[]) {
74
74
// Wait for the orderer to be ready with the new tag
75
75
let ready = false
76
76
const maxWaitTime = 10 * 60 * 1000 // 10 minutes in milliseconds
77
- const pollInterval = 10000 // 10 seconds
77
+ const pollInterval = 1000 // 1 second
78
78
79
79
const startTime = Date . now ( )
80
80
@@ -84,14 +84,14 @@ async function updateOrderers(orderers: { name: string; namespace: string }[]) {
84
84
const res = await appsV1Api . readNamespacedDeployment ( orderer . name , orderer . namespace )
85
85
const deployment = res . body
86
86
87
- const hasCorrectTag = deployment . spec ?. template . spec ?. containers . some ( ( container ) => container . image ?. includes ( '3.0.0-beta' ) )
87
+ const hasCorrectTag = deployment . spec ?. template . spec ?. containers . some ( ( container ) => container . image ?. includes ( ORDERER_IMAGE_TAG ) )
88
88
const isReady =
89
89
deployment . status ?. conditions ?. some ( ( condition ) => condition . type === 'Available' && condition . status === 'True' ) &&
90
90
deployment . status ?. readyReplicas === deployment . status ?. replicas
91
91
92
92
if ( hasCorrectTag && isReady ) {
93
93
ready = true
94
- console . log ( `Orderer ${ orderer . name } in namespace ${ orderer . namespace } is ready with tag 3.0.0-beta ` )
94
+ console . log ( `Orderer ${ orderer . name } in namespace ${ orderer . namespace } is ready with tag ${ ORDERER_IMAGE_TAG } ` )
95
95
} else {
96
96
const elapsedTime = Math . floor ( ( Date . now ( ) - startTime ) / 1000 )
97
97
console . log ( `Waiting for orderer ${ orderer . name } in namespace ${ orderer . namespace } to be ready (${ elapsedTime } seconds elapsed)...` )
@@ -296,6 +296,71 @@ async function getChannelFromKubernetes(channelName: string): Promise<any> {
296
296
}
297
297
}
298
298
299
+ async function updateChannelCapabilities ( channelName : string ) : Promise < void > {
300
+ try {
301
+ console . log ( `Updating channel ${ channelName } capabilities to V3_0...` )
302
+ const channel = await getChannelFromKubernetes ( channelName )
303
+
304
+ if ( channel . spec && channel . spec . channelConfig ) {
305
+ channel . spec . channelConfig . capabilities = [ 'V3_0' ]
306
+ } else {
307
+ console . error ( `Channel ${ channelName } configuration is not in the expected format.` )
308
+ throw new Error ( 'Invalid channel configuration' )
309
+ }
310
+
311
+ const kc = new k8s . KubeConfig ( )
312
+ kc . loadFromDefault ( )
313
+ const k8sApi = kc . makeApiClient ( k8s . CustomObjectsApi )
314
+
315
+ await k8sApi . patchNamespacedCustomObject ( 'hlf.kungfusoftware.es' , 'v1alpha1' , '' , 'fabricmainchannels' , channelName , channel , undefined , undefined , undefined , {
316
+ headers : { 'Content-Type' : 'application/merge-patch+json' } ,
317
+ } )
318
+ await waitForChannelCapabilitiesUpdate ( channelName , [ 'V3_0' ] )
319
+ console . log ( `Successfully updated channel ${ channelName } capabilities to V3_0` )
320
+ } catch ( error ) {
321
+ console . error ( `Error updating channel ${ channelName } capabilities:` , error )
322
+ throw error
323
+ }
324
+ }
325
+ async function waitForChannelCapabilitiesUpdate ( channelName : string , expectedCapabilities : string [ ] ) : Promise < void > {
326
+ console . log ( `Waiting for channel ${ channelName } capabilities to update...` )
327
+ const kc = new k8s . KubeConfig ( )
328
+ kc . loadFromDefault ( )
329
+ const k8sApi = kc . makeApiClient ( k8s . CoreV1Api )
330
+
331
+ const maxWaitTime = 5 * 60 * 1000 // 5 minutes in milliseconds
332
+ const pollInterval = 1000 // 1 second
333
+ const startTime = Date . now ( )
334
+
335
+ while ( Date . now ( ) - startTime < maxWaitTime ) {
336
+ try {
337
+ const res = await k8sApi . readNamespacedConfigMap ( `${ channelName } -config` , 'default' )
338
+ const configMap = res . body
339
+ const channelJson = JSON . parse ( configMap . data ! [ 'channel.json' ] )
340
+ const currentCapabilities = Object . keys ( channelJson . channel_group . values . Capabilities . value . capabilities || { } )
341
+
342
+ if ( arraysEqual ( currentCapabilities , expectedCapabilities ) ) {
343
+ console . log ( `Channel ${ channelName } capabilities have been updated successfully.` )
344
+ return
345
+ }
346
+
347
+ console . log ( `Waiting for ${ channelName } capabilities to update. Current capabilities: ${ currentCapabilities } ` )
348
+ await new Promise ( ( resolve ) => setTimeout ( resolve , pollInterval ) )
349
+ } catch ( err ) {
350
+ console . error ( `Error checking ${ channelName } -config configmap:` , err )
351
+ await new Promise ( ( resolve ) => setTimeout ( resolve , pollInterval ) )
352
+ }
353
+ }
354
+
355
+ console . error ( `Timeout: ${ channelName } capabilities did not update within 5 minutes` )
356
+ throw new Error ( `Timeout waiting for ${ channelName } capabilities to update` )
357
+ }
358
+
359
+ function arraysEqual ( arr1 : string [ ] , arr2 : string [ ] ) : boolean {
360
+ if ( arr1 . length !== arr2 . length ) return false
361
+ return arr1 . every ( ( value , index ) => value === arr2 [ index ] )
362
+ }
363
+
299
364
async function updateChannelToBFT ( channelName : string ) : Promise < void > {
300
365
try {
301
366
console . log ( `Updating channel ${ channelName } to use BFT consensus...` )
@@ -305,36 +370,50 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
305
370
console . log ( channel )
306
371
// Update the consensus type to BFT
307
372
if ( channel . spec && channel . spec . channelConfig ) {
308
- channel . spec . channelConfig . capabilities = [ 'V3_0' ]
309
- channel . spec . channelConfig . application . capabilities = [ 'V3_0' ]
310
373
channel . spec . channelConfig . orderer . ordererType = 'BFT'
311
374
// go through channel.spec.orderers and ask either for the orderer name or the namespace (radio, select one), or ask for the identity file path to get the certificate from
312
375
const consenterMapping = [ ]
313
- let idx = 0
314
- for ( const orderer of channel . spec . orderers ) {
376
+ let idx = 1
377
+ const selectedOrderers = new Set ( )
378
+ for ( const orderer of channel . spec . orderers as {
379
+ host : string
380
+ port : number
381
+ tlsCert : string
382
+ } [ ] ) {
383
+ const availableOrderers = orderers . filter ( ( o ) => ! selectedOrderers . has ( o . metadata . name ) )
384
+ const choices = [
385
+ ...availableOrderers . map ( ( o ) => ( {
386
+ name : `${ o . metadata . name } (${ o . metadata . namespace } )` ,
387
+ value : `${ o . metadata . name } .${ o . metadata . namespace } ` ,
388
+ } ) ) ,
389
+ { name : 'Identity file path' , value : 'identity' } ,
390
+ ]
315
391
const selectedOrderer = await select ( {
316
- message : `Select the orderer ${ orderer . name } ( ${ orderer . namespace } ) for the consenter ${ orderer . host } :${ orderer . port } ` ,
317
- choices : [ ... orderers . map ( ( orderer ) => ( { name : orderer . metadata . name , value : orderer . metadata . name } ) ) , { name : 'Identity file path' , value : 'identity' } ] ,
392
+ message : `Select the orderer ${ orderer . host } for the consenter ${ orderer . host } :${ orderer . port } ` ,
393
+ choices : choices ,
318
394
} )
395
+ console . log ( 'selectedOrderer' , selectedOrderer )
319
396
let identityCert = ''
320
397
let mspId = ''
321
398
if ( selectedOrderer === 'identity' ) {
322
399
const identity = await input ( { message : 'Enter the identity file path:' } )
323
400
identityCert = ( await readFile ( identity ) ) . toString ( 'utf-8' )
324
- // ask for the mspId
325
401
mspId = await input ( { message : 'Enter the mspId:' } )
326
402
} else {
327
- // get fabricorderernode and get the identity cert from `status.signCert`
328
- const fabricOrdererNode = await getFabricOrdererNode ( selectedOrderer )
403
+ const [ name , namespace ] = selectedOrderer . split ( '.' )
404
+ const fabricOrdererNode = await getFabricOrdererNode ( name , namespace )
329
405
identityCert = fabricOrdererNode . status . signCert
330
406
mspId = fabricOrdererNode . spec . mspID
407
+ selectedOrderers . add ( selectedOrderer )
331
408
}
409
+
332
410
if ( ! identityCert ) {
333
411
throw new Error ( `Identity cert not found for orderer ${ selectedOrderer } ` )
334
412
}
335
413
if ( ! mspId ) {
336
414
throw new Error ( `MspId not found for orderer ${ selectedOrderer } ` )
337
415
}
416
+
338
417
consenterMapping . push ( {
339
418
client_tls_cert : orderer . tlsCert ,
340
419
host : orderer . host ,
@@ -344,6 +423,7 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
344
423
port : orderer . port ,
345
424
server_tls_cert : orderer . tlsCert ,
346
425
} )
426
+ idx ++
347
427
}
348
428
channel . spec . channelConfig . orderer . consenterMapping = consenterMapping
349
429
channel . spec . channelConfig . orderer . smartBFT = {
@@ -352,7 +432,7 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
352
432
incomingMessageBufferSize : 200 ,
353
433
leaderHeartbeatCount : 10 ,
354
434
leaderHeartbeatTimeout : '1m0s' ,
355
- leaderRotation : 2 ,
435
+ leaderRotation : 0 ,
356
436
requestAutoRemoveTimeout : '3m' ,
357
437
requestBatchMaxBytes : 10485760 ,
358
438
requestBatchMaxCount : 100 ,
@@ -387,14 +467,95 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
387
467
}
388
468
}
389
469
470
+ async function getPeersFromClusterBelow30 ( namespace : string ) : Promise < any [ ] > {
471
+ const kc = new k8s . KubeConfig ( )
472
+ kc . loadFromDefault ( )
473
+
474
+ const k8sApi = kc . makeApiClient ( k8s . CustomObjectsApi )
475
+
476
+ try {
477
+ const res = await k8sApi . listNamespacedCustomObject ( 'hlf.kungfusoftware.es' , 'v1alpha1' , namespace , 'fabricpeers' )
478
+ const peerList = ( res . body as any ) . items
479
+ return peerList . filter ( ( peer : any ) => peer . spec . image . tag !== PEER_IMAGE_TAG )
480
+ } catch ( err ) {
481
+ console . error ( 'Error fetching peers from cluster:' , err )
482
+ return [ ]
483
+ }
484
+ }
485
+
486
+ async function updatePeerTag ( peerNames : string [ ] , namespace : string = 'default' ) {
487
+ for ( const peerName of peerNames ) {
488
+ try {
489
+ const res = await k8sApi . getNamespacedCustomObject ( 'hlf.kungfusoftware.es' , 'v1alpha1' , namespace , 'fabricpeers' , peerName )
490
+
491
+ const peer = res . body as any
492
+ if ( peer . spec && peer . spec . image ) {
493
+ peer . spec . tag = PEER_IMAGE_TAG
494
+ } else {
495
+ console . error ( `Unable to update tag for peer ${ peerName } : image spec not found` )
496
+ continue
497
+ }
498
+
499
+ await k8sApi . patchNamespacedCustomObject ( 'hlf.kungfusoftware.es' , 'v1alpha1' , namespace , 'fabricpeers' , peerName , peer , undefined , undefined , undefined , {
500
+ headers : { 'Content-Type' : 'application/merge-patch+json' } ,
501
+ } )
502
+
503
+ console . log ( `Successfully updated tag for peer ${ peerName } to ${ PEER_IMAGE_TAG } ` )
504
+ } catch ( err ) {
505
+ console . error ( `Error updating peer ${ peerName } :` , err )
506
+ }
507
+ }
508
+ }
509
+
510
+ async function updatePeers ( peers : { name : string ; namespace : string } [ ] ) {
511
+ for ( const peer of peers ) {
512
+ await updatePeerTag ( [ peer . name ] , peer . namespace )
513
+ console . log ( `Waiting for peer ${ peer . name } in namespace ${ peer . namespace } to be ready...` )
514
+
515
+ let ready = false
516
+ const maxWaitTime = 10 * 60 * 1000 // 10 minutes in milliseconds
517
+ const pollInterval = 1000 // 1 second
518
+
519
+ const startTime = Date . now ( )
520
+
521
+ while ( ! ready && Date . now ( ) - startTime < maxWaitTime ) {
522
+ try {
523
+ const appsV1Api = kc . makeApiClient ( k8s . AppsV1Api )
524
+ const res = await appsV1Api . readNamespacedDeployment ( peer . name , peer . namespace )
525
+ const deployment = res . body
526
+
527
+ const hasCorrectTag = deployment . spec ?. template . spec ?. containers . some ( ( container ) => container . image ?. includes ( PEER_IMAGE_TAG ) )
528
+ const isReady =
529
+ deployment . status ?. conditions ?. some ( ( condition ) => condition . type === 'Available' && condition . status === 'True' ) &&
530
+ deployment . status ?. readyReplicas === deployment . status ?. replicas
531
+
532
+ if ( hasCorrectTag && isReady ) {
533
+ ready = true
534
+ console . log ( `Peer ${ peer . name } in namespace ${ peer . namespace } is ready with tag ${ PEER_IMAGE_TAG } ` )
535
+ } else {
536
+ const elapsedTime = Math . floor ( ( Date . now ( ) - startTime ) / 1000 )
537
+ console . log ( `Waiting for peer ${ peer . name } in namespace ${ peer . namespace } to be ready (${ elapsedTime } seconds elapsed)...` )
538
+ await new Promise ( ( resolve ) => setTimeout ( resolve , pollInterval ) )
539
+ }
540
+ } catch ( err ) {
541
+ console . error ( `Error checking peer ${ peer . name } in namespace ${ peer . namespace } status:` , err )
542
+ await new Promise ( ( resolve ) => setTimeout ( resolve , pollInterval ) )
543
+ }
544
+ }
545
+
546
+ if ( ! ready ) {
547
+ console . error ( `Peer ${ peer . name } in namespace ${ peer . namespace } did not become ready within the expected time.` )
548
+ }
549
+ }
550
+ }
551
+
390
552
async function main ( ) {
391
553
const channelName = await input ( { message : 'Enter the channel name:' } )
392
554
const channel = await getChannelFromKubernetes ( channelName )
393
- console . log ( channel )
394
555
// const ordererNamesInput = await input({ message: 'Enter orderer names (comma-separated):' })
395
556
const ordererList = await getOrderersFromClusterBelow30 ( '' )
396
557
const selectedOrderers = await checkbox ( {
397
- message : ' What orderers do you want to upgrade to 3.0.0-beta?' ,
558
+ message : ` What orderers do you want to upgrade to ${ ORDERER_IMAGE_TAG } ?` ,
398
559
choices : ordererList . map ( ( orderer : any ) => ( {
399
560
name : orderer . metadata . name ,
400
561
value : {
@@ -407,13 +568,37 @@ async function main() {
407
568
// console.log('selectedOrderers', selectedOrderers)
408
569
// ask for confirmation on to upgrade the selected orderers
409
570
const confirmed = await confirm ( {
410
- message : `Upgrade the following orderers to version 3.0.0-beta ?\n${ selectedOrderers . map ( ( orderer ) => `- ${ orderer . name } (${ orderer . namespace } )` ) . join ( '\n' ) } ` ,
571
+ message : `Upgrade the following orderers to version ${ ORDERER_IMAGE_TAG } ?\n${ selectedOrderers . map ( ( orderer ) => `- ${ orderer . name } (${ orderer . namespace } )` ) . join ( '\n' ) } ` ,
411
572
default : true ,
412
573
} )
413
574
if ( confirmed ) {
414
575
console . log ( 'Upgrading the selected orderers...' )
415
576
await updateOrderers ( selectedOrderers )
416
577
}
578
+
579
+ // Add peer upgrade step
580
+ const peerList = await getPeersFromClusterBelow30 ( '' )
581
+ const selectedPeers = await checkbox ( {
582
+ message : `What peers do you want to upgrade to ${ PEER_IMAGE_TAG } ?` ,
583
+ choices : peerList . map ( ( peer : any ) => ( {
584
+ name : peer . metadata . name ,
585
+ value : {
586
+ name : peer . metadata . name ,
587
+ namespace : peer . metadata . namespace ,
588
+ } ,
589
+ checked : true ,
590
+ } ) ) ,
591
+ } )
592
+
593
+ const peerConfirmed = await confirm ( {
594
+ message : `Upgrade the following peers to version ${ PEER_IMAGE_TAG } ?\n${ selectedPeers . map ( ( peer ) => `- ${ peer . name } (${ peer . namespace } )` ) . join ( '\n' ) } ` ,
595
+ default : true ,
596
+ } )
597
+
598
+ if ( peerConfirmed ) {
599
+ console . log ( 'Upgrading the selected peers...' )
600
+ await updatePeers ( selectedPeers )
601
+ }
417
602
// confirm set channel to maintenance
418
603
const stateConfirmed = await confirm ( {
419
604
message : `Set channel ${ channelName } to STATE_MAINTENANCE?` ,
@@ -422,6 +607,15 @@ async function main() {
422
607
if ( stateConfirmed ) {
423
608
await setFabricMainChannelToMaintenance ( channelName )
424
609
}
610
+
611
+ const capabilitiesConfirmed = await confirm ( {
612
+ message : `Update channel ${ channelName } capabilities to V3_0?` ,
613
+ default : true ,
614
+ } )
615
+ if ( capabilitiesConfirmed ) {
616
+ await updateChannelCapabilities ( channelName )
617
+ }
618
+
425
619
const bftConfirmed = await confirm ( {
426
620
message : `Update channel ${ channelName } to use BFT consensus?` ,
427
621
default : true ,
@@ -442,7 +636,7 @@ async function main() {
442
636
main ( ) . catch ( console . error )
443
637
444
638
// 1. Ask for backup of the orderers
445
- // 2. Update the orderers to the version 3.0.0-beta one by one and wait for the orderers to be ready
639
+ // 2. Update the orderers to the version 3.0.0 one by one and wait for the orderers to be ready
446
640
// 3. Set channel to STATE_MAINTENANCE
447
641
// 4. Wait for the channel to be updated by checking the ${channel}-config configmap
448
642
// 5. Add consenter_mapping to the channel and update the capabilities
0 commit comments