Skip to content

Commit

Permalink
feat: Support upgrading built-in Wasm plugins (#327)
Browse files Browse the repository at this point in the history
  • Loading branch information
CH3CHO authored Sep 2, 2024
1 parent 459474b commit e30551a
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,13 @@ private void reloadServiceInfoFromK8s() {
V1Secret secret = kubernetesClientService.readSecret(secretName);
Map<String, byte[]> data = secret.getData();
if (MapUtils.isEmpty(data)) {
log.error("Secret {} is empty.", secretName);
log.warn("Secret {} is empty.", secretName);
return;
}
byte[] serviceUrlData = data.get(SERVICE_URL_KEY);
byte[] serviceTokenData = data.get(SERVICE_TOKEN_KEY);
if (serviceUrlData == null || serviceUrlData.length == 0 || serviceTokenData == null
|| serviceTokenData.length == 0) {
log.error("Secret {} does not contain service URL or token for ai-proxy.", secretName);
return;
}
serviceInfoHolder.set(new ServiceInfo(new String(serviceUrlData), new String(serviceTokenData)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ public ResponseEntity<Response<WasmPlugin>> update(@PathVariable("name") @NotBla
throw new ValidationException("Plugin name in the URL doesn't match the one in the body.");
}
plugin.validate();
WasmPlugin updatedPlugin = wasmPluginService.updateCustom(plugin);
WasmPlugin updatedPlugin = Boolean.TRUE.equals(plugin.getBuiltIn())
? wasmPluginService.updateBuiltIn(plugin.getName(), plugin.getImageVersion())
: wasmPluginService.updateCustom(plugin);
return ControllerUtil.buildResponseEntity(updatedPlugin);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public interface WasmPluginService {

String queryReadme(String name, String language);

WasmPlugin updateBuiltIn(String name, String imageVersion);

WasmPlugin addCustom(WasmPlugin plugin);

WasmPlugin updateCustom(WasmPlugin plugin);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import javax.annotation.PostConstruct;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Preconditions;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.oas.models.media.Schema;
Expand Down Expand Up @@ -174,7 +175,7 @@ private void fillPluginConfigExample(Plugin plugin, String content) {
|| plugin.getSpec().getConfigSchema().getOpenApiV3Schema() == null) {
return;
}
Schema schema = plugin.getSpec().getConfigSchema().getOpenApiV3Schema();
Schema<?> schema = plugin.getSpec().getConfigSchema().getOpenApiV3Schema();
schema.addExtension(EXAMPLE_RAW_PROPERTY_NAME, example);
}

Expand Down Expand Up @@ -255,22 +256,29 @@ private String loadPluginReadme(String pluginId, String fileName) {
@Override
public PaginatedResult<WasmPlugin> list(WasmPluginPageQuery query) {
String lang = query != null ? query.getLang() : null;
List<Object> plugins = new ArrayList<>(builtInPlugins);
List<WasmPlugin> plugins = new ArrayList<>();
for (PluginCacheItem item : builtInPlugins) {
plugins.add(item.buildWasmPlugin(lang));
}
try {
List<V1alpha1WasmPlugin> customPluginCrs = kubernetesClientService.listWasmPlugin(null, null, false);
plugins.addAll(customPluginCrs);
List<V1alpha1WasmPlugin> crs = kubernetesClientService.listWasmPlugin();
for (V1alpha1WasmPlugin cr : crs) {
WasmPlugin plugin = kubernetesModelConverter.wasmPluginFromCr(cr);
if (plugin.getBuiltIn()) {
WasmPlugin builtInPlugin = plugins.stream()
.filter(p -> p.getName().equals(plugin.getName())).findFirst().orElse(null);
if (builtInPlugin != null){
builtInPlugin.setImageRepository(plugin.getImageRepository());
builtInPlugin.setImageVersion(plugin.getImageVersion());
continue;
}
}
plugins.add(plugin);
}
} catch (ApiException e) {
throw new BusinessException("Error occurs when listing custom Wasm plugins", e);
}
return PaginatedResult.createFromFullList(plugins, query, o -> {
if (o instanceof PluginCacheItem) {
return ((PluginCacheItem)o).buildWasmPlugin(lang);
}
if (o instanceof V1alpha1WasmPlugin) {
return kubernetesModelConverter.wasmPluginFromCr((V1alpha1WasmPlugin)o);
}
throw new IllegalStateException("Unexpected element type: " + o.getClass().getName());
});
return PaginatedResult.createFromFullList(plugins, query);
}

@Override
Expand Down Expand Up @@ -325,7 +333,7 @@ public WasmPluginConfig queryConfig(String name, String language) {
if (CollectionUtils.isNotEmpty(crs)) {
// TODO: Config of a custom plugin is not supported yet. Return an empty schema instead.
WasmPluginConfig config = new WasmPluginConfig();
Schema schema = new Schema();
Schema<?> schema = new Schema<>();
schema.setType("object");
config.setSchema(schema);
return config;
Expand Down Expand Up @@ -366,6 +374,63 @@ public String queryReadme(String name, String language) {
return null;
}

@Override
public WasmPlugin updateBuiltIn(String name, String imageVersion) {
Preconditions.checkArgument(StringUtils.isNotEmpty(name), "name cannot be blank.");
Preconditions.checkArgument(StringUtils.isNotEmpty(imageVersion), "imageVersion cannot be blank.");

PluginCacheItem builtInPlugin =
builtInPlugins.stream().filter(p -> p.getName().equals(name)).findFirst().orElse(null);
if (builtInPlugin == null) {
throw new ResourceConflictException("No built-in plugin is found with the given name: " + name);
}

List<V1alpha1WasmPlugin> existedCrs;
try {
final String pluginVersion = builtInPlugin.getPlugin().getInfo().getVersion();
existedCrs = kubernetesClientService.listWasmPlugin(name, pluginVersion, true);
} catch (ApiException e) {
throw new BusinessException("Error occurs when checking existed Wasm plugins with name " + name, e);
}

V1alpha1WasmPlugin updatedCr;
if (CollectionUtils.isEmpty(existedCrs)) {
WasmPlugin plugin = builtInPlugin.buildWasmPlugin();
plugin.setImageVersion(imageVersion);
V1alpha1WasmPlugin cr = kubernetesModelConverter.wasmPluginToCr(plugin);
// Make sure it is disabled by default.
cr.getSpec().setDefaultConfigDisable(true);
try {
updatedCr = kubernetesClientService.createWasmPlugin(cr);
} catch (ApiException e) {
if (e.getCode() == HttpStatus.CONFLICT) {
throw new ResourceConflictException();
}
throw new BusinessException(
"Error occurs when adding a new Wasm plugin with name: " + cr.getMetadata().getName(), e);
}
} else {
V1alpha1WasmPlugin existedCr = existedCrs.get(0);

WasmPlugin plugin = kubernetesModelConverter.wasmPluginFromCr(existedCr);
plugin.setImageVersion(imageVersion);
updatedCr = kubernetesModelConverter.wasmPluginToCr(plugin);
kubernetesModelConverter.mergeWasmPluginSpec(existedCr, updatedCr);

try {
updatedCr = kubernetesClientService.replaceWasmPlugin(updatedCr);
} catch (ApiException e) {
if (e.getCode() == HttpStatus.CONFLICT) {
throw new ResourceConflictException();
}
throw new BusinessException(
"Error occurs when updating the Wasm plugin wth name " + existedCr.getMetadata().getName(), e);
}
}

return kubernetesModelConverter.wasmPluginFromCr(updatedCr);
}

@Override
public WasmPlugin addCustom(WasmPlugin plugin) {
if (Boolean.TRUE.equals(plugin.getBuiltIn())) {
Expand Down Expand Up @@ -549,6 +614,10 @@ public void setReadme(String language, String content) {
}
}

public WasmPlugin buildWasmPlugin() {
return buildWasmPlugin(null);
}

public WasmPlugin buildWasmPlugin(String language) {
WasmPlugin wasmPlugin = new WasmPlugin();
wasmPlugin.setName(name);
Expand Down Expand Up @@ -596,9 +665,11 @@ public WasmPluginConfig buildWasmPluginConfig(String language) {
|| plugin.getSpec().getConfigSchema().getOpenApiV3Schema() == null) {
return new WasmPluginConfig();
}
Schema schema = null;
Schema<?> schema;
try {
schema = Json.mapper().readValue(Json.mapper().writeValueAsString(plugin.getSpec().getConfigSchema().getOpenApiV3Schema()), Schema.class);
schema = Json.mapper().readValue(
Json.mapper().writeValueAsString(plugin.getSpec().getConfigSchema().getOpenApiV3Schema()),
Schema.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// eslint-disable-next-line max-len
export const DEFAULT_PLUGIN_IMG = `data:image/png;base64,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`;

export const BUILTIN_PLUGIN_LIST = [
export const BUILTIN_ROUTE_PLUGIN_LIST = [
{
key: 'rewrite',
title: '重写',
Expand Down
96 changes: 51 additions & 45 deletions frontend/src/pages/plugin/components/PluginList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Avatar, Button, Card, Col, Dropdown, Popconfirm, Row, Typography } from
import { useSearchParams } from 'ice';
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { BUILTIN_PLUGIN_LIST, DEFAULT_PLUGIN_IMG } from './constant';
import { BUILTIN_ROUTE_PLUGIN_LIST, DEFAULT_PLUGIN_IMG } from './constant';
import { getI18nValue } from '../../utils';

const { Paragraph } = Typography;
Expand All @@ -29,12 +29,6 @@ const PluginList = forwardRef((props: Props, ref) => {
const { data, onOpen, onEdit, onDelete } = props;
const [searchParams] = useSearchParams();

const type = searchParams.get('type');

const showBuiltInPlugins = useMemo(() => {
return type === 'route';
}, [type]);

const handleClickPlugin = (item) => {
onOpen(item);
};
Expand All @@ -46,8 +40,8 @@ const PluginList = forwardRef((props: Props, ref) => {
}, {
manual: true,
onSuccess: (result = []) => {
if (showBuiltInPlugins) {
setPluginList(BUILTIN_PLUGIN_LIST.concat(result) as any);
if (searchParams.get('type') === 'route') {
setPluginList(BUILTIN_ROUTE_PLUGIN_LIST.concat(result) as any);
return;
}
setPluginList(result);
Expand All @@ -68,6 +62,51 @@ const PluginList = forwardRef((props: Props, ref) => {

i18n.on('languageChanged', () => loadWasmPlugins());

const createPluginDropdown = (plugin) => {
if (BUILTIN_ROUTE_PLUGIN_LIST.some(p => p.key === plugin.key)) {
return null;
}
const items = [
{
key: 'edit',
label: (
<span
onClick={() => {
onEdit?.(plugin);
}}
>
{t('misc.edit')}
</span>
),
},
];
if (!plugin.builtIn) {
items.push({
key: 'delete',
label: (
<Popconfirm
title={t('plugins.deleteConfirmation')}
onConfirm={() => {
onDelete?.(plugin.name);
}}
>
<span>{t('misc.delete')}</span>
</Popconfirm>
),
danger: true,
});
}
return (
<Dropdown
menu={{
items,
}}
>
<EllipsisOutlined />
</Dropdown>
)
};

return (
<Row gutter={[16, 16]}>
{pluginList.map((item) => {
Expand Down Expand Up @@ -101,42 +140,9 @@ const PluginList = forwardRef((props: Props, ref) => {
<div style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{getI18nValue(item, 'title')}
</div>
{item.builtIn === false ? (
<Dropdown
menu={{
items: [
{
key: 'edit',
label: (
<span
onClick={() => {
onEdit?.(item);
}}
>
{t('misc.edit')}
</span>
),
},
{
key: 'delete',
label: (
<Popconfirm
title={t('plugins.deleteConfirmation')}
onConfirm={() => {
onDelete?.(item.name);
}}
>
<span>{t('misc.delete')}</span>
</Popconfirm>
),
danger: true,
},
],
}}
>
<EllipsisOutlined />
</Dropdown>
) : undefined}
{
createPluginDropdown(item)
}
</div>
}
description={
Expand Down
Loading

0 comments on commit e30551a

Please sign in to comment.