Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
Signed-off-by: David VIEJO <[email protected]>
  • Loading branch information
dviejokfs committed Oct 2, 2024
1 parent 53aed79 commit 965950f
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 26 deletions.
1 change: 0 additions & 1 deletion controllers/mainchannel/mainchannel_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,6 @@ func (r *FabricMainChannelReconciler) updateChannelConfig(ctx context.Context, f
}
isMaintenanceMode := ordererConfig.State == orderer.ConsensusStateMaintenance
switchingToMaintenanceMode := !isMaintenanceMode && newConfigTx.Orderer.State == orderer.ConsensusStateMaintenance
r.Log.Info("Is maintenance mode", "isMaintenanceMode", isMaintenanceMode, "switchingToMaintenanceMode", switchingToMaintenanceMode)

if !isMaintenanceMode && !switchingToMaintenanceMode {
if err := updateApplicationChannelConfigTx(currentConfigTx, newConfigTx); err != nil {
Expand Down
Binary file modified scripts/bun.lockb
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ const kc = new k8s.KubeConfig()
kc.loadFromDefault()

const k8sApi = kc.makeApiClient(k8s.CustomObjectsApi)

const ORDERER_IMAGE_TAG = '3.0.0'
const PEER_IMAGE_TAG = '3.0.0'
async function updateOrdererTag(ordererNames: string[], namespace: string = 'default') {
for (const ordererName of ordererNames) {
try {
// Get the current FabricOrdererNode
const res = await k8sApi.getNamespacedCustomObject('hlf.kungfusoftware.es', 'v1alpha1', namespace, 'fabricorderernodes', ordererName)

const orderer = res.body as any
console.log(orderer)
// Update the tag to 3.0.0-beta
// Update the tag to 3.0.0
if (orderer.spec && orderer.spec.image) {
orderer.spec.tag = '3.0.0-beta'
orderer.spec.tag = ORDERER_IMAGE_TAG
} else {
console.error(`Unable to update tag for orderer ${ordererName}: image spec not found`)
continue
Expand All @@ -27,7 +27,7 @@ async function updateOrdererTag(ordererNames: string[], namespace: string = 'def
headers: { 'Content-Type': 'application/merge-patch+json' },
})

console.log(`Successfully updated tag for orderer ${ordererName} to 3.0.0-beta`)
console.log(`Successfully updated tag for orderer ${ordererName} to ${ORDERER_IMAGE_TAG}`)
} catch (err) {
console.error(`Error updating orderer ${ordererName}:`, err)
}
Expand All @@ -43,7 +43,7 @@ async function getOrderersFromClusterBelow30(namespace: string): Promise<any[]>
try {
const res = await k8sApi.listNamespacedCustomObject('hlf.kungfusoftware.es', 'v1alpha1', namespace, 'fabricorderernodes')
const ordererList = (res.body as any).items
return ordererList.filter((orderer: any) => orderer.spec.image.tag !== '3.0.0-beta')
return ordererList.filter((orderer: any) => orderer.spec.image.tag !== ORDERER_IMAGE_TAG)
} catch (err) {
console.error('Error fetching orderers from cluster:', err)
return []
Expand Down Expand Up @@ -74,7 +74,7 @@ async function updateOrderers(orderers: { name: string; namespace: string }[]) {
// Wait for the orderer to be ready with the new tag
let ready = false
const maxWaitTime = 10 * 60 * 1000 // 10 minutes in milliseconds
const pollInterval = 10000 // 10 seconds
const pollInterval = 1000 // 1 second

const startTime = Date.now()

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

const hasCorrectTag = deployment.spec?.template.spec?.containers.some((container) => container.image?.includes('3.0.0-beta'))
const hasCorrectTag = deployment.spec?.template.spec?.containers.some((container) => container.image?.includes(ORDERER_IMAGE_TAG))
const isReady =
deployment.status?.conditions?.some((condition) => condition.type === 'Available' && condition.status === 'True') &&
deployment.status?.readyReplicas === deployment.status?.replicas

if (hasCorrectTag && isReady) {
ready = true
console.log(`Orderer ${orderer.name} in namespace ${orderer.namespace} is ready with tag 3.0.0-beta`)
console.log(`Orderer ${orderer.name} in namespace ${orderer.namespace} is ready with tag ${ORDERER_IMAGE_TAG}`)
} else {
const elapsedTime = Math.floor((Date.now() - startTime) / 1000)
console.log(`Waiting for orderer ${orderer.name} in namespace ${orderer.namespace} to be ready (${elapsedTime} seconds elapsed)...`)
Expand Down Expand Up @@ -296,6 +296,71 @@ async function getChannelFromKubernetes(channelName: string): Promise<any> {
}
}

async function updateChannelCapabilities(channelName: string): Promise<void> {
try {
console.log(`Updating channel ${channelName} capabilities to V3_0...`)
const channel = await getChannelFromKubernetes(channelName)

if (channel.spec && channel.spec.channelConfig) {
channel.spec.channelConfig.capabilities = ['V3_0']
} else {
console.error(`Channel ${channelName} configuration is not in the expected format.`)
throw new Error('Invalid channel configuration')
}

const kc = new k8s.KubeConfig()
kc.loadFromDefault()
const k8sApi = kc.makeApiClient(k8s.CustomObjectsApi)

await k8sApi.patchNamespacedCustomObject('hlf.kungfusoftware.es', 'v1alpha1', '', 'fabricmainchannels', channelName, channel, undefined, undefined, undefined, {
headers: { 'Content-Type': 'application/merge-patch+json' },
})
await waitForChannelCapabilitiesUpdate(channelName, ['V3_0'])
console.log(`Successfully updated channel ${channelName} capabilities to V3_0`)
} catch (error) {
console.error(`Error updating channel ${channelName} capabilities:`, error)
throw error
}
}
async function waitForChannelCapabilitiesUpdate(channelName: string, expectedCapabilities: string[]): Promise<void> {
console.log(`Waiting for channel ${channelName} capabilities to update...`)
const kc = new k8s.KubeConfig()
kc.loadFromDefault()
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)

const maxWaitTime = 5 * 60 * 1000 // 5 minutes in milliseconds
const pollInterval = 1000 // 1 second
const startTime = Date.now()

while (Date.now() - startTime < maxWaitTime) {
try {
const res = await k8sApi.readNamespacedConfigMap(`${channelName}-config`, 'default')
const configMap = res.body
const channelJson = JSON.parse(configMap.data!['channel.json'])
const currentCapabilities = Object.keys(channelJson.channel_group.values.Capabilities.value.capabilities || {})

if (arraysEqual(currentCapabilities, expectedCapabilities)) {
console.log(`Channel ${channelName} capabilities have been updated successfully.`)
return
}

console.log(`Waiting for ${channelName} capabilities to update. Current capabilities: ${currentCapabilities}`)
await new Promise((resolve) => setTimeout(resolve, pollInterval))
} catch (err) {
console.error(`Error checking ${channelName}-config configmap:`, err)
await new Promise((resolve) => setTimeout(resolve, pollInterval))
}
}

console.error(`Timeout: ${channelName} capabilities did not update within 5 minutes`)
throw new Error(`Timeout waiting for ${channelName} capabilities to update`)
}

function arraysEqual(arr1: string[], arr2: string[]): boolean {
if (arr1.length !== arr2.length) return false
return arr1.every((value, index) => value === arr2[index])
}

async function updateChannelToBFT(channelName: string): Promise<void> {
try {
console.log(`Updating channel ${channelName} to use BFT consensus...`)
Expand All @@ -305,36 +370,50 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
console.log(channel)
// Update the consensus type to BFT
if (channel.spec && channel.spec.channelConfig) {
channel.spec.channelConfig.capabilities = ['V3_0']
channel.spec.channelConfig.application.capabilities = ['V3_0']
channel.spec.channelConfig.orderer.ordererType = 'BFT'
// 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
const consenterMapping = []
let idx = 0
for (const orderer of channel.spec.orderers) {
let idx = 1
const selectedOrderers = new Set()
for (const orderer of channel.spec.orderers as {
host: string
port: number
tlsCert: string
}[]) {
const availableOrderers = orderers.filter((o) => !selectedOrderers.has(o.metadata.name))
const choices = [
...availableOrderers.map((o) => ({
name: `${o.metadata.name} (${o.metadata.namespace})`,
value: `${o.metadata.name}.${o.metadata.namespace}`,
})),
{ name: 'Identity file path', value: 'identity' },
]
const selectedOrderer = await select({
message: `Select the orderer ${orderer.name} (${orderer.namespace}) for the consenter ${orderer.host}:${orderer.port}`,
choices: [...orderers.map((orderer) => ({ name: orderer.metadata.name, value: orderer.metadata.name })), { name: 'Identity file path', value: 'identity' }],
message: `Select the orderer ${orderer.host} for the consenter ${orderer.host}:${orderer.port}`,
choices: choices,
})
console.log('selectedOrderer', selectedOrderer)
let identityCert = ''
let mspId = ''
if (selectedOrderer === 'identity') {
const identity = await input({ message: 'Enter the identity file path:' })
identityCert = (await readFile(identity)).toString('utf-8')
// ask for the mspId
mspId = await input({ message: 'Enter the mspId:' })
} else {
// get fabricorderernode and get the identity cert from `status.signCert`
const fabricOrdererNode = await getFabricOrdererNode(selectedOrderer)
const [name, namespace] = selectedOrderer.split('.')
const fabricOrdererNode = await getFabricOrdererNode(name, namespace)
identityCert = fabricOrdererNode.status.signCert
mspId = fabricOrdererNode.spec.mspID
selectedOrderers.add(selectedOrderer)
}

if (!identityCert) {
throw new Error(`Identity cert not found for orderer ${selectedOrderer}`)
}
if (!mspId) {
throw new Error(`MspId not found for orderer ${selectedOrderer}`)
}

consenterMapping.push({
client_tls_cert: orderer.tlsCert,
host: orderer.host,
Expand All @@ -344,6 +423,7 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
port: orderer.port,
server_tls_cert: orderer.tlsCert,
})
idx++
}
channel.spec.channelConfig.orderer.consenterMapping = consenterMapping
channel.spec.channelConfig.orderer.smartBFT = {
Expand All @@ -352,7 +432,7 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
incomingMessageBufferSize: 200,
leaderHeartbeatCount: 10,
leaderHeartbeatTimeout: '1m0s',
leaderRotation: 2,
leaderRotation: 0,
requestAutoRemoveTimeout: '3m',
requestBatchMaxBytes: 10485760,
requestBatchMaxCount: 100,
Expand Down Expand Up @@ -387,14 +467,95 @@ async function updateChannelToBFT(channelName: string): Promise<void> {
}
}

async function getPeersFromClusterBelow30(namespace: string): Promise<any[]> {
const kc = new k8s.KubeConfig()
kc.loadFromDefault()

const k8sApi = kc.makeApiClient(k8s.CustomObjectsApi)

try {
const res = await k8sApi.listNamespacedCustomObject('hlf.kungfusoftware.es', 'v1alpha1', namespace, 'fabricpeers')
const peerList = (res.body as any).items
return peerList.filter((peer: any) => peer.spec.image.tag !== PEER_IMAGE_TAG)
} catch (err) {
console.error('Error fetching peers from cluster:', err)
return []
}
}

async function updatePeerTag(peerNames: string[], namespace: string = 'default') {
for (const peerName of peerNames) {
try {
const res = await k8sApi.getNamespacedCustomObject('hlf.kungfusoftware.es', 'v1alpha1', namespace, 'fabricpeers', peerName)

const peer = res.body as any
if (peer.spec && peer.spec.image) {
peer.spec.tag = PEER_IMAGE_TAG
} else {
console.error(`Unable to update tag for peer ${peerName}: image spec not found`)
continue
}

await k8sApi.patchNamespacedCustomObject('hlf.kungfusoftware.es', 'v1alpha1', namespace, 'fabricpeers', peerName, peer, undefined, undefined, undefined, {
headers: { 'Content-Type': 'application/merge-patch+json' },
})

console.log(`Successfully updated tag for peer ${peerName} to ${PEER_IMAGE_TAG}`)
} catch (err) {
console.error(`Error updating peer ${peerName}:`, err)
}
}
}

async function updatePeers(peers: { name: string; namespace: string }[]) {
for (const peer of peers) {
await updatePeerTag([peer.name], peer.namespace)
console.log(`Waiting for peer ${peer.name} in namespace ${peer.namespace} to be ready...`)

let ready = false
const maxWaitTime = 10 * 60 * 1000 // 10 minutes in milliseconds
const pollInterval = 1000 // 1 second

const startTime = Date.now()

while (!ready && Date.now() - startTime < maxWaitTime) {
try {
const appsV1Api = kc.makeApiClient(k8s.AppsV1Api)
const res = await appsV1Api.readNamespacedDeployment(peer.name, peer.namespace)
const deployment = res.body

const hasCorrectTag = deployment.spec?.template.spec?.containers.some((container) => container.image?.includes(PEER_IMAGE_TAG))
const isReady =
deployment.status?.conditions?.some((condition) => condition.type === 'Available' && condition.status === 'True') &&
deployment.status?.readyReplicas === deployment.status?.replicas

if (hasCorrectTag && isReady) {
ready = true
console.log(`Peer ${peer.name} in namespace ${peer.namespace} is ready with tag ${PEER_IMAGE_TAG}`)
} else {
const elapsedTime = Math.floor((Date.now() - startTime) / 1000)
console.log(`Waiting for peer ${peer.name} in namespace ${peer.namespace} to be ready (${elapsedTime} seconds elapsed)...`)
await new Promise((resolve) => setTimeout(resolve, pollInterval))
}
} catch (err) {
console.error(`Error checking peer ${peer.name} in namespace ${peer.namespace} status:`, err)
await new Promise((resolve) => setTimeout(resolve, pollInterval))
}
}

if (!ready) {
console.error(`Peer ${peer.name} in namespace ${peer.namespace} did not become ready within the expected time.`)
}
}
}

async function main() {
const channelName = await input({ message: 'Enter the channel name:' })
const channel = await getChannelFromKubernetes(channelName)
console.log(channel)
// const ordererNamesInput = await input({ message: 'Enter orderer names (comma-separated):' })
const ordererList = await getOrderersFromClusterBelow30('')
const selectedOrderers = await checkbox({
message: 'What orderers do you want to upgrade to 3.0.0-beta?',
message: `What orderers do you want to upgrade to ${ORDERER_IMAGE_TAG}?`,
choices: ordererList.map((orderer: any) => ({
name: orderer.metadata.name,
value: {
Expand All @@ -407,13 +568,37 @@ async function main() {
// console.log('selectedOrderers', selectedOrderers)
// ask for confirmation on to upgrade the selected orderers
const confirmed = await confirm({
message: `Upgrade the following orderers to version 3.0.0-beta?\n${selectedOrderers.map((orderer) => `- ${orderer.name} (${orderer.namespace})`).join('\n')}`,
message: `Upgrade the following orderers to version ${ORDERER_IMAGE_TAG}?\n${selectedOrderers.map((orderer) => `- ${orderer.name} (${orderer.namespace})`).join('\n')}`,
default: true,
})
if (confirmed) {
console.log('Upgrading the selected orderers...')
await updateOrderers(selectedOrderers)
}

// Add peer upgrade step
const peerList = await getPeersFromClusterBelow30('')
const selectedPeers = await checkbox({
message: `What peers do you want to upgrade to ${PEER_IMAGE_TAG}?`,
choices: peerList.map((peer: any) => ({
name: peer.metadata.name,
value: {
name: peer.metadata.name,
namespace: peer.metadata.namespace,
},
checked: true,
})),
})

const peerConfirmed = await confirm({
message: `Upgrade the following peers to version ${PEER_IMAGE_TAG}?\n${selectedPeers.map((peer) => `- ${peer.name} (${peer.namespace})`).join('\n')}`,
default: true,
})

if (peerConfirmed) {
console.log('Upgrading the selected peers...')
await updatePeers(selectedPeers)
}
// confirm set channel to maintenance
const stateConfirmed = await confirm({
message: `Set channel ${channelName} to STATE_MAINTENANCE?`,
Expand All @@ -422,6 +607,15 @@ async function main() {
if (stateConfirmed) {
await setFabricMainChannelToMaintenance(channelName)
}

const capabilitiesConfirmed = await confirm({
message: `Update channel ${channelName} capabilities to V3_0?`,
default: true,
})
if (capabilitiesConfirmed) {
await updateChannelCapabilities(channelName)
}

const bftConfirmed = await confirm({
message: `Update channel ${channelName} to use BFT consensus?`,
default: true,
Expand All @@ -442,7 +636,7 @@ async function main() {
main().catch(console.error)

// 1. Ask for backup of the orderers
// 2. Update the orderers to the version 3.0.0-beta one by one and wait for the orderers to be ready
// 2. Update the orderers to the version 3.0.0 one by one and wait for the orderers to be ready
// 3. Set channel to STATE_MAINTENANCE
// 4. Wait for the channel to be updated by checking the ${channel}-config configmap
// 5. Add consenter_mapping to the channel and update the capabilities
Expand Down
4 changes: 2 additions & 2 deletions scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5.0.0"
"typescript": "^5.5.4"
},
"dependencies": {
"@inquirer/prompts": "^5.3.8",
"@inquirer/prompts": "^5.5.0",
"@kubernetes/client-node": "^0.21.0"
}
}

0 comments on commit 965950f

Please sign in to comment.