blob: 6dcd0a7577d446eced12631b4b6e6c307e527b86 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.submarine.server.submitter.k8s.model.notebook;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
import org.apache.submarine.server.api.notebook.Notebook;
import org.apache.submarine.server.api.spec.NotebookSpec;
import org.apache.submarine.server.submitter.k8s.client.K8sClient;
import org.apache.submarine.server.submitter.k8s.K8sSubmitter;
import org.apache.submarine.server.submitter.k8s.model.K8sResource;
import org.apache.submarine.server.submitter.k8s.parser.NotebookSpecParser;
import org.apache.submarine.server.submitter.k8s.util.NotebookUtils;
import org.apache.submarine.server.submitter.k8s.util.OwnerReferenceUtils;
import org.apache.submarine.server.submitter.k8s.util.YamlUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import static org.apache.submarine.server.submitter.k8s.K8sSubmitter.getDeleteOptions;
public class NotebookCR implements KubernetesObject, K8sResource<Notebook> {
private static final Logger LOG = LoggerFactory.getLogger(NotebookCR.class);
public static final String CRD_NOTEBOOK_VERSION_V1 = "v1";
public static final String CRD_NOTEBOOK_GROUP_V1 = "kubeflow.org";
public static final String CRD_NOTEBOOK_APIVERSION_V1 =
CRD_NOTEBOOK_GROUP_V1 + "/" + CRD_NOTEBOOK_VERSION_V1;
public static final String CRD_NOTEBOOK_KIND_V1 = "Notebook";
public static final String CRD_NOTEBOOK_PLURAL_V1 = "notebooks";
public static final String NOTEBOOK_OWNER_SELECTOR_KEY = "notebook-owner-id";
public static final String NOTEBOOK_ID = "notebook-id";
@SerializedName("apiVersion")
private String apiVersion;
@SerializedName("kind")
private String kind;
@SerializedName("metadata")
private V1ObjectMeta metadata;
private transient String group;
private transient String version;
private transient String plural;
@SerializedName("spec")
private NotebookCRSpec spec;
@SerializedName("status")
private NotebookStatus status;
public String getApiVersion() {
return apiVersion;
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public V1ObjectMeta getMetadata() {
return metadata;
}
public void setMetadata(V1ObjectMeta metadata) {
this.metadata = metadata;
}
public String getGroup() {
return this.group;
}
public void setGroup(String group) {
this.group = group;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
public String getPlural() {
return this.plural;
}
public void setPlural(String plural) {
this.plural = plural;
}
public NotebookCRSpec getSpec() {
return spec;
}
public void setSpec(NotebookCRSpec spec) {
this.spec = spec;
}
public NotebookStatus getStatus() {
return status;
}
public void setStatus(NotebookStatus status) {
this.status = status;
}
private String notebookId;
private NotebookSpec notebookSpec;
public String getNotebookId() {
return notebookId;
}
public NotebookSpec getNotebookSpec() {
return notebookSpec;
}
public NotebookCR() {
setApiVersion(CRD_NOTEBOOK_APIVERSION_V1);
setKind(CRD_NOTEBOOK_KIND_V1);
setPlural(CRD_NOTEBOOK_PLURAL_V1);
setGroup(CRD_NOTEBOOK_GROUP_V1);
setVersion(CRD_NOTEBOOK_VERSION_V1);
}
public NotebookCR(NotebookSpec notebookSpec, String notebookId, String namespace) {
this();
this.notebookId = notebookId;
this.notebookSpec = notebookSpec;
String notebookName = String.format("%s-%s", notebookId.replace("_", "-"),
notebookSpec.getMeta().getName());
try {
// set metadata
V1ObjectMeta meta = new V1ObjectMeta();
meta.setName(notebookName);
meta.setNamespace(namespace);
// we need to use some labels to define/filter the properties of notebook
Map<String, String> labels = notebookSpec.getMeta().getLabels();
if (labels == null) {
labels = new HashMap<>(2);
}
labels.put("notebook-owner-id", notebookSpec.getMeta().getOwnerId());
labels.put("notebook-id", notebookId);
meta.setLabels(labels);
meta.setOwnerReferences(OwnerReferenceUtils.getOwnerReference());
this.setMetadata(meta);
// set spec
this.setSpec(NotebookSpecParser.parseNotebookCRSpec(notebookSpec, notebookName));
} catch (JsonSyntaxException e) {
LOG.error("K8s submitter: parse response object failed by " + e.getMessage(), e);
throw new SubmarineRuntimeException(500, "K8s Submitter parse upstream response failed.");
}
}
/**
* Rest return notebook name.
* The name of created notebook resource will be replaced when it is created,
* so the name needs to be reset when it is returned.
*/
private Notebook resetName(Notebook notebook) {
if (getNotebookSpec() != null) {
notebook.setName(getNotebookSpec().getMeta().getName());
}
return notebook;
}
/**
* Create Notebook CRD
* @param client K8sClient
* @param tolerate Update when create conflicts
* @return Notebook
*/
public Notebook create(K8sClient client, boolean tolerate) {
Notebook notebook = null;
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Create Notebook resource: \n{}", YamlUtils.toPrettyYaml(this));
}
Object object = client.getNotebookCRClient()
.create(this)
.throwsApiException()
.getObject();
notebook = NotebookUtils.parseObject(object, NotebookUtils.ParseOpt.PARSE_OPT_CREATE);
} catch (JsonSyntaxException e) {
LOG.error("K8s submitter: parse response object failed by " + e.getMessage(), e);
throw new SubmarineRuntimeException(500, "K8s Submitter parse upstream response failed.");
} catch (ApiException e) {
if (e.getCode() == 409 && tolerate) {// conflict
// todo need to replace CRD
LOG.warn("K8s submitter: resource already exists, need to replace it.", e);
notebook = NotebookUtils.parseObject(this, NotebookUtils.ParseOpt.PARSE_OPT_REPLACE);
} else {
LOG.error("K8s submitter: parse Notebook object failed by " + e.getMessage(), e);
throw new SubmarineRuntimeException(e.getCode(), "K8s submitter: parse Notebook object failed by " +
e.getMessage());
}
}
return resetName(notebook);
}
@Override
public Notebook create(K8sClient api) {
return create(api, false);
}
@Override
public Notebook replace(K8sClient api) {
throw new UnsupportedOperationException();
}
@Override
public Notebook delete(K8sClient api) {
Notebook notebook = null;
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Delete Notebook resource in namespace: {} and name: {}",
this.getMetadata().getNamespace(), this.getMetadata().getName());
}
Object object = api.getNotebookCRClient()
.delete(
getMetadata().getNamespace(),
this.getMetadata().getName(),
getDeleteOptions(this.getApiVersion())
).throwsApiException().getStatus();
notebook = NotebookUtils.parseObject(object, NotebookUtils.ParseOpt.PARSE_OPT_DELETE);
} catch (ApiException e) {
K8sSubmitter.API_EXCEPTION_404_CONSUMER.apply(e);
} finally {
if (notebook == null) {
// add metadata time info
this.getMetadata().setDeletionTimestamp(new DateTime());
// build notebook response
notebook = NotebookUtils.buildNotebookResponse(this);
notebook.setStatus(Notebook.Status.STATUS_NOT_FOUND.getValue());
notebook.setReason("The notebook instance is not found");
}
}
return resetName(notebook);
}
@Override
public Notebook read(K8sClient api) {
Notebook notebook = null;
try {
Object object = api.getNotebookCRClient()
.get(getMetadata().getNamespace(), getMetadata().getName())
.throwsApiException()
.getObject();
if (LOG.isDebugEnabled()) {
LOG.debug("Get Notebook resource: \n{}", YamlUtils.toPrettyYaml(object));
}
notebook = NotebookUtils.parseObject(object, NotebookUtils.ParseOpt.PARSE_OPT_GET);
} catch (ApiException e) {
// SUBMARINE-1124
// The exception that obtaining CRD resources is not necessarily because the CRD is deleted,
// but maybe due to timeout or API error caused by network and other reasons.
// Therefore, the status of the notebook should be set to a new enum NOTFOUND.
LOG.warn("Get error when submitter is finding notebook: {}", getMetadata().getName());
if (notebook == null) {
notebook = new Notebook();
}
notebook.setReason(e.getMessage());
notebook.setStatus(Notebook.Status.STATUS_NOT_FOUND.getValue());
}
return resetName(notebook);
}
}