Skip to content

Commit 965950f

Browse files
committed
Update
Signed-off-by: David VIEJO <[email protected]>
1 parent 53aed79 commit 965950f

File tree

4 files changed

+219
-26
lines changed

4 files changed

+219
-26
lines changed

controllers/mainchannel/mainchannel_controller.go

-1
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,6 @@ func (r *FabricMainChannelReconciler) updateChannelConfig(ctx context.Context, f
580580
}
581581
isMaintenanceMode := ordererConfig.State == orderer.ConsensusStateMaintenance
582582
switchingToMaintenanceMode := !isMaintenanceMode && newConfigTx.Orderer.State == orderer.ConsensusStateMaintenance
583-
r.Log.Info("Is maintenance mode", "isMaintenanceMode", isMaintenanceMode, "switchingToMaintenanceMode", switchingToMaintenanceMode)
584583

585584
if !isMaintenanceMode && !switchingToMaintenanceMode {
586585
if err := updateApplicationChannelConfigTx(currentConfigTx, newConfigTx); err != nil {

scripts/bun.lockb

681 Bytes
Binary file not shown.

scripts/migrate-channel-raft-bft.tsx scripts/migrate-channel-raft-bft.ts

+217-23
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ const kc = new k8s.KubeConfig()
55
kc.loadFromDefault()
66

77
const k8sApi = kc.makeApiClient(k8s.CustomObjectsApi)
8-
8+
const ORDERER_IMAGE_TAG = '3.0.0'
9+
const PEER_IMAGE_TAG = '3.0.0'
910
async function updateOrdererTag(ordererNames: string[], namespace: string = 'default') {
1011
for (const ordererName of ordererNames) {
1112
try {
1213
// Get the current FabricOrdererNode
1314
const res = await k8sApi.getNamespacedCustomObject('hlf.kungfusoftware.es', 'v1alpha1', namespace, 'fabricorderernodes', ordererName)
1415

1516
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
1818
if (orderer.spec && orderer.spec.image) {
19-
orderer.spec.tag = '3.0.0-beta'
19+
orderer.spec.tag = ORDERER_IMAGE_TAG
2020
} else {
2121
console.error(`Unable to update tag for orderer ${ordererName}: image spec not found`)
2222
continue
@@ -27,7 +27,7 @@ async function updateOrdererTag(ordererNames: string[], namespace: string = 'def
2727
headers: { 'Content-Type': 'application/merge-patch+json' },
2828
})
2929

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}`)
3131
} catch (err) {
3232
console.error(`Error updating orderer ${ordererName}:`, err)
3333
}
@@ -43,7 +43,7 @@ async function getOrderersFromClusterBelow30(namespace: string): Promise<any[]>
4343
try {
4444
const res = await k8sApi.listNamespacedCustomObject('hlf.kungfusoftware.es', 'v1alpha1', namespace, 'fabricorderernodes')
4545
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)
4747
} catch (err) {
4848
console.error('Error fetching orderers from cluster:', err)
4949
return []
@@ -74,7 +74,7 @@ async function updateOrderers(orderers: { name: string; namespace: string }[]) {
7474
// Wait for the orderer to be ready with the new tag
7575
let ready = false
7676
const maxWaitTime = 10 * 60 * 1000 // 10 minutes in milliseconds
77-
const pollInterval = 10000 // 10 seconds
77+
const pollInterval = 1000 // 1 second
7878

7979
const startTime = Date.now()
8080

@@ -84,14 +84,14 @@ async function updateOrderers(orderers: { name: string; namespace: string }[]) {
8484
const res = await appsV1Api.readNamespacedDeployment(orderer.name, orderer.namespace)
8585
const deployment = res.body
8686

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))
8888
const isReady =
8989
deployment.status?.conditions?.some((condition) => condition.type === 'Available' && condition.status === 'True') &&
9090
deployment.status?.readyReplicas === deployment.status?.replicas
9191

9292
if (hasCorrectTag && isReady) {
9393
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}`)
9595
} else {
9696
const elapsedTime = Math.floor((Date.now() - startTime) / 1000)
9797
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> {
296296
}
297297
}
298298

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+
299364
async function updateChannelToBFT(channelName: string): Promise<void> {
300365
try {
301366
console.log(`Updating channel ${channelName} to use BFT consensus...`)
@@ -305,36 +370,50 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
305370
console.log(channel)
306371
// Update the consensus type to BFT
307372
if (channel.spec && channel.spec.channelConfig) {
308-
channel.spec.channelConfig.capabilities = ['V3_0']
309-
channel.spec.channelConfig.application.capabilities = ['V3_0']
310373
channel.spec.channelConfig.orderer.ordererType = 'BFT'
311374
// 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
312375
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+
]
315391
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,
318394
})
395+
console.log('selectedOrderer', selectedOrderer)
319396
let identityCert = ''
320397
let mspId = ''
321398
if (selectedOrderer === 'identity') {
322399
const identity = await input({ message: 'Enter the identity file path:' })
323400
identityCert = (await readFile(identity)).toString('utf-8')
324-
// ask for the mspId
325401
mspId = await input({ message: 'Enter the mspId:' })
326402
} 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)
329405
identityCert = fabricOrdererNode.status.signCert
330406
mspId = fabricOrdererNode.spec.mspID
407+
selectedOrderers.add(selectedOrderer)
331408
}
409+
332410
if (!identityCert) {
333411
throw new Error(`Identity cert not found for orderer ${selectedOrderer}`)
334412
}
335413
if (!mspId) {
336414
throw new Error(`MspId not found for orderer ${selectedOrderer}`)
337415
}
416+
338417
consenterMapping.push({
339418
client_tls_cert: orderer.tlsCert,
340419
host: orderer.host,
@@ -344,6 +423,7 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
344423
port: orderer.port,
345424
server_tls_cert: orderer.tlsCert,
346425
})
426+
idx++
347427
}
348428
channel.spec.channelConfig.orderer.consenterMapping = consenterMapping
349429
channel.spec.channelConfig.orderer.smartBFT = {
@@ -352,7 +432,7 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
352432
incomingMessageBufferSize: 200,
353433
leaderHeartbeatCount: 10,
354434
leaderHeartbeatTimeout: '1m0s',
355-
leaderRotation: 2,
435+
leaderRotation: 0,
356436
requestAutoRemoveTimeout: '3m',
357437
requestBatchMaxBytes: 10485760,
358438
requestBatchMaxCount: 100,
@@ -387,14 +467,95 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
387467
}
388468
}
389469

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+
390552
async function main() {
391553
const channelName = await input({ message: 'Enter the channel name:' })
392554
const channel = await getChannelFromKubernetes(channelName)
393-
console.log(channel)
394555
// const ordererNamesInput = await input({ message: 'Enter orderer names (comma-separated):' })
395556
const ordererList = await getOrderersFromClusterBelow30('')
396557
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}?`,
398559
choices: ordererList.map((orderer: any) => ({
399560
name: orderer.metadata.name,
400561
value: {
@@ -407,13 +568,37 @@ async function main() {
407568
// console.log('selectedOrderers', selectedOrderers)
408569
// ask for confirmation on to upgrade the selected orderers
409570
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')}`,
411572
default: true,
412573
})
413574
if (confirmed) {
414575
console.log('Upgrading the selected orderers...')
415576
await updateOrderers(selectedOrderers)
416577
}
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+
}
417602
// confirm set channel to maintenance
418603
const stateConfirmed = await confirm({
419604
message: `Set channel ${channelName} to STATE_MAINTENANCE?`,
@@ -422,6 +607,15 @@ async function main() {
422607
if (stateConfirmed) {
423608
await setFabricMainChannelToMaintenance(channelName)
424609
}
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+
425619
const bftConfirmed = await confirm({
426620
message: `Update channel ${channelName} to use BFT consensus?`,
427621
default: true,
@@ -442,7 +636,7 @@ async function main() {
442636
main().catch(console.error)
443637

444638
// 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
446640
// 3. Set channel to STATE_MAINTENANCE
447641
// 4. Wait for the channel to be updated by checking the ${channel}-config configmap
448642
// 5. Add consenter_mapping to the channel and update the capabilities

scripts/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
"@types/bun": "latest"
77
},
88
"peerDependencies": {
9-
"typescript": "^5.0.0"
9+
"typescript": "^5.5.4"
1010
},
1111
"dependencies": {
12-
"@inquirer/prompts": "^5.3.8",
12+
"@inquirer/prompts": "^5.5.0",
1313
"@kubernetes/client-node": "^0.21.0"
1414
}
1515
}

0 commit comments

Comments
 (0)