Skip to content

Commit

Permalink
feat(controller): add job template api (#2801)
Browse files Browse the repository at this point in the history
  • Loading branch information
goldenxinxing authored Oct 8, 2023
1 parent bd1f831 commit 3a48759
Show file tree
Hide file tree
Showing 15 changed files with 769 additions and 5 deletions.
35 changes: 35 additions & 0 deletions client/starwhale/base/client/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ class Config:
force: Optional[bool] = None


class CreateJobTemplateRequest(BaseModel):
class Config:
allow_population_by_field_name = True

name: str
job_url: str = Field(..., alias='jobUrl')


class ModelServingRequest(BaseModel):
class Config:
allow_population_by_field_name = True
Expand Down Expand Up @@ -795,6 +803,33 @@ class Config:
retention_time: Optional[int] = Field(None, alias='retentionTime')


class JobTemplateVo(BaseModel):
class Config:
allow_population_by_field_name = True

id: Optional[int] = None
name: Optional[str] = None
job_id: Optional[int] = Field(None, alias='jobId')


class ResponseMessageListJobTemplateVo(BaseModel):
class Config:
allow_population_by_field_name = True

code: str
message: str
data: List[JobTemplateVo]


class ResponseMessageJobTemplateVo(BaseModel):
class Config:
allow_population_by_field_name = True

code: str
message: str
data: JobTemplateVo


class RuntimeVersionVo(BaseModel):
class Config:
allow_population_by_field_name = True
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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;

import ai.starwhale.mlops.api.protocol.Code;
import ai.starwhale.mlops.api.protocol.ResponseMessage;
import ai.starwhale.mlops.api.protocol.job.CreateJobTemplateRequest;
import ai.starwhale.mlops.api.protocol.job.JobTemplateVo;
import ai.starwhale.mlops.domain.job.template.TemplateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@Slf4j
@Validated
@RestController
@Tag(name = "Template")
@RequestMapping("${sw.controller.api-prefix}")
public class TemplateController {

private final TemplateService templateService;

public TemplateController(TemplateService templateService) {
this.templateService = templateService;
}

@Operation(summary = "Add Template for job")
@PostMapping(value = "/project/{projectUrl}/template", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyRole('OWNER', 'MAINTAINER')")
public ResponseEntity<ResponseMessage<String>> addTemplate(
@PathVariable String projectUrl,
@Valid @RequestBody CreateJobTemplateRequest request) {
templateService.add(projectUrl, request.getJobUrl(), request.getName());
return ResponseEntity.ok(Code.success.asResponse("success"));
}

@Operation(summary = "Delete Template")
@DeleteMapping(value = "/project/{projectUrl}/template/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyRole('OWNER', 'MAINTAINER')")
public ResponseEntity<ResponseMessage<String>> deleteTemplate(
@PathVariable String projectUrl,
@PathVariable Long id) {
templateService.delete(id);
return ResponseEntity.ok(Code.success.asResponse("success"));
}

@Operation(summary = "Get Template")
@GetMapping(value = "/project/{projectUrl}/template/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyRole('OWNER', 'MAINTAINER')")
public ResponseEntity<ResponseMessage<JobTemplateVo>> getTemplate(
@PathVariable String projectUrl,
@PathVariable Long id) {
return ResponseEntity.ok(Code.success.asResponse(JobTemplateVo.fromBo(templateService.get(id))));
}

@Operation(summary = "Get Templates for project")
@GetMapping(value = "/project/{projectUrl}/template", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyRole('OWNER', 'MAINTAINER', 'GUEST')")
public ResponseEntity<ResponseMessage<List<JobTemplateVo>>> selectAllInProject(@PathVariable String projectUrl) {
return ResponseEntity.ok(Code.success.asResponse(
templateService.listAll(projectUrl).stream()
.map(JobTemplateVo::fromBo)
.collect(Collectors.toList())
));
}

@Operation(summary = "Get Recently Templates for project")
@GetMapping(value = "/project/{projectUrl}/recent-template", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyRole('OWNER', 'MAINTAINER', 'GUEST')")
public ResponseEntity<ResponseMessage<List<JobTemplateVo>>> selectRecentlyInProject(
@PathVariable String projectUrl,
@RequestParam(required = false, defaultValue = "5")
@Valid
@Min(value = 1, message = "limit must be greater than or equal to 1")
@Max(value = 50, message = "limit must be less than or equal to 50")
Integer limit
) {
return ResponseEntity.ok(Code.success.asResponse(
templateService.listRecently(projectUrl, limit).stream()
.map(JobTemplateVo::fromBo)
.collect(Collectors.toList())
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateJobTemplateRequest {
@NotNull
private String name;
@NotNull
private String jobUrl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.template.bo.Template;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class JobTemplateVo {
private Long id;
private String name;
private Long jobId;

public static JobTemplateVo fromBo(Template jobTemplate) {
if (jobTemplate == null) {
return null;
}
return new JobTemplateVo(jobTemplate.getId(), jobTemplate.getName(), jobTemplate.getJobId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import ai.starwhale.mlops.domain.bundle.base.BundleEntity;
import ai.starwhale.mlops.domain.dataset.DatasetDao;
import ai.starwhale.mlops.domain.job.JobDao;
import ai.starwhale.mlops.domain.job.template.TemplateDao;
import ai.starwhale.mlops.domain.model.ModelDao;
import ai.starwhale.mlops.domain.project.ProjectService;
import ai.starwhale.mlops.domain.project.bo.Project;
Expand Down Expand Up @@ -72,14 +73,15 @@ public class ProjectNameExtractorDataStoreMixed implements ProjectNameExtractor
private final DatasetDao datasetDao;
private final RuntimeDao runtimeDao;
private final ReportDao reportDao;
private final TemplateDao templateDao;

static final String PATH_LIST_TABLES = "/datastore/listTables";
static final String PATH_UPDATE_TABLE = "/datastore/updateTable";
static final String PATH_QUERY_TABLE = "/datastore/queryTable";
static final String PATH_SCAN_TABLE = "/datastore/scanTable";

private static final Pattern RESOURCE_PATTERN =
Pattern.compile("^/project/([^/]+)/(runtime|job|dataset|model|report)/([^/]+).*$");
Pattern.compile("^/project/([^/]+)/(runtime|job|dataset|model|report|template)/([^/]+).*$");

public ProjectNameExtractorDataStoreMixed(
@Value("${sw.controller.api-prefix}") String apiPrefix,
Expand All @@ -90,7 +92,7 @@ public ProjectNameExtractorDataStoreMixed(
ModelDao modelDao,
DatasetDao datasetDao,
RuntimeDao runtimeDao,
ReportDao reportDao) {
ReportDao reportDao, TemplateDao templateDao) {
this.apiPrefix = StringUtils.trimTrailingCharacter(apiPrefix, '/');
this.objectMapper = objectMapper;
this.projectService = projectService;
Expand All @@ -100,6 +102,7 @@ public ProjectNameExtractorDataStoreMixed(
this.datasetDao = datasetDao;
this.runtimeDao = runtimeDao;
this.reportDao = reportDao;
this.templateDao = templateDao;
}

@Override
Expand Down Expand Up @@ -210,7 +213,8 @@ public void checkResourceOwnerShip(HttpServletRequest request) {
"job", jobDao,
"dataset", datasetDao,
"model", modelDao,
"report", reportDao
"report", reportDao,
"template", templateDao
);
if (!accessor.containsKey(resourceType)) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ public interface BundleAccessor {
Type getType();

enum Type {
MODEL, DATASET, RUNTIME, JOB, REPORT
MODEL, DATASET, RUNTIME, JOB, REPORT, TEMPLATE
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.template;

import ai.starwhale.mlops.domain.bundle.BundleAccessor;
import ai.starwhale.mlops.domain.bundle.base.BundleEntity;
import ai.starwhale.mlops.domain.bundle.recover.RecoverAccessor;
import ai.starwhale.mlops.domain.bundle.remove.RemoveAccessor;
import ai.starwhale.mlops.domain.job.template.mapper.TemplateMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class TemplateDao implements BundleAccessor, RecoverAccessor, RemoveAccessor {
private final TemplateMapper mapper;

public TemplateDao(TemplateMapper mapper) {
this.mapper = mapper;
}

@Override
public BundleEntity findById(Long id) {
return mapper.selectById(id);
}

@Override
public BundleEntity findByNameForUpdate(String name, Long projectId) {
return mapper.selectByNameForUpdate(name, projectId);
}

@Override
public Type getType() {
return Type.TEMPLATE;
}

@Override
public BundleEntity findDeletedBundleById(Long id) {
return mapper.selectDeletedById(id);
}

@Override
public Boolean recover(Long id) {
return mapper.recover(id) > 0;
}

@Override
public Boolean remove(Long id) {
return mapper.remove(id) > 0;
}
}
Loading

0 comments on commit 3a48759

Please sign in to comment.