Skip to content

Commit

Permalink
feat(dashboard): watch mountpod accesslog (#1010)
Browse files Browse the repository at this point in the history
* feat(dashboard): watch mountpod accesslog

Signed-off-by: Xuhui zhang <[email protected]>
  • Loading branch information
zxh326 authored Jul 2, 2024
1 parent 8c2ea15 commit d60de1f
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 82 deletions.
23 changes: 20 additions & 3 deletions dashboard-ui-v2/src/components/containers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@

import { ProCard } from '@ant-design/pro-components'
import { Button, Space, Table, Tag } from 'antd'
import { Container, ContainerStatus } from 'kubernetes-types/core/v1'
import { ContainerStatus } from 'kubernetes-types/core/v1'
import { FormattedMessage } from 'react-intl'
import { useParams } from 'react-router-dom'

import LogModal from './log-modal'
import XTermModal from './xterm-modal'
import { DetailParams } from '@/types'
import { Pod } from '@/types/k8s'
import { isMountPod } from '@/utils'

const Containers: React.FC<{
containers: Array<Container>
pod: Pod
containerStatuses?: Array<ContainerStatus>
}> = (props) => {
const { containerStatuses } = props
const { pod, containerStatuses } = props

const { namespace, name } = useParams<DetailParams>()

Expand Down Expand Up @@ -64,6 +66,21 @@ const Containers: React.FC<{
key: 'action',
render: (record, c) => (
<Space>
{isMountPod(pod) ? (
<LogModal
namespace={namespace!}
name={name!}
container={record.name}
hasPrevious={false}
type="accesslog"
>
{({ onClick }) => (
<Button type="primary" onClick={onClick}>
Access Log
</Button>
)}
</LogModal>
) : null}
<LogModal
namespace={namespace!}
name={name!}
Expand Down
166 changes: 88 additions & 78 deletions dashboard-ui-v2/src/components/log-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,90 +25,100 @@ const LogModal: React.FC<{
name: string
container: string
hasPrevious?: boolean
type?: 'logs' | 'accesslog'
children: ({ onClick }: { onClick: () => void }) => ReactNode
}> = memo(({ namespace, name, container, hasPrevious, children }) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const [data, setData] = useState<string>('')
const [previous, setPrevious] = useState<boolean>(false)
}> = memo(
({ namespace, name, container, hasPrevious, children, type = 'logs' }) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const [data, setData] = useState<string>('')
const [previous, setPrevious] = useState<boolean>(false)

const [state, doFetch] = useDownloadPodLogs(namespace, name, container)
const [state, doFetch] = useDownloadPodLogs(namespace, name, container)

useWebsocket(
`/api/v1/ws/pod/${namespace}/${name}/${container}/logs`,
{
queryParams: {
previous: previous ? 'true' : 'false',
useWebsocket(
`/api/v1/ws/pod/${namespace}/${name}/${container}/${type}`,
{
queryParams: {
previous: previous ? 'true' : 'false',
},
onMessage: (msg) => {
setData((prev) => prev + msg.data)
},
},
onMessage: (msg) => {
setData((prev) => prev + msg.data)
},
},
isModalOpen,
)

const showModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false)
}
const handleCancel = () => {
setIsModalOpen(false)
}
isModalOpen,
)

useEffect(() => {
if (!isModalOpen) {
setData('')
const showModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false)
}
}, [isModalOpen])
const handleCancel = () => {
setIsModalOpen(false)
}

useEffect(() => {
if (!isModalOpen) {
setData('')
}
}, [isModalOpen])

return (
<>
{children({ onClick: showModal })}
<Modal
title={`Logs: ${namespace}/${name}/${container}`}
open={isModalOpen}
footer={() => (
<Space>
<Button onClick={() => setData('')}> Clear </Button>
<Button
loading={state.loading}
onClick={() => {
doFetch()
console.log('Download full log')
}}
>
Download full log
</Button>
<Button
onClick={() => {
setPrevious(!previous)
setData('')
}}
disabled={!hasPrevious}
>
{previous ? 'Show current log' : 'Show previous log'}
</Button>
</Space>
)}
onOk={handleOk}
onCancel={handleCancel}
>
{isModalOpen && (
<Editor
defaultLanguage="yaml"
options={{
wordWrap: 'on',
readOnly: true,
theme: 'vs-light', // TODO dark mode
scrollBeyondLastLine: false,
}}
value={data}
/>
)}
</Modal>
</>
)
})
return (
<>
{children({ onClick: showModal })}
{isModalOpen ? (
<Modal
title={`${type == 'accesslog' ? 'Access Log' : 'Log'}: ${namespace}/${name}/${container}`}
open={isModalOpen}
footer={() => (
<Space>
<Button onClick={() => setData('')}> Clear </Button>
{type === 'logs' ? (
<>
<Button
loading={state.loading}
onClick={() => {
doFetch()
console.log('Download full log')
}}
>
Download full log
</Button>
<Button
onClick={() => {
setPrevious(!previous)
setData('')
}}
disabled={!hasPrevious}
>
{previous ? 'Show current log' : 'Show previous log'}
</Button>
</>
) : null}
</Space>
)}
onOk={handleOk}
onCancel={handleCancel}
>
{isModalOpen && (
<Editor
defaultLanguage="yaml"
options={{
wordWrap: 'on',
readOnly: true,
theme: 'vs-light', // TODO dark mode
folding: true,
scrollBeyondLastLine: true,
}}
value={data}
/>
)}
</Modal>
) : null}
</>
)
},
)

