Skip to content

Commit

Permalink
feat: trigger upgrade in dashboard
Browse files Browse the repository at this point in the history
Signed-off-by: zwwhdls <[email protected]>
  • Loading branch information
zwwhdls committed Sep 4, 2024
1 parent 677f36f commit c1ef144
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 8 deletions.
31 changes: 29 additions & 2 deletions dashboard-ui-v2/src/components/containers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
*/

import { ProCard } from '@ant-design/pro-components'
import { Button, Space, Table, Tag, Tooltip } from 'antd'
import { Button, Space, Table, Tag, Tooltip, Popconfirm, message } from 'antd'
import type { PopconfirmProps } from 'antd'
import { ContainerStatus } from 'kubernetes-types/core/v1'
import { FormattedMessage } from 'react-intl'
import { useParams } from 'react-router-dom'
Expand All @@ -28,12 +29,13 @@ import {
AccessLogIcon,
DebugIcon,
LogIcon,
TerminalIcon,
TerminalIcon, UpgradeIcon,
WarmupIcon,
} from '@/icons'
import { DetailParams } from '@/types'
import { Pod } from '@/types/k8s'
import { isMountPod, supportDebug } from '@/utils'
import { useMountPodImage, useMountUpgrade } from '@/hooks/use-api.ts'

const Containers: React.FC<{
pod: Pod
Expand All @@ -42,7 +44,17 @@ const Containers: React.FC<{
const { pod, containerStatuses } = props

const { namespace, name } = useParams<DetailParams>()
const [, actions] = useMountUpgrade()

const confirm: PopconfirmProps['onConfirm'] = () => {
actions.execute(
namespace,
name,
)
message.success("Successfully trigger smoothly upgrade")
}

const { data } = useMountPodImage(namespace, name)
return (
<ProCard title={<FormattedMessage id="containerList" />}>
<Table
Expand Down Expand Up @@ -165,6 +177,21 @@ const Containers: React.FC<{
</Tooltip>
)}
</WarmupModal>

<Popconfirm
title="Smoothly Upgrade"
description={`Are you sure to upgrade to ${data}?`}
onConfirm={confirm}
okText="Yes"
cancelText="No"
>
<Tooltip title="Smoothly Upgrade" zIndex={0}>
<Button
className="action-button"
icon={<UpgradeIcon />}
/>
</Tooltip>
</Popconfirm>
</>
) : null}
</Space>
Expand Down
16 changes: 16 additions & 0 deletions dashboard-ui-v2/src/hooks/use-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ export function useSysAppPods(args: SysPagingListArgs) {
)
}

export function useMountPodImage(namespace?: string, name?: string) {
return useSWR<string>(`/api/v1/pod/${namespace}/${name}/latestimage`)
}

export function useMountUpgrade() {
return useAsync(async (namespace?: string, name?: string) => {
await fetch(`${getHost()}/api/v1/pod/${namespace}/${name}/upgrade`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
},
)
})
}

