Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit 3f7a32f

Browse files
committed
fix: optimize kubectl watch load on apiserver
1) do a bulk fetch rather than a bunch of individual gets to kubectl 2) LivePaginatedTable had a bug with bulk table updates, where the deferredUpdate array would have duplicates Fixes #4494
1 parent 4ccb733 commit 3f7a32f

File tree

4 files changed

+41
-49
lines changed

4 files changed

+41
-49
lines changed

plugins/plugin-client-common/src/components/Content/Table/LivePaginatedTable.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export default class LivePaginatedTable extends PaginatedTable<LiveProps, LiveSt
121121
const nRowsBefore = existingRows.length
122122

123123
const foundIndex = existingRows.findIndex(_ => _.NAME === newKuiRow.name)
124+
124125
const insertionIndex = foundIndex === -1 ? nRowsBefore : foundIndex
125126

126127
const newRow = kuiRow2carbonRow(this.state.headers)(newKuiRow, insertionIndex)
@@ -146,7 +147,7 @@ export default class LivePaginatedTable extends PaginatedTable<LiveProps, LiveSt
146147
if (!batch) {
147148
this.setState({ rows: newRows })
148149
} else if (this._deferredUpdate) {
149-
this._deferredUpdate = this._deferredUpdate.concat(newRows)
150+
this._deferredUpdate = newRows
150151
} else {
151152
this._deferredUpdate = newRows
152153
}

plugins/plugin-kubectl/src/controller/kubectl/fqn.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 IBM Corporation
2+
* Copyright 2019-2020 IBM Corporation
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -53,7 +53,7 @@ function versionString(apiVersion: string): string {
5353
return group.length > 0 ? `.${version}.${group}` : ''
5454
}
5555

56-
function kindPart(apiVersion: string, kind: string) {
56+
export function kindPart(apiVersion: string, kind: string) {
5757
return `${kind}${versionString(apiVersion)}`
5858
}
5959

plugins/plugin-kubectl/src/controller/kubectl/watch/get-watch.ts

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import Debug from 'debug'
1818
import { Table, Arguments, CodedError, Streamable, Abortable, Watchable, Watcher, WatchPusher } from '@kui-shell/core'
1919

20-
import fqn from '../fqn'
20+
import { kindPart } from '../fqn'
2121
import { formatOf, KubeOptions, KubeExecOptions } from '../options'
2222

2323
import { Pair } from '../../../lib/view/formatTable'
@@ -146,56 +146,47 @@ class KubectlWatcher implements Abortable, Watcher {
146146
const { rows } = preprocessed
147147

148148
// now process the full rows into table view updates
149-
const tables = await Promise.all(
150-
rows.map(async row => {
151-
try {
152-
const [{ value: name }, { value: kind }, { value: apiVersion }, { value: namespace }] = row
149+
const kind = rows[0][1].value
150+
const apiVersion = rows[0][2].value
151+
const namespace = rows[0][3].value || 'default'
152+
const rowNames = rows.map(_ => _[0].value)
153153

154-
const getCommand = `${getCommandFromArgs(this.args)} get ${fqn(apiVersion, kind, name, namespace)} ${
155-
this.output ? `-o ${this.output}` : ''
156-
}`
154+
const getCommand = `${getCommandFromArgs(this.args)} get ${kindPart(apiVersion, kind)} ${rowNames.join(
155+
' '
156+
)} -n ${namespace} ${this.output ? `-o ${this.output}` : ''}`
157157

158-
// this is where we fetch the table columns the user
159-
// requested; note our use of the "output" variable,
160-
// which (above) we defined to be the user's schema
161-
// request
162-
return this.args.REPL.qexec<Table>(getCommand).catch((err: CodedError) => {
163-
// error fetching the row data
164-
// const rowKey = fqn(apiVersion, kind, name, namespace)
165-
if (err.code !== 404) {
166-
console.error(err)
167-
}
168-
this.pusher.offline(name)
169-
})
170-
} catch (err) {
171-
console.error('error handling watched row', err)
172-
}
173-
})
174-
)
158+
const table = await this.args.REPL.qexec<Table>(getCommand).catch((err: CodedError) => {
159+
if (err.code !== 404) {
160+
console.error(err)
161+
}
162+
// mark as all offline, if we got a 404 for the bulk get
163+
rowNames.forEach(name => this.pusher.offline(name))
164+
})
175165

176-
// in case the initial get was empty, we add the header to the
177-
// table; see https://github.com/kui-shell/plugin-kubeui/issues/219
178-
const tableWithHeader = tables.find(table => table && table.header)
179-
if (tableWithHeader && tableWithHeader.header) {
180-
// yup, we have a header; push it to the view
181-
this.pusher.header(tableWithHeader.header)
182-
}
166+
if (table) {
167+
// in case the initial get was empty, we add the header to the
168+
// table; see https://github.com/kui-shell/plugin-kubeui/issues/219
169+
if (table.header) {
170+
// yup, we have a header; push it to the view
171+
this.pusher.header(table.header)
172+
}
183173

184-
// based on the information we got back, 1) we push updates to
185-
// the table model; and 2) we may be able to discern that we
186-
// can stop watching
187-
tables.forEach(table => {
188-
if (table) {
189-
table.body.forEach(row => {
190-
// push an update to the table model
191-
// true means we want to do a batch update
174+
// based on the information we got back, 1) we push updates to
175+
// the table model; and 2) we may be able to discern that we
176+
// can stop watching
177+
table.body.forEach(row => {
178+
// push an update to the table model
179+
// true means we want to do a batch update
180+
if (row.isDeleted) {
181+
this.pusher.offline(row.name)
182+
} else {
192183
this.pusher.update(row, true)
193-
})
184+
}
185+
})
194186

195-
// batch update done!
196-
this.pusher.batchUpdateDone()
197-
}
198-
})
187+
// batch update done!
188+
this.pusher.batchUpdateDone()
189+
}
199190
} else {
200191
console.error('unknown streamable type', _)
201192
}

plugins/plugin-kubectl/src/test/k8s1/get-namespaces-with-watch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const testDrilldown = async (nsName: string, res: ReplExpect.AppAndCount) => {
104104
/** k get ns -w */
105105
const watchNS = function(this: Common.ISuite, kubectl: string) {
106106
const watchCmds = [
107-
`${kubectl} get ns -w`,
107+
// `${kubectl} get ns -w`, <-- not guaranteed to work locally, due to table pagination
108108
`${kubectl} get ns ${nsName} -w`,
109109
`${kubectl} get -w=true --watch ns ${nsName} --watch=true -w`
110110
]

0 commit comments

Comments
 (0)