export default LogModal
2 changes: 1 addition & 1 deletion dashboard-ui-v2/src/pages/pod-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const PodDetail: React.FC<{
>
<PodBasic pod={data} />
<Containers
containers={data.spec!.containers}
pod={data}
containerStatuses={data.status?.containerStatuses}
/>
<PodsTable
Expand Down
8 changes: 8 additions & 0 deletions dashboard-ui-v2/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,11 @@ export function getHost(): string {
const host = import.meta.env.VITE_HOST ?? window.location.host
return `${protocol}://${host}`
}

export function isMountPod(pod: Pod): boolean {
return (
(pod.metadata?.name?.startsWith('juicefs-') &&
pod.metadata?.labels?.['app.kubernetes.io/name'] === 'juicefs-mount') ||
false
)
}
2 changes: 2 additions & 0 deletions pkg/dashboard/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,7 @@ func (api *API) Handle(group *gin.RouterGroup) {

websocketAPI := group.Group("/ws")
websocketAPI.GET("/pod/:namespace/:name/:container/logs", api.watchPodLogs())
// only for mountpod
websocketAPI.GET("/pod/:namespace/:name/:container/accesslog", api.watchMountPodAccessLog())
websocketAPI.GET("/pod/:namespace/:name/:container/exec", api.execPod())
}
51 changes: 51 additions & 0 deletions pkg/dashboard/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/juicedata/juicefs-csi-driver/pkg/config"
"github.com/juicedata/juicefs-csi-driver/pkg/util"
)

type PodExtra struct {
Expand Down Expand Up @@ -854,3 +855,53 @@ func (api *API) execPod() gin.HandlerFunc {
}).ServeHTTP(c.Writer, c.Request)
}
}

func (api *API) watchMountPodAccessLog() gin.HandlerFunc {
return func(c *gin.Context) {
namespace := c.Param("namespace")
name := c.Param("name")
container := c.Param("container")
websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()
terminal := &terminalSession{
conn: ws,
sizeCh: make(chan *remotecommand.TerminalSize),
}
mountpod, err := api.client.CoreV1().Pods(namespace).Get(c, name, metav1.GetOptions{})
if err != nil {
klog.Error("Failed to get mount pod: ", err)
return
}
mntPath, _, err := util.GetMountPathOfPod(*mountpod)
if err != nil || mntPath == "" {
klog.Error("Failed to get mount path: ", err)
return
}
req := api.client.CoreV1().RESTClient().Post().
Resource("pods").
Name(name).
Namespace(namespace).SubResource("exec")
req.VersionedParams(&corev1.PodExecOptions{
Command: []string{"sh", "-c", "cat " + mntPath + "/.accesslog"},
Container: container,
Stdin: true,
Stdout: true,
Stderr: true,
}, scheme.ParameterCodec)

executor, err := remotecommand.NewSPDYExecutor(api.kubeconfig, "POST", req.URL())
if err != nil {
klog.Error("Failed to create SPDY executor: ", err)
return
}
if err := executor.Stream(remotecommand.StreamOptions{
Stdin: terminal,
Stdout: terminal,
Stderr: terminal,
}); err != nil {
klog.Error("Failed to stream: ", err)
return
}
}).ServeHTTP(c.Writer, c.Request)
}
}

0 comments on commit d60de1f

Please sign in to comment.