Skip to content

Commit

Permalink
chore(controller): refine the exposed links (#2504)
Browse files Browse the repository at this point in the history
  • Loading branch information
jialeicui authored Jul 14, 2023
1 parent 076a8b2 commit 11a2637
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 32 deletions.
26 changes: 26 additions & 0 deletions console/src/domain/job/components/ExposedLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { ExposedLinkType, IExposedLinkSchema } from '@job/schemas/job'
import IconFont, { IconTypesT } from '@starwhale/ui/IconFont'

export interface IExposedLinkProps {
data: IExposedLinkSchema
}

const VSCODE_NAME = 'VS_CODE'

function ExposedLink({ data: { type, name, link } }: IExposedLinkProps) {
let font: IconTypesT = 'global'
let title = name
if (type === ExposedLinkType.DEV_MODE && name === VSCODE_NAME) {
font = 'vscode'
title = 'vscode'
}

return (
<a key={link} target='_blank' href={link} rel='noreferrer' title={title}>
<IconFont type={font} size={16} />
</a>
)
}

export default ExposedLink
13 changes: 12 additions & 1 deletion console/src/domain/job/schemas/job.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ export enum JobStatusType {
UNKNOWN = 'UNKNOWN',
}

export enum ExposedLinkType {
DEV_MODE = 'DEV_MODE',
WEB_HANDLER = 'WEB_HANDLER',
}

export interface IExposedLinkSchema {
type: ExposedLinkType
name: string
link: string
}

export interface IJobSchema extends IResourceSchema {
uuid: string
name: string
Expand All @@ -39,7 +50,7 @@ export interface IJobSchema extends IResourceSchema {
stopTime?: number
createdTime?: number
pinnedTime?: number
exposedLinks?: string[]
exposedLinks?: IExposedLinkSchema[]
isTimeToLiveInSec?: boolean
timeToLiveInSec?: number
}
Expand Down
20 changes: 5 additions & 15 deletions console/src/pages/Job/JobListCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import JobStatus from '@/domain/job/components/JobStatus'
import Button from '@starwhale/ui/Button'
import { useAuthPrivileged, WithCurrentAuth } from '@/api/WithAuth'
import { IconTooltip } from '@starwhale/ui/Tooltip'
import IconFont from '@starwhale/ui/IconFont'
import { useProjectRole } from '@project/hooks/useProjectRole'
import { ConfigurationOverride } from '@starwhale/ui/base/helpers/overrides'
import { ConfirmButton } from '@starwhale/ui'
import ExposedLink from '@job/components/ExposedLink'

interface IActionButtonProps {
jobId: string
Expand Down Expand Up @@ -226,21 +226,11 @@ export default function JobListCard() {
typeof job.duration === 'string' ? '-' : durationToStr(job.duration),
job?.stopTime && job?.stopTime > 0 ? formatTimestampDateTime(job?.stopTime) : '-',
<JobStatus key='jobStatus' status={job.jobStatus as any} />,
<div key='action' style={{ display: 'flex', gap: '8px' }}>
<div key='action' style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
{actions[job.jobStatus] ?? ''}
{job.exposedLinks?.map((link) => {
return (
<a
key={link}
target='_blank'
href={link}
rel='noreferrer'
title={t('job.expose.title')}
>
<IconFont type='global' size={16} />
</a>
)
})}
{job.exposedLinks?.map((exposed) => (
<ExposedLink key={exposed.link} data={exposed} />
))}
</div>,
]
}) ?? []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2022 Starwhale, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ai.starwhale.mlops.api.protocol.job;

import ai.starwhale.mlops.domain.job.step.ExposedType;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class ExposedLinkVo {
private ExposedType type;
private String name;
private String link;
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public class JobVo implements Serializable {
// expose links is used to get the serving url of the model, it may contain:
// 1. vscode url when the model is running under dev mode
// 2. serving url when the model is running a web handler (which using the non-zero expose handler decorator)
private List<String> exposedLinks;
private List<ExposedLinkVo> exposedLinks;

@JsonProperty("duration")
public Long getDuration() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,22 @@
package ai.starwhale.mlops.domain.job.converter;


import ai.starwhale.mlops.api.protocol.job.ExposedLinkVo;
import ai.starwhale.mlops.api.protocol.job.JobVo;
import ai.starwhale.mlops.api.protocol.runtime.RuntimeVo;
import ai.starwhale.mlops.api.protocol.user.UserVo;
import ai.starwhale.mlops.common.IdConverter;
import ai.starwhale.mlops.common.proxy.WebServerInTask;
import ai.starwhale.mlops.domain.dataset.DatasetDao;
import ai.starwhale.mlops.domain.dataset.bo.DatasetVersion;
import ai.starwhale.mlops.domain.job.DevWay;
import ai.starwhale.mlops.domain.job.bo.Job;
import ai.starwhale.mlops.domain.job.cache.HotJobHolder;
import ai.starwhale.mlops.domain.job.po.JobEntity;
import ai.starwhale.mlops.domain.job.spec.JobSpecParser;
import ai.starwhale.mlops.domain.job.spec.StepSpec;
import ai.starwhale.mlops.domain.job.status.JobStatus;
import ai.starwhale.mlops.domain.job.step.ExposedType;
import ai.starwhale.mlops.domain.runtime.RuntimeService;
import ai.starwhale.mlops.domain.system.SystemSettingService;
import ai.starwhale.mlops.domain.task.bo.Task;
Expand All @@ -40,12 +43,12 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.function.Consumer4;

@Component
public class JobConverter {
Expand Down Expand Up @@ -99,38 +102,38 @@ private List<String> findDatasetVersionNamesByJobId(Long jobId) {
* @param jobId job id
* @return exposed links
*/
private List<String> generateJobExposedLinks(Long jobId) {
var links = new ArrayList<String>();
private List<ExposedLinkVo> generateJobExposedLinks(Long jobId) {
var exposed = new ArrayList<ExposedLinkVo>();
var jobs = hotJobHolder.ofIds(List.of(jobId));
if (CollectionUtils.isEmpty(jobs)) {
return links;
return exposed;
}
var job = jobs.stream().findAny().get();

// only running job should generate exposed links
var jobStatus = job.getStatus();
if (jobStatus != JobStatus.RUNNING) {
return links;
return exposed;
}
// check if job has exposed port in step spec
var stepSpecStr = job.getStepSpec();
if (!StringUtils.hasText(stepSpecStr)) {
return links;
return exposed;
}

List<StepSpec> stepSpecs;
try {
stepSpecs = jobSpecParser.parseAndFlattenStepFromYaml(stepSpecStr);
} catch (JsonProcessingException e) {
return links;
return exposed;
}
if (CollectionUtils.isEmpty(stepSpecs)) {
return links;
return exposed;
}

var steps = job.getSteps();

BiConsumer<Task, Integer> addRunningTask = (task, port) -> {
Consumer4<Task, Integer, ExposedType, String> addRunningTask = (task, port, type, name) -> {
if (task.getStatus() != TaskStatus.RUNNING) {
return;
}
Expand All @@ -139,13 +142,13 @@ private List<String> generateJobExposedLinks(Long jobId) {
return;
}
var link = webServerInTask.generateGatewayUrl(task.getId(), task.getIp(), port);
links.add(link);
exposed.add(ExposedLinkVo.builder().type(type).name(name).link(link).build());
};

// dev mode
if (job.isDevMode()) {
job.getSteps().stream().flatMap(s -> s.getTasks().stream())
.forEach(task -> addRunningTask.accept(task, devPort));
.forEach(task -> addRunningTask.accept(task, devPort, ExposedType.DEV_MODE, DevWay.VS_CODE.name()));
}

// web handler
Expand All @@ -165,10 +168,17 @@ private List<String> generateJobExposedLinks(Long jobId) {
if (CollectionUtils.isEmpty(tasks)) {
return;
}
tasks.forEach(task -> addRunningTask.accept(task, exposedPort));
String name;
// https://github.com/star-whale/starwhale/blob/c924313166d065ab941a99fa1dec04b7bfbe5fa7/client/starwhale/core/model/model.py#L246-L252
if (Boolean.TRUE.equals(stepSpec.getVirtual()) && stepSpec.getName().equals("serving")) {
name = "online evaluation";
} else {
name = stepSpec.getName();
}
tasks.forEach(t -> addRunningTask.accept(t, exposedPort, ExposedType.WEB_HANDLER, name));
});

return links;
return exposed;
}

public JobVo convert(Job job) throws ConvertException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2022 Starwhale, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ai.starwhale.mlops.domain.job.step;

public enum ExposedType {
DEV_MODE,
WEB_HANDLER,
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import static org.mockito.BDDMockito.mock;
import static org.mockito.Mockito.when;

import ai.starwhale.mlops.api.protocol.job.ExposedLinkVo;
import ai.starwhale.mlops.api.protocol.runtime.RuntimeVo;
import ai.starwhale.mlops.api.protocol.user.UserVo;
import ai.starwhale.mlops.common.IdConverter;
Expand All @@ -39,6 +40,7 @@
import ai.starwhale.mlops.domain.job.spec.JobSpecParser;
import ai.starwhale.mlops.domain.job.spec.StepSpec;
import ai.starwhale.mlops.domain.job.status.JobStatus;
import ai.starwhale.mlops.domain.job.step.ExposedType;
import ai.starwhale.mlops.domain.job.step.bo.Step;
import ai.starwhale.mlops.domain.model.po.ModelVersionEntity;
import ai.starwhale.mlops.domain.runtime.RuntimeService;
Expand Down Expand Up @@ -166,13 +168,15 @@ public void testConvertJobExposedLinks() throws JsonProcessingException {
entity.setJobStatus(JobStatus.RUNNING);
job.setStatus(JobStatus.RUNNING);
res = jobConvertor.convert(entity);
assertThat(res.getExposedLinks(), is(List.of("/foo/bar/")));
var expectWeb = ExposedLinkVo.builder().type(ExposedType.WEB_HANDLER).link("/foo/bar/").name("step").build();
assertThat(res.getExposedLinks(), is(List.of(expectWeb)));

// get debug mode links when dev mode is on
when(webServerInTask.generateGatewayUrl(7L, "1.1.1.1", 1234)).thenReturn("/baz/");
entity.setDevMode(true);
job.setDevMode(true);
res = jobConvertor.convert(entity);
assertThat(res.getExposedLinks(), is(List.of("/baz/", "/foo/bar/")));
var expectDevMode = ExposedLinkVo.builder().type(ExposedType.DEV_MODE).link("/baz/").name("VS_CODE").build();
assertThat(res.getExposedLinks(), is(List.of(expectDevMode, expectWeb)));
}
}

0 comments on commit 11a2637

Please sign in to comment.