Skip to content

[release-4.18] OCPBUGS-54337: Debug pod logs are not accessible when debugging a node via OpenShift Console #14942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: release-4.18
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 60 additions & 28 deletions frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import * as React from 'react';
import { Alert } from '@patternfly/react-core';
import { useTranslation, Trans } from 'react-i18next';
import { PodConnectLoader } from '@console/internal/components/pod';
import {
Firehose,
FirehoseResource,
FirehoseResult,
LoadingBox,
} from '@console/internal/components/utils';
import { NodeKind, PodKind } from '@console/internal/module/k8s';
import { PodExecLoader } from '../../../../../public/components/pod';
import { ImageStreamTagModel, NamespaceModel, PodModel } from '../../../../../public/models';
import { k8sCreate, k8sGet, k8sKillByName } from '../../../../../public/module/k8s';
import { ImageStreamTagModel, NamespaceModel, PodModel } from '@console/internal/models';
import { NodeKind, PodKind, k8sCreate, k8sGet, k8sKillByName } from '@console/internal/module/k8s';

type NodeTerminalErrorProps = {
error: React.ReactNode;
Expand All @@ -33,9 +32,15 @@ const getDebugImage = async (): Promise<string> => {
}
};

const getDebugPod = async (name: string, namespace: string, nodeName: string): Promise<PodKind> => {
const getDebugPod = async (
name: string,
namespace: string,
nodeName: string,
isWindows: boolean,
): Promise<PodKind> => {
const image = await getDebugImage();
return {
// configuration as specified in https://github.com/openshift/oc/blob/master/pkg/cli/debug/debug.go#L1024-L1114
const template: PodKind = {
kind: 'Pod',
apiVersion: 'v1',
metadata: {
Expand All @@ -48,43 +53,68 @@ const getDebugPod = async (name: string, namespace: string, nodeName: string): P
},
},
spec: {
activeDeadlineSeconds: 21600,
volumes: [
{
name: 'host',
hostPath: {
path: '/',
type: 'Directory',
},
},
],
containers: [
{
name: 'container-00',
image,
command: ['/bin/sh'],
resources: {},
volumeMounts: [
env: [
{
name: 'host',
mountPath: '/host',
// Set the Shell variable to auto-logout after 15m idle timeout
name: 'TMOUT',
value: '900',
},
{
// this env requires to be set in order to collect more sos reports
name: 'HOST',
value: '/host',
},
],
image,
name: 'container-00',
resources: {},
securityContext: {
privileged: true,
runAsUser: 0,
},
stdin: true,
stdinOnce: true,
tty: true,
volumeMounts: [
{
name: 'host',
mountPath: '/host',
},
],
},
],
restartPolicy: 'Never',
nodeName,
hostNetwork: true,
hostIPC: true,
hostPID: true,
hostNetwork: true,
nodeName,
restartPolicy: 'Never',
volumes: [
{
name: 'host',
hostPath: {
path: '/',
type: 'Directory',
},
},
],
},
};

if (isWindows) {
template.spec.OS = 'windows';
template.spec.hostPID = false;
template.spec.hostIPC = false;
const containerUser = 'ContainerUser';
template.spec.containers[0].securityContext = {
windowsOptions: {
runAsUserName: containerUser,
},
};
}
return template;
};

const NodeTerminalError: React.FC<NodeTerminalErrorProps> = ({ error }) => {
Expand Down Expand Up @@ -118,7 +148,7 @@ const NodeTerminalInner: React.FC<NodeTerminalInnerProps> = ({ obj }) => {
/>
);
case 'Running':
return <PodExecLoader obj={obj.data} message={message} />;
return <PodConnectLoader obj={obj.data} message={message} attach />;
default:
return <LoadingBox />;
}
Expand All @@ -128,6 +158,8 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
const [resources, setResources] = React.useState<FirehoseResource[]>([]);
const [errorMessage, setErrorMessage] = React.useState('');
const nodeName = node.metadata.name;
const isWindows = node.status?.nodeInfo?.operatingSystem === 'windows';

React.useEffect(() => {
let namespace;
const name = `${nodeName?.replace(/\./g, '-')}-debug`;
Expand Down Expand Up @@ -160,7 +192,7 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
},
},
});
const podToCreate = await getDebugPod(name, namespace.metadata.name, nodeName);
const podToCreate = await getDebugPod(name, namespace.metadata.name, nodeName, isWindows);
// wait for the namespace to be ready
await new Promise((resolve) => setTimeout(resolve, 1000));
const debugPod = await k8sCreate(PodModel, podToCreate);
Expand Down Expand Up @@ -188,7 +220,7 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
deleteNamespace(namespace.metadata.name);
window.removeEventListener('beforeunload', closeTab);
};
}, [nodeName]);
}, [nodeName, isWindows]);