export function useAppPod(namespace?: string, name?: string) {
return useSWR<Pod>(`/api/v1/pod/${namespace}/${name}/`)
}
Expand Down
21 changes: 19 additions & 2 deletions dashboard-ui-v2/src/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ const LocaleIcon = (props: Partial<CustomIconComponentProps>) => (
<Icon
component={() => (
<svg viewBox="0 0 1024 1024" width="1em" height="1em">
<path d="M549.12 642.986667l-108.373333-107.093334 1.28-1.28A747.52 747.52 0 0 0 600.32 256H725.333333V170.666667h-298.666666V85.333333H341.333333v85.333334H42.666667v84.906666h476.586666C490.666667 337.92 445.44 416 384 484.266667 344.32 440.32 311.466667 392.106667 285.44 341.333333h-85.333333c31.146667 69.546667 73.813333 135.253333 127.146666 194.56l-217.173333 214.186667L170.666667 810.666667l213.333333-213.333334 132.693333 132.693334 32.426667-87.04zM789.333333 426.666667h-85.333333L512 938.666667h85.333333l47.786667-128h202.666667L896 938.666667h85.333333l-192-512z m-111.786666 298.666666l69.12-184.746666L815.786667 725.333333h-138.24z"></path>
<path
d="M549.12 642.986667l-108.373333-107.093334 1.28-1.28A747.52 747.52 0 0 0 600.32 256H725.333333V170.666667h-298.666666V85.333333H341.333333v85.333334H42.666667v84.906666h476.586666C490.666667 337.92 445.44 416 384 484.266667 344.32 440.32 311.466667 392.106667 285.44 341.333333h-85.333333c31.146667 69.546667 73.813333 135.253333 127.146666 194.56l-217.173333 214.186667L170.666667 810.666667l213.333333-213.333334 132.693333 132.693334 32.426667-87.04zM789.333333 426.666667h-85.333333L512 938.666667h85.333333l47.786667-128h202.666667L896 938.666667h85.333333l-192-512z m-111.786666 298.666666l69.12-184.746666L815.786667 725.333333h-138.24z"></path>
</svg>
)}
{...props}
Expand All @@ -81,7 +82,9 @@ const TerminalIcon = (props: Partial<CustomIconComponentProps>) => (
<Icon
component={() => (
<svg viewBox="0 0 1024 1024" width="1em" height="1em">
<path d="M93.568 984.234667c-12.416 0-23.296-4.650667-32.64-13.994667-18.645333-18.645333-18.645333-48.213333 0-65.28l388.693333-388.693333-388.693333-388.693334c-18.645333-18.645333-18.645333-48.213333 0-65.28 18.645333-18.645333 48.213333-18.645333 65.28 0l421.333333 421.333334c18.688 18.645333 18.688 48.213333 0 65.28L126.208 970.24a44.757333 44.757333 0 0 1-32.64 13.994667zM934.698667 982.698667h-419.797334c-26.453333 0-46.634667-20.224-46.634666-46.634667 0-26.453333 20.224-46.634667 46.634666-46.634667h419.797334c26.453333 0 46.634667 20.181333 46.634666 46.634667s-20.224 46.634667-46.634666 46.634667z"></path>{' '}
<path
d="M93.568 984.234667c-12.416 0-23.296-4.650667-32.64-13.994667-18.645333-18.645333-18.645333-48.213333 0-65.28l388.693333-388.693333-388.693333-388.693334c-18.645333-18.645333-18.645333-48.213333 0-65.28 18.645333-18.645333 48.213333-18.645333 65.28 0l421.333333 421.333334c18.688 18.645333 18.688 48.213333 0 65.28L126.208 970.24a44.757333 44.757333 0 0 1-32.64 13.994667zM934.698667 982.698667h-419.797334c-26.453333 0-46.634667-20.224-46.634666-46.634667 0-26.453333 20.224-46.634667 46.634666-46.634667h419.797334c26.453333 0 46.634667 20.181333 46.634666 46.634667s-20.224 46.634667-46.634666 46.634667z"></path>
{' '}
</svg>
)}
{...props}
Expand Down Expand Up @@ -193,6 +196,19 @@ const WarmupIcon = (props: Partial<CustomIconComponentProps>) => (
/>
)

const UpgradeIcon = (props: Partial<CustomIconComponentProps>) => (
<Icon
component={() => (
<svg viewBox="0 0 1024 1024" width="16" height="16">
<path
d="M410.282667 126.421333l-264.362667 309.333334A74.666667 74.666667 0 0 0 128 484.48l0.256 6.144c3.242667 38.357333 35.456 68.437333 74.666667 68.309333l104.149333-0.384 0.042667 145.664c0 41.258667 33.450667 74.666667 74.666666 74.666667h234.666667l6.144-0.256a74.666667 74.666667 0 0 0 68.522667-74.410667v-146.773333l106.837333-0.298667a74.666667 74.666667 0 0 0 56.32-123.52l-266.026667-307.669333a117.333333 117.333333 0 0 0-177.92 0.469333z m238.165333 716.458667a32 32 0 1 1 0 64h-298.666667a32 32 0 1 1 0-64h298.666667zM529.536 158.592l4.608 3.541333c2.048 1.706667 3.925333 3.626667 5.717333 5.674667l265.941334 307.669333a10.666667 10.666667 0 0 1-8.021334 17.664l-138.752 0.384a32 32 0 0 0-31.914666 32v178.688a10.666667 10.666667 0 0 1-10.666667 10.666667h-234.666667a10.666667 10.666667 0 0 1-10.666666-10.666667V526.506667a32 32 0 0 0-32.085334-32l-136.32 0.426666a10.666667 10.666667 0 0 1-8.149333-17.578666l264.405333-309.333334a53.333333 53.333333 0 0 1 70.570667-9.429333z"
fill="#000000" p-id="2445"></path>
</svg>
)}
{...props}
/>
)

export {
DSIcon,
PODIcon,
Expand All @@ -208,4 +224,5 @@ export {
YamlIcon,
DebugIcon,
WarmupIcon,
UpgradeIcon,
}
34 changes: 34 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package config

import (
"context"
"encoding/json"
"fmt"
"hash/fnv"
Expand All @@ -33,6 +34,8 @@ import (
"k8s.io/klog/v2"

corev1 "k8s.io/api/core/v1"

k8s "github.com/juicedata/juicefs-csi-driver/pkg/k8sclient"
)

var (
Expand Down Expand Up @@ -421,6 +424,37 @@ func LoadConfig(configPath string) error {
return err
}

func LoadFromConfigMap(ctx context.Context, client *k8s.K8sClient) error {
cmName := os.Getenv("JUICEFS_CONFIG_NAME")
if cmName == "" {
cmName = "juicefs-csi-driver-config"
}
sysNamespace := os.Getenv("SYS_NAMESPACE")
if sysNamespace == "" {
sysNamespace = "kube-system"
}
cm, err := client.GetConfigMap(ctx, cmName, sysNamespace)
if err != nil {
return err
}

cfg := newCfg()

// compatible with old version
if os.Getenv("ENABLE_NODE_SELECTOR") == "1" {
cfg.EnableNodeSelector = true
}

err = cfg.Unmarshal([]byte(cm.Data["config.yaml"]))
if err != nil {
return err
}

GlobalConfig = cfg
log.V(1).Info("config loaded", "global config", *GlobalConfig)
return err
}

// ConfigReloader reloads config file when it is updated
func StartConfigReloader(configPath string) error {
// load first
Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/app_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func TestAppController_umountFuseSidecars(t *testing.T) {
Convey("Test umountFuseSidecars", t, func() {
Convey("exec pod cmd normal", func() {
client := &k8sclient.K8sClient{}
patch1 := ApplyMethod(reflect.TypeOf(client), "ExecuteInContainer", func(_ *k8sclient.K8sClient, podName, namespace, containerName string, cmd []string) (stdout string, stderr string, err error) {
patch1 := ApplyMethod(reflect.TypeOf(client), "ExecuteInContainer", func(_ *k8sclient.K8sClient, c ctx.Context, podName, namespace, containerName string, cmd []string) (stdout string, stderr string, err error) {
return "", "", nil
})
defer patch1.Reset()
Expand Down Expand Up @@ -381,7 +381,7 @@ func TestAppController_umountFuseSidecars(t *testing.T) {
})
Convey("exec pod cmd error", func() {
client := &k8sclient.K8sClient{}
patch1 := ApplyMethod(reflect.TypeOf(client), "ExecuteInContainer", func(_ *k8sclient.K8sClient, podName, namespace, containerName string, cmd []string) (stdout string, stderr string, err error) {
patch1 := ApplyMethod(reflect.TypeOf(client), "ExecuteInContainer", func(_ *k8sclient.K8sClient, c ctx.Context, podName, namespace, containerName string, cmd []string) (stdout string, stderr string, err error) {
return "", "", fmt.Errorf("exec error")
})
defer patch1.Reset()
Expand Down
2 changes: 2 additions & 0 deletions pkg/dashboard/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ func (api *API) Handle(group *gin.RouterGroup) {
group.PUT("/config", api.putCSIConfig())
podGroup := group.Group("/pod/:namespace/:name", api.getPodMiddileware())
podGroup.GET("/", api.getPodHandler())
podGroup.GET("/latestimage", api.getPodLatestImage())
podGroup.PUT("/upgrade", api.smoothUpgrade())
podGroup.GET("/events", api.getPodEvents())
podGroup.GET("/logs/:container", api.getPodLogs())
podGroup.GET("/pvs", api.listPodPVsHandler())
Expand Down
51 changes: 51 additions & 0 deletions pkg/dashboard/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

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

Expand Down Expand Up @@ -372,6 +373,33 @@ func (api *API) getPodHandler() gin.HandlerFunc {
}
}

func (api *API) getPodLatestImage() gin.HandlerFunc {
return func(c *gin.Context) {
po, ok := c.Get("pod")
if !ok {
c.String(404, "not found")
return
}
rawPod := po.(*corev1.Pod)
// gen k8s client
k8sClient, err := k8sclient.NewClientWithConfig(api.kubeconfig)
if err != nil {
c.String(500, "Could not create k8s client: %v", err)
return
}
if err := config.LoadFromConfigMap(c, k8sClient); err != nil {
c.String(500, "Load config from configmap error: %v", err)
return
}
attr, err := config.GenPodAttrWithMountPod(c, k8sClient, rawPod)
if err != nil {
c.String(500, "generate pod attribute error: %v", err)
return
}
c.IndentedJSON(200, attr.Image)
}
}

func (api *API) getPodEvents() gin.HandlerFunc {
return func(c *gin.Context) {
p, ok := c.Get("pod")
Expand Down Expand Up @@ -931,3 +959,26 @@ func (api *API) downloadDebugFile() gin.HandlerFunc {
}
}
}

func (api *API) smoothUpgrade() gin.HandlerFunc {
return func(c *gin.Context) {
po, ok := c.Get("pod")
if !ok {
c.String(404, "not found")
return
}
rawPod := po.(*corev1.Pod)
csiNode, err := api.getCSINode(c, rawPod.Spec.NodeName)
if err != nil {
podLog.Error(err, "get csi node error", "node", rawPod.Spec.NodeName)
c.String(500, "get csi node error %v", err)
return
}

if err := resource.SmoothUpgrade(api.client, api.kubeconfig, csiNode.Name, rawPod.Name, csiNode.Namespace); err != nil {
c.String(500, "Failed to smooth upgrade: %v", err)
return
}
return
}
}
4 changes: 2 additions & 2 deletions pkg/juicefs/mount/builder/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var (
},
},
}, {
Name: JfsFuseFdPathName,
Name: config.JfsFuseFdPathName,
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: path.Join(JfsFuseFsPathInHost, "test"),
Expand Down Expand Up @@ -107,7 +107,7 @@ var (
MountPath: config.PodMountBase,
MountPropagation: &mp,
}, {
Name: JfsFuseFdPathName,
Name: config.JfsFuseFdPathName,
MountPath: "/tmp",
}, {

Expand Down
19 changes: 19 additions & 0 deletions pkg/k8sclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ func NewClient() (*K8sClient, error) {
if config == nil {
return nil, status.Error(codes.NotFound, "Can't get kube InClusterConfig")
}
return newClient(config)
}

func NewClientWithConfig(config *rest.Config) (*K8sClient, error) {
return newClient(config)
}

func newClient(config *rest.Config) (*K8sClient, error) {
config.Timeout = timeout

if os.Getenv("KUBE_QPS") != "" {
Expand Down Expand Up @@ -501,3 +509,14 @@ func execute(method string, url *url.URL, config *restclient.Config, stdin io.Re
Tty: tty,
})
}

func (k *K8sClient) GetConfigMap(ctx context.Context, cmName, namespace string) (*corev1.ConfigMap, error) {
log := util.GenLog(ctx, clientLog, "")
log.V(1).Info("Get configmap", "name", cmName)
cm, err := k.CoreV1().ConfigMaps(namespace).Get(ctx, cmName, metav1.GetOptions{})
if err != nil {
log.V(1).Info("Can't get configMap", "name", cmName, "namespace", namespace, "error", err)
return nil, err
}
return cm, nil
}
32 changes: 32 additions & 0 deletions pkg/util/resource/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package resource

import (
"bytes"
"context"
"encoding/json"
"io"
Expand Down Expand Up @@ -180,3 +181,34 @@ func DownloadPodFile(client kubernetes.Interface, cfg *rest.Config, writer io.Wr

return nil
}

func SmoothUpgrade(client kubernetes.Interface, cfg *rest.Config, csiName, name, namespace string) error {
req := client.CoreV1().RESTClient().Post().
Resource("pods").
Name(csiName).
Namespace(namespace).SubResource("exec")
req.VersionedParams(&corev1.PodExecOptions{
Command: []string{"juicefs-csi-driver", "upgrade", name},
Container: "juicefs-plugin",
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)

var sout, serr bytes.Buffer
executor, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL())
if err != nil {
resourceLog.Error(err, "Failed to create SPDY executor")
return err
}
if err := executor.Stream(remotecommand.StreamOptions{
Stdout: &sout,
Stderr: &serr,
}); err != nil {
resourceLog.Error(err, "Failed to stream", "stdout", sout, "stderr", serr)
return err
}

return nil
}

0 comments on commit c1ef144

Please sign in to comment.