return errorMessage ? (
<NodeTerminalError error={errorMessage} />
Expand Down
4 changes: 2 additions & 2 deletions frontend/public/components/debug-terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Alert } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { LoadingBox, PageHeading } from '@console/internal/components/utils';
import { ObjectMetadata, PodKind, k8sCreate, k8sKillByName } from '@console/internal/module/k8s';
import { PodExecLoader } from '@console/internal/components/pod';
import { PodConnectLoader } from '@console/internal/components/pod';
import { PodModel } from '@console/internal/models';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';

Expand Down Expand Up @@ -92,7 +92,7 @@ const DebugTerminalInner: React.FC<DebugTerminalInnerProps> = ({ debugPod, initi
);
case 'Running':
return (
<PodExecLoader
<PodConnectLoader
obj={debugPod}
initialContainer={initialContainer}
infoMessage={infoMessage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,26 @@ import { resourceURL } from '../module/k8s';
import { PodModel } from '../models';
import { isWindowsPod } from '../module/k8s/pods';

// pod exec WS protocol is FD prefixed, base64 encoded data (sometimes json stringified)
// pod connect WS protocol is FD prefixed, base64 encoded data (sometimes json stringified)

// Channel 0 is STDIN, 1 is STDOUT, 2 is STDERR (if TTY is not requested), and 3 is a special error channel - 4 is C&C
// The server only reads from STDIN, writes to the other three.
// see also: https://github.com/kubernetes/kubernetes/pull/13885

const EXEC_COMMAND = 'exec';
const ATTACH_COMMAND = 'attach';

const NO_SH =
'starting container process caused "exec: \\"sh\\": executable file not found in $PATH"';

const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
class PodExec extends React.PureComponent {
const PodConnect_ = connectToFlags(FLAGS.OPENSHIFT)(
class PodConnect extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
open: false,
containers: {},
attach: props.attach,
activeContainer:
props.initialContainer ||
props.obj.metadata?.annotations?.['kubectl.kubernetes.io/default-container'] ||
Expand All @@ -46,20 +50,22 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
const {
metadata: { name, namespace },
} = this.props.obj;
const { activeContainer } = this.state;
const { activeContainer, attach } = this.state;
const usedClient = this.props.flags[FLAGS.OPENSHIFT] ? 'oc' : 'kubectl';
const command = isWindowsPod(this.props.obj) ? ['cmd'] : ['sh', '-i', '-c', 'TERM=xterm sh'];
const params = {
ns: namespace,
name,
path: 'exec',
path: attach ? ATTACH_COMMAND : EXEC_COMMAND,
queryParams: {
stdout: 1,
stdin: 1,
stderr: 1,
tty: 1,
container: activeContainer,
command: command.map((c) => encodeURIComponent(c)).join('&command='),
...(!attach && {
command: command.map((c) => encodeURIComponent(c)).join('&command='),
}),
},
};

Expand Down Expand Up @@ -227,4 +233,4 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
},
);

export const PodExec = withTranslation()(PodExec_);
export const PodConnect = withTranslation()(PodConnect_);
11 changes: 7 additions & 4 deletions frontend/public/components/pod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -963,22 +963,24 @@ const PodEnvironmentComponent = (props) => (
<EnvironmentPage obj={props.obj} rawEnvData={props.obj.spec} envPath={envPath} readOnly={true} />
);

export const PodExecLoader: React.FC<PodExecLoaderProps> = ({
export const PodConnectLoader: React.FC<PodConnectLoaderProps> = ({
obj,
message,
initialContainer,
infoMessage,
attach = false,
}) => (
<div className="co-m-pane__body">
<div className="row">
<div className="col-xs-12">
<div className="panel-body">
<AsyncComponent
loader={() => import('./pod-exec').then((c) => c.PodExec)}
loader={() => import('./pod-connect').then((c) => c.PodConnect)}
obj={obj}
message={message}
infoMessage={infoMessage}
initialContainer={initialContainer}
attach={attach}
/>
</div>
</div>
Expand Down Expand Up @@ -1012,7 +1014,7 @@ export const PodsDetailsPage: React.FC<PodDetailsPageProps> = (props) => {
navFactory.envEditor(PodEnvironmentComponent),
navFactory.logs(PodLogs),
navFactory.events(ResourceEventStream),
navFactory.terminal(PodExecLoader),
navFactory.terminal(PodConnectLoader),
]}
/>
);
Expand Down Expand Up @@ -1244,11 +1246,12 @@ export type PodDetailsListProps = {
pod: PodKind;
};

type PodExecLoaderProps = {
type PodConnectLoaderProps = {
obj: PodKind;
message?: React.ReactElement;
infoMessage?: React.ReactElement;
initialContainer?: string;
attach?: boolean;
};

type PodDetailsProps = {
Expand Down