YARN-9038. [CSI] Add ability to publish/unpublish volumes on node managers. Contributed by Weiwei Yang.
This commit is contained in:
parent
dfceffa70d
commit
f4906ac019
|
@ -19,6 +19,10 @@ package org.apache.hadoop.yarn.api;
|
||||||
|
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoRequest;
|
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoRequest;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoResponse;
|
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoResponse;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeResponse;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeResponse;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
||||||
import org.apache.hadoop.yarn.exceptions.YarnException;
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
|
@ -30,10 +34,50 @@ import java.io.IOException;
|
||||||
*/
|
*/
|
||||||
public interface CsiAdaptorProtocol {
|
public interface CsiAdaptorProtocol {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get plugin info from the CSI driver. The driver usually returns
|
||||||
|
* the name of the driver and its version.
|
||||||
|
* @param request get plugin info request.
|
||||||
|
* @return response that contains driver name and its version.
|
||||||
|
* @throws YarnException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
GetPluginInfoResponse getPluginInfo(GetPluginInfoRequest request)
|
GetPluginInfoResponse getPluginInfo(GetPluginInfoRequest request)
|
||||||
throws YarnException, IOException;
|
throws YarnException, IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate if the volume capacity can be satisfied on the underneath
|
||||||
|
* storage system. This method responses if the capacity can be satisfied
|
||||||
|
* or not, with a detailed message.
|
||||||
|
* @param request validate volume capability request.
|
||||||
|
* @return validation response.
|
||||||
|
* @throws YarnException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
ValidateVolumeCapabilitiesResponse validateVolumeCapacity(
|
ValidateVolumeCapabilitiesResponse validateVolumeCapacity(
|
||||||
ValidateVolumeCapabilitiesRequest request) throws YarnException,
|
ValidateVolumeCapabilitiesRequest request) throws YarnException,
|
||||||
IOException;
|
IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish the volume on a node manager, the volume will be mounted
|
||||||
|
* to the local file system and become visible for clients.
|
||||||
|
* @param request publish volume request.
|
||||||
|
* @return publish volume response.
|
||||||
|
* @throws YarnException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
NodePublishVolumeResponse nodePublishVolume(
|
||||||
|
NodePublishVolumeRequest request) throws YarnException, IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a reverse operation of
|
||||||
|
* {@link #nodePublishVolume(NodePublishVolumeRequest)}, it un-mounts the
|
||||||
|
* volume from given node.
|
||||||
|
* @param request un-publish volume request.
|
||||||
|
* @return un-publish volume response.
|
||||||
|
* @throws YarnException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
NodeUnpublishVolumeResponse nodeUnpublishVolume(
|
||||||
|
NodeUnpublishVolumeRequest request) throws YarnException, IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.api.protocolrecords;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest.VolumeCapability;
|
||||||
|
import org.apache.hadoop.yarn.util.Records;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request sent by node manager to CSI driver adaptor
|
||||||
|
* to publish a volume on a node.
|
||||||
|
*/
|
||||||
|
public abstract class NodePublishVolumeRequest {
|
||||||
|
|
||||||
|
public static NodePublishVolumeRequest newInstance(String volumeId,
|
||||||
|
boolean readOnly, String targetPath, String stagingPath,
|
||||||
|
VolumeCapability capability,
|
||||||
|
Map<String, String> publishContext,
|
||||||
|
Map<String, String> secrets) {
|
||||||
|
NodePublishVolumeRequest request =
|
||||||
|
Records.newRecord(NodePublishVolumeRequest.class);
|
||||||
|
request.setVolumeId(volumeId);
|
||||||
|
request.setReadonly(readOnly);
|
||||||
|
request.setTargetPath(targetPath);
|
||||||
|
request.setStagingPath(stagingPath);
|
||||||
|
request.setVolumeCapability(capability);
|
||||||
|
request.setPublishContext(publishContext);
|
||||||
|
request.setSecrets(secrets);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void setVolumeId(String volumeId);
|
||||||
|
|
||||||
|
public abstract String getVolumeId();
|
||||||
|
|
||||||
|
public abstract void setReadonly(boolean readonly);
|
||||||
|
|
||||||
|
public abstract boolean getReadOnly();
|
||||||
|
|
||||||
|
public abstract void setTargetPath(String targetPath);
|
||||||
|
|
||||||
|
public abstract String getTargetPath();
|
||||||
|
|
||||||
|
public abstract void setStagingPath(String stagingPath);
|
||||||
|
|
||||||
|
public abstract String getStagingPath();
|
||||||
|
|
||||||
|
public abstract void setVolumeCapability(VolumeCapability capability);
|
||||||
|
|
||||||
|
public abstract VolumeCapability getVolumeCapability();
|
||||||
|
|
||||||
|
public abstract void setPublishContext(Map<String, String> publishContext);
|
||||||
|
|
||||||
|
public abstract Map<String, String> getPublishContext();
|
||||||
|
|
||||||
|
public abstract void setSecrets(Map<String, String> secrets);
|
||||||
|
|
||||||
|
public abstract Map<String, String> getSecrets();
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
JsonObject jsonObject = new JsonObject();
|
||||||
|
jsonObject.addProperty("VolumeId", getVolumeId());
|
||||||
|
jsonObject.addProperty("ReadOnly", getReadOnly());
|
||||||
|
jsonObject.addProperty("TargetPath", getTargetPath());
|
||||||
|
jsonObject.addProperty("StagingPath", getStagingPath());
|
||||||
|
if (getVolumeCapability() != null) {
|
||||||
|
JsonObject jsonCap = new JsonObject();
|
||||||
|
jsonCap.addProperty("AccessMode",
|
||||||
|
getVolumeCapability().getAccessMode().name());
|
||||||
|
jsonCap.addProperty("VolumeType",
|
||||||
|
getVolumeCapability().getVolumeType().name());
|
||||||
|
jsonObject.addProperty("VolumeCapability",
|
||||||
|
jsonCap.toString());
|
||||||
|
}
|
||||||
|
return jsonObject.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.api.protocolrecords;
|
||||||
|
|
||||||
|
import org.apache.hadoop.yarn.util.Records;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response sent by a CSI driver adaptor to the node manager
|
||||||
|
* after publishing a volume on the node.
|
||||||
|
*/
|
||||||
|
public abstract class NodePublishVolumeResponse {
|
||||||
|
|
||||||
|
public static NodePublishVolumeResponse newInstance() {
|
||||||
|
return Records.newRecord(NodePublishVolumeResponse.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.api.protocolrecords;
|
||||||
|
|
||||||
|
import org.apache.hadoop.yarn.util.Records;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request sent by node manager to CSI driver adaptor
|
||||||
|
* to un-publish a volume on a node.
|
||||||
|
*/
|
||||||
|
public abstract class NodeUnpublishVolumeRequest {
|
||||||
|
|
||||||
|
public static NodeUnpublishVolumeRequest newInstance(String volumeId,
|
||||||
|
String targetPath) {
|
||||||
|
NodeUnpublishVolumeRequest request =
|
||||||
|
Records.newRecord(NodeUnpublishVolumeRequest.class);
|
||||||
|
request.setVolumeId(volumeId);
|
||||||
|
request.setTargetPath(targetPath);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void setVolumeId(String volumeId);
|
||||||
|
|
||||||
|
public abstract void setTargetPath(String targetPath);
|
||||||
|
|
||||||
|
public abstract String getVolumeId();
|
||||||
|
|
||||||
|
public abstract String getTargetPath();
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.api.protocolrecords;
|
||||||
|
|
||||||
|
import org.apache.hadoop.yarn.util.Records;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response sent by a CSI driver adaptor to the node manager
|
||||||
|
* after un-publishing a volume on the node.
|
||||||
|
*/
|
||||||
|
public class NodeUnpublishVolumeResponse {
|
||||||
|
|
||||||
|
public static NodeUnpublishVolumeResponse newInstance() {
|
||||||
|
return Records.newRecord(NodeUnpublishVolumeResponse.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -276,10 +276,10 @@ public class ResourceInformation implements Comparable<ResourceInformation> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceInformation newInstance(String name, String units,
|
public static ResourceInformation newInstance(String name, String units,
|
||||||
long value, Map<String, String> attributes) {
|
long value, Set<String> tags, Map<String, String> attributes) {
|
||||||
return ResourceInformation
|
return ResourceInformation
|
||||||
.newInstance(name, units, value, ResourceTypes.COUNTABLE, 0L,
|
.newInstance(name, units, value, ResourceTypes.COUNTABLE, 0L,
|
||||||
Long.MAX_VALUE, null, attributes);
|
Long.MAX_VALUE, tags, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceInformation newInstance(String name, String units,
|
public static ResourceInformation newInstance(String name, String units,
|
||||||
|
|
|
@ -3443,13 +3443,28 @@ public class YarnConfiguration extends Configuration {
|
||||||
// CSI Volume configs
|
// CSI Volume configs
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
/**
|
/**
|
||||||
* One or more socket addresses for csi-adaptor.
|
* TERMS:
|
||||||
* Multiple addresses are delimited by ",".
|
* csi-driver: a 3rd party CSI driver which implements the CSI protocol.
|
||||||
|
* It is provided by the storage system.
|
||||||
|
* csi-driver-adaptor: this is an internal RPC service working
|
||||||
|
* as a bridge between YARN and a csi-driver.
|
||||||
*/
|
*/
|
||||||
public static final String NM_CSI_ADAPTOR_PREFIX =
|
public static final String NM_CSI_ADAPTOR_PREFIX =
|
||||||
NM_PREFIX + "csi-driver-adaptor.";
|
NM_PREFIX + "csi-driver-adaptor.";
|
||||||
|
public static final String NM_CSI_DRIVER_PREFIX =
|
||||||
|
NM_PREFIX + "csi-driver.";
|
||||||
|
public static final String NM_CSI_DRIVER_ENDPOINT_SUFFIX =
|
||||||
|
".endpoint";
|
||||||
|
public static final String NM_CSI_ADAPTOR_ADDRESS_SUFFIX =
|
||||||
|
".address";
|
||||||
|
/**
|
||||||
|
* One or more socket addresses for csi-adaptor.
|
||||||
|
* Multiple addresses are delimited by ",".
|
||||||
|
*/
|
||||||
public static final String NM_CSI_ADAPTOR_ADDRESSES =
|
public static final String NM_CSI_ADAPTOR_ADDRESSES =
|
||||||
NM_CSI_ADAPTOR_PREFIX + "addresses";
|
NM_CSI_ADAPTOR_PREFIX + "addresses";
|
||||||
|
public static final String NM_CSI_DRIVER_NAMES =
|
||||||
|
NM_CSI_DRIVER_PREFIX + "names";
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// Other Configs
|
// Other Configs
|
||||||
|
|
|
@ -15,8 +15,9 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.hadoop.yarn.csi.utils;
|
package org.apache.hadoop.yarn.util.csi;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
import org.apache.hadoop.yarn.exceptions.YarnException;
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
|
@ -24,13 +25,30 @@ import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to load configurations.
|
* Utility class for CSI in the API level.
|
||||||
*/
|
*/
|
||||||
public final class ConfigUtils {
|
public final class CsiConfigUtils {
|
||||||
|
|
||||||
private ConfigUtils() {
|
private CsiConfigUtils() {
|
||||||
// Hide constructor for utility class.
|
// Hide constructor for utility class.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String[] getCsiDriverNames(Configuration conf) {
|
||||||
|
return conf.getStrings(YarnConfiguration.NM_CSI_DRIVER_NAMES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCsiDriverEndpoint(String driverName,
|
||||||
|
Configuration conf) throws YarnException {
|
||||||
|
String driverEndpointProperty = YarnConfiguration.NM_CSI_DRIVER_PREFIX
|
||||||
|
+ driverName + YarnConfiguration.NM_CSI_DRIVER_ENDPOINT_SUFFIX;
|
||||||
|
String driverEndpoint = conf.get(driverEndpointProperty);
|
||||||
|
if (Strings.isNullOrEmpty(driverEndpoint)) {
|
||||||
|
throw new YarnException("CSI driver's endpoint is not specified or"
|
||||||
|
+ " invalid, property "+ driverEndpointProperty + " is not defined");
|
||||||
|
}
|
||||||
|
return driverEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the CSI adaptor address for a CSI driver from configuration.
|
* Resolve the CSI adaptor address for a CSI driver from configuration.
|
||||||
* Expected configuration property name is
|
* Expected configuration property name is
|
||||||
|
@ -43,7 +61,7 @@ public final class ConfigUtils {
|
||||||
public static InetSocketAddress getCsiAdaptorAddressForDriver(
|
public static InetSocketAddress getCsiAdaptorAddressForDriver(
|
||||||
String driverName, Configuration conf) throws YarnException {
|
String driverName, Configuration conf) throws YarnException {
|
||||||
String configName = YarnConfiguration.NM_CSI_ADAPTOR_PREFIX
|
String configName = YarnConfiguration.NM_CSI_ADAPTOR_PREFIX
|
||||||
+ driverName + ".address";
|
+ driverName + YarnConfiguration.NM_CSI_ADAPTOR_ADDRESS_SUFFIX;
|
||||||
String errorMessage = "Failed to load CSI adaptor address for driver "
|
String errorMessage = "Failed to load CSI adaptor address for driver "
|
||||||
+ driverName + ", configuration property " + configName
|
+ driverName + ", configuration property " + configName
|
||||||
+ " is not defined or invalid.";
|
+ " is not defined or invalid.";
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* 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 that includes some CSI utility classes.
|
||||||
|
*/
|
||||||
|
package org.apache.hadoop.yarn.util.csi;
|
|
@ -31,4 +31,10 @@ service CsiAdaptorProtocolService {
|
||||||
|
|
||||||
rpc validateVolumeCapacity (ValidateVolumeCapabilitiesRequest)
|
rpc validateVolumeCapacity (ValidateVolumeCapabilitiesRequest)
|
||||||
returns (ValidateVolumeCapabilitiesResponse);
|
returns (ValidateVolumeCapabilitiesResponse);
|
||||||
|
|
||||||
|
rpc nodePublishVolume (NodePublishVolumeRequest)
|
||||||
|
returns (NodePublishVolumeResponse);
|
||||||
|
|
||||||
|
rpc nodeUnpublishVolume (NodeUnpublishVolumeRequest)
|
||||||
|
returns (NodeUnpublishVolumeResponse);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,4 +66,28 @@ message GetPluginInfoRequest {
|
||||||
message GetPluginInfoResponse {
|
message GetPluginInfoResponse {
|
||||||
required string name = 1;
|
required string name = 1;
|
||||||
required string vendor_version = 2;
|
required string vendor_version = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NodePublishVolumeRequest {
|
||||||
|
required string volume_id = 1;
|
||||||
|
repeated StringStringMapProto publish_context = 2;
|
||||||
|
optional string staging_target_path = 3;
|
||||||
|
required string target_path = 4;
|
||||||
|
required VolumeCapability volume_capability = 5;
|
||||||
|
required bool readonly = 6;
|
||||||
|
repeated StringStringMapProto secrets = 7;
|
||||||
|
repeated StringStringMapProto volume_context = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NodePublishVolumeResponse {
|
||||||
|
// Intentionally empty.
|
||||||
|
}
|
||||||
|
|
||||||
|
message NodeUnpublishVolumeRequest {
|
||||||
|
required string volume_id = 1;
|
||||||
|
required string target_path = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NodeUnpublishVolumeResponse {
|
||||||
|
// Intentionally empty.
|
||||||
}
|
}
|
|
@ -19,12 +19,14 @@ package org.apache.hadoop.yarn.service.api.records;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResourceInformation determines unit/name/value of resource types in addition to memory and vcores. It will be part of Resource object
|
* ResourceInformation determines unit/name/value of resource types in addition to memory and vcores. It will be part of Resource object
|
||||||
|
@ -40,11 +42,25 @@ public class ResourceInformation {
|
||||||
@SerializedName("attributes")
|
@SerializedName("attributes")
|
||||||
private Map<String, String> attributes = null;
|
private Map<String, String> attributes = null;
|
||||||
|
|
||||||
|
@SerializedName("tags")
|
||||||
|
private Set<String> tags = null;
|
||||||
|
|
||||||
public ResourceInformation value(Long value) {
|
public ResourceInformation value(Long value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceInformation tags(Set<String> resourceTags) {
|
||||||
|
this.tags = resourceTags;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "")
|
||||||
|
@JsonProperty("tags")
|
||||||
|
public Set<String> getTags() {
|
||||||
|
return tags == null ? ImmutableSet.of() : tags;
|
||||||
|
}
|
||||||
|
|
||||||
@ApiModelProperty(value = "")
|
@ApiModelProperty(value = "")
|
||||||
@JsonProperty("attributes")
|
@JsonProperty("attributes")
|
||||||
public Map<String, String> getAttributes() {
|
public Map<String, String> getAttributes() {
|
||||||
|
@ -116,6 +132,7 @@ public class ResourceInformation {
|
||||||
sb.append(" unit: ").append(toIndentedString(unit)).append("\n");
|
sb.append(" unit: ").append(toIndentedString(unit)).append("\n");
|
||||||
sb.append(" attributes: ").append(toIndentedString(attributes))
|
sb.append(" attributes: ").append(toIndentedString(attributes))
|
||||||
.append("\n");
|
.append("\n");
|
||||||
|
sb.append(" tags: ").append(toIndentedString(tags)).append("\n");
|
||||||
sb.append("}");
|
sb.append("}");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -755,6 +755,7 @@ public class Component implements EventHandler<ComponentEvent> {
|
||||||
entry.getKey(),
|
entry.getKey(),
|
||||||
specInfo.getUnit(),
|
specInfo.getUnit(),
|
||||||
specInfo.getValue(),
|
specInfo.getValue(),
|
||||||
|
specInfo.getTags(),
|
||||||
specInfo.getAttributes());
|
specInfo.getAttributes());
|
||||||
resource.setResourceInformation(resourceName, ri);
|
resource.setResourceInformation(resourceName, ri);
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,5 +231,6 @@ public class TestAppJsonResolve extends Assert {
|
||||||
Assert.assertEquals("yarn.io/csi-volume", volume.getKey());
|
Assert.assertEquals("yarn.io/csi-volume", volume.getKey());
|
||||||
Assert.assertEquals(100L, volume.getValue().getValue().longValue());
|
Assert.assertEquals(100L, volume.getValue().getValue().longValue());
|
||||||
Assert.assertEquals(2, volume.getValue().getAttributes().size());
|
Assert.assertEquals(2, volume.getValue().getAttributes().size());
|
||||||
|
Assert.assertEquals(1, volume.getValue().getTags().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
"yarn.io/csi-volume": {
|
"yarn.io/csi-volume": {
|
||||||
"value": 100,
|
"value": 100,
|
||||||
"unit": "Gi",
|
"unit": "Gi",
|
||||||
|
"tags": ["sample-tag"],
|
||||||
"attributes" : {
|
"attributes" : {
|
||||||
"driver" : "hostpath",
|
"driver" : "hostpath",
|
||||||
"mountPath" : "/mnt/data"
|
"mountPath" : "/mnt/data"
|
||||||
|
|
|
@ -25,10 +25,18 @@ import org.apache.hadoop.yarn.api.CsiAdaptorPB;
|
||||||
import org.apache.hadoop.yarn.api.CsiAdaptorProtocol;
|
import org.apache.hadoop.yarn.api.CsiAdaptorProtocol;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoRequest;
|
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoRequest;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoResponse;
|
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoResponse;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeResponse;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeResponse;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.GetPluginInfoRequestPBImpl;
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.GetPluginInfoRequestPBImpl;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.GetPluginInfoResponsePBImpl;
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.GetPluginInfoResponsePBImpl;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.NodePublishVolumeRequestPBImpl;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.NodePublishVolumeResponsePBImpl;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.NodeUnpublishVolumeRequestPBImpl;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.NodeUnpublishVolumeResponsePBImpl;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesRequestPBImpl;
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesRequestPBImpl;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesResponsePBImpl;
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesResponsePBImpl;
|
||||||
import org.apache.hadoop.yarn.exceptions.YarnException;
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
|
@ -82,6 +90,34 @@ public class CsiAdaptorProtocolPBClientImpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodePublishVolumeResponse nodePublishVolume(
|
||||||
|
NodePublishVolumeRequest request) throws IOException, YarnException {
|
||||||
|
CsiAdaptorProtos.NodePublishVolumeRequest requestProto =
|
||||||
|
((NodePublishVolumeRequestPBImpl) request).getProto();
|
||||||
|
try {
|
||||||
|
return new NodePublishVolumeResponsePBImpl(
|
||||||
|
proxy.nodePublishVolume(null, requestProto));
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
RPCUtil.unwrapAndThrowException(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeUnpublishVolumeResponse nodeUnpublishVolume(
|
||||||
|
NodeUnpublishVolumeRequest request) throws YarnException, IOException {
|
||||||
|
CsiAdaptorProtos.NodeUnpublishVolumeRequest requestProto =
|
||||||
|
((NodeUnpublishVolumeRequestPBImpl) request).getProto();
|
||||||
|
try {
|
||||||
|
return new NodeUnpublishVolumeResponsePBImpl(
|
||||||
|
proxy.nodeUnpublishVolume(null, requestProto));
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
RPCUtil.unwrapAndThrowException(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
if(this.proxy != null) {
|
if(this.proxy != null) {
|
||||||
|
|
|
@ -23,9 +23,15 @@ import org.apache.hadoop.yarn.api.CsiAdaptorPB;
|
||||||
import org.apache.hadoop.yarn.api.CsiAdaptorProtocol;
|
import org.apache.hadoop.yarn.api.CsiAdaptorProtocol;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoRequest;
|
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoRequest;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoResponse;
|
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoResponse;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeResponse;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeResponse;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.GetPluginInfoRequestPBImpl;
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.GetPluginInfoRequestPBImpl;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.GetPluginInfoResponsePBImpl;
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.GetPluginInfoResponsePBImpl;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.NodePublishVolumeRequestPBImpl;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.NodePublishVolumeResponsePBImpl;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.NodeUnpublishVolumeRequestPBImpl;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.NodeUnpublishVolumeResponsePBImpl;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesRequestPBImpl;
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesRequestPBImpl;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesResponsePBImpl;
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesResponsePBImpl;
|
||||||
import org.apache.hadoop.yarn.exceptions.YarnException;
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
|
@ -72,4 +78,34 @@ public class CsiAdaptorProtocolPBServiceImpl implements CsiAdaptorPB {
|
||||||
throw new ServiceException(e);
|
throw new ServiceException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CsiAdaptorProtos.NodePublishVolumeResponse nodePublishVolume(
|
||||||
|
RpcController controller,
|
||||||
|
CsiAdaptorProtos.NodePublishVolumeRequest request)
|
||||||
|
throws ServiceException {
|
||||||
|
try {
|
||||||
|
NodePublishVolumeRequestPBImpl req =
|
||||||
|
new NodePublishVolumeRequestPBImpl(request);
|
||||||
|
NodePublishVolumeResponse response = real.nodePublishVolume(req);
|
||||||
|
return ((NodePublishVolumeResponsePBImpl) response).getProto();
|
||||||
|
} catch (YarnException | IOException e) {
|
||||||
|
throw new ServiceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CsiAdaptorProtos.NodeUnpublishVolumeResponse nodeUnpublishVolume(
|
||||||
|
RpcController controller,
|
||||||
|
CsiAdaptorProtos.NodeUnpublishVolumeRequest request)
|
||||||
|
throws ServiceException {
|
||||||
|
try {
|
||||||
|
NodeUnpublishVolumeRequestPBImpl req =
|
||||||
|
new NodeUnpublishVolumeRequestPBImpl(request);
|
||||||
|
NodeUnpublishVolumeResponse response = real.nodeUnpublishVolume(req);
|
||||||
|
return ((NodeUnpublishVolumeResponsePBImpl) response).getProto();
|
||||||
|
} catch (YarnException | IOException e) {
|
||||||
|
throw new ServiceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.api.protocolrecords.impl.pb;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.protobuf.TextFormat;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest.VolumeCapability;
|
||||||
|
import org.apache.hadoop.yarn.api.records.impl.pb.ProtoUtils;
|
||||||
|
import org.apache.hadoop.yarn.proto.CsiAdaptorProtos;
|
||||||
|
import org.apache.hadoop.yarn.proto.YarnProtos;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request to publish volume on node manager.
|
||||||
|
*/
|
||||||
|
public class NodePublishVolumeRequestPBImpl extends
|
||||||
|
NodePublishVolumeRequest {
|
||||||
|
|
||||||
|
private CsiAdaptorProtos.NodePublishVolumeRequest.Builder builder;
|
||||||
|
|
||||||
|
public NodePublishVolumeRequestPBImpl() {
|
||||||
|
this.builder = CsiAdaptorProtos.NodePublishVolumeRequest.newBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodePublishVolumeRequestPBImpl(
|
||||||
|
CsiAdaptorProtos.NodePublishVolumeRequest request) {
|
||||||
|
this.builder = request.toBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CsiAdaptorProtos.NodePublishVolumeRequest getProto() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVolumeId(String volumeId) {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
builder.setVolumeId(volumeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVolumeId() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.getVolumeId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadonly(boolean readonly) {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
builder.setReadonly(readonly);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getReadOnly() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.getReadonly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSecrets(Map<String, String> secrets) {
|
||||||
|
if (secrets != null) {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
for(Map.Entry<String, String> entry : secrets.entrySet()) {
|
||||||
|
YarnProtos.StringStringMapProto mapEntry =
|
||||||
|
YarnProtos.StringStringMapProto.newBuilder()
|
||||||
|
.setKey(entry.getKey())
|
||||||
|
.setValue(entry.getValue())
|
||||||
|
.build();
|
||||||
|
builder.addSecrets(mapEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getSecrets() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.getSecretsCount() > 0 ?
|
||||||
|
ProtoUtils.convertStringStringMapProtoListToMap(
|
||||||
|
builder.getSecretsList()) : ImmutableMap.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTargetPath() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.getTargetPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStagingPath(String stagingPath) {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
builder.setStagingTargetPath(stagingPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStagingPath() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.getStagingTargetPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPublishContext(Map<String, String> publishContext) {
|
||||||
|
if (publishContext != null) {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
for(Map.Entry<String, String> entry : publishContext.entrySet()) {
|
||||||
|
YarnProtos.StringStringMapProto mapEntry =
|
||||||
|
YarnProtos.StringStringMapProto.newBuilder()
|
||||||
|
.setKey(entry.getKey())
|
||||||
|
.setValue(entry.getValue())
|
||||||
|
.build();
|
||||||
|
builder.addPublishContext(mapEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getPublishContext() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.getPublishContextCount() > 0 ?
|
||||||
|
ProtoUtils.convertStringStringMapProtoListToMap(
|
||||||
|
builder.getPublishContextList()) : ImmutableMap.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTargetPath(String targetPath) {
|
||||||
|
if (targetPath != null) {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
builder.setTargetPath(targetPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVolumeCapability(
|
||||||
|
VolumeCapability capability) {
|
||||||
|
if (capability != null) {
|
||||||
|
CsiAdaptorProtos.VolumeCapability vc =
|
||||||
|
CsiAdaptorProtos.VolumeCapability.newBuilder()
|
||||||
|
.setAccessMode(CsiAdaptorProtos.VolumeCapability
|
||||||
|
.AccessMode.valueOf(
|
||||||
|
capability.getAccessMode().ordinal()))
|
||||||
|
.setVolumeType(CsiAdaptorProtos.VolumeCapability
|
||||||
|
.VolumeType.valueOf(capability.getVolumeType().ordinal()))
|
||||||
|
.addAllMountFlags(capability.getMountFlags())
|
||||||
|
.build();
|
||||||
|
builder.setVolumeCapability(vc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VolumeCapability getVolumeCapability() {
|
||||||
|
CsiAdaptorProtos.VolumeCapability cap0 = builder.getVolumeCapability();
|
||||||
|
if (builder.hasVolumeCapability()) {
|
||||||
|
return new VolumeCapability(
|
||||||
|
ValidateVolumeCapabilitiesRequest.AccessMode
|
||||||
|
.valueOf(cap0.getAccessMode().name()),
|
||||||
|
ValidateVolumeCapabilitiesRequest.VolumeType
|
||||||
|
.valueOf(cap0.getVolumeType().name()),
|
||||||
|
cap0.getMountFlagsList());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return TextFormat.shortDebugString(getProto());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getProto().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (other.getClass().isAssignableFrom(this.getClass())) {
|
||||||
|
return this.getProto().equals(this.getClass().cast(other).getProto());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.api.protocolrecords.impl.pb;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeResponse;
|
||||||
|
import org.apache.hadoop.yarn.proto.CsiAdaptorProtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protobuf record class for node publish response.
|
||||||
|
*/
|
||||||
|
public class NodePublishVolumeResponsePBImpl
|
||||||
|
extends NodePublishVolumeResponse {
|
||||||
|
|
||||||
|
private CsiAdaptorProtos.NodePublishVolumeResponse.Builder builder;
|
||||||
|
|
||||||
|
public NodePublishVolumeResponsePBImpl(
|
||||||
|
CsiAdaptorProtos.NodePublishVolumeResponse proto) {
|
||||||
|
this.builder = proto.toBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodePublishVolumeResponsePBImpl() {
|
||||||
|
this.builder = CsiAdaptorProtos.NodePublishVolumeResponse
|
||||||
|
.newBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CsiAdaptorProtos.NodePublishVolumeResponse getProto() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getProto().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (other.getClass().isAssignableFrom(this.getClass())) {
|
||||||
|
return this.getProto().equals(this.getClass().cast(other).getProto());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.api.protocolrecords.impl.pb;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.protobuf.TextFormat;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.proto.CsiAdaptorProtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The protobuf record class for request to un-publish volume on node manager.
|
||||||
|
*/
|
||||||
|
public class NodeUnpublishVolumeRequestPBImpl extends
|
||||||
|
NodeUnpublishVolumeRequest {
|
||||||
|
|
||||||
|
private CsiAdaptorProtos.NodeUnpublishVolumeRequest.Builder builder;
|
||||||
|
|
||||||
|
public NodeUnpublishVolumeRequestPBImpl() {
|
||||||
|
this.builder = CsiAdaptorProtos.NodeUnpublishVolumeRequest.newBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeUnpublishVolumeRequestPBImpl(
|
||||||
|
CsiAdaptorProtos.NodeUnpublishVolumeRequest request) {
|
||||||
|
this.builder = request.toBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CsiAdaptorProtos.NodeUnpublishVolumeRequest getProto() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVolumeId(String volumeId) {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
this.builder.setVolumeId(volumeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTargetPath(String targetPath) {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
this.builder.setTargetPath(targetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVolumeId() {
|
||||||
|
return builder.getVolumeId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTargetPath() {
|
||||||
|
return builder.getTargetPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return TextFormat.shortDebugString(getProto());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getProto().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (other.getClass().isAssignableFrom(this.getClass())) {
|
||||||
|
return this.getProto().equals(this.getClass().cast(other).getProto());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.api.protocolrecords.impl.pb;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeResponse;
|
||||||
|
import org.apache.hadoop.yarn.proto.CsiAdaptorProtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response to the un-publish volume request on node manager.
|
||||||
|
*/
|
||||||
|
public class NodeUnpublishVolumeResponsePBImpl extends
|
||||||
|
NodeUnpublishVolumeResponse {
|
||||||
|
|
||||||
|
private CsiAdaptorProtos.NodeUnpublishVolumeResponse.Builder builder;
|
||||||
|
|
||||||
|
public NodeUnpublishVolumeResponsePBImpl() {
|
||||||
|
this.builder = CsiAdaptorProtos.NodeUnpublishVolumeResponse.newBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeUnpublishVolumeResponsePBImpl(
|
||||||
|
CsiAdaptorProtos.NodeUnpublishVolumeResponse response) {
|
||||||
|
this.builder = response.toBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CsiAdaptorProtos.NodeUnpublishVolumeResponse getProto() {
|
||||||
|
Preconditions.checkNotNull(builder);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getProto().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (other.getClass().isAssignableFrom(this.getClass())) {
|
||||||
|
return this.getProto().equals(this.getClass().cast(other).getProto());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4083,4 +4083,19 @@
|
||||||
<name>yarn.nodemanager.csi-driver-adaptor.addresses</name>
|
<name>yarn.nodemanager.csi-driver-adaptor.addresses</name>
|
||||||
<value></value>
|
<value></value>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<description>
|
||||||
|
CSI driver names running on this node, multiple driver names need to
|
||||||
|
be delimited by comma. The driver name should be same value returned
|
||||||
|
by the getPluginInfo call. For each of the CSI driver name, it must
|
||||||
|
to define following two corresponding properties:
|
||||||
|
"yarn.nodemanager.csi-driver.${NAME}.endpoint"
|
||||||
|
"yarn.nodemanager.csi-driver-adaptor.${NAME}.address"
|
||||||
|
The 1st property defines where the driver's endpoint is;
|
||||||
|
2nd property defines where the mapping csi-driver-adaptor's address is.
|
||||||
|
</description>
|
||||||
|
<name>yarn.nodemanager.csi-driver.names</name>
|
||||||
|
<value></value>
|
||||||
|
</property>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -180,6 +180,24 @@
|
||||||
<excludePackageNames>csi.v0</excludePackageNames>
|
<excludePackageNames>csi.v0</excludePackageNames>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>build-helper-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>add-source</id>
|
||||||
|
<phase>generate-sources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>add-source</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sources>
|
||||||
|
<source>${basedir}/target/generated-sources/protobuf/java</source>
|
||||||
|
</sources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
|
@ -21,29 +21,36 @@ import com.google.common.annotations.VisibleForTesting;
|
||||||
import csi.v0.Csi;
|
import csi.v0.Csi;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.ipc.Server;
|
import org.apache.hadoop.ipc.Server;
|
||||||
import org.apache.hadoop.service.AbstractService;
|
|
||||||
import org.apache.hadoop.yarn.api.CsiAdaptorProtocol;
|
import org.apache.hadoop.yarn.api.CsiAdaptorProtocol;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoRequest;
|
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoRequest;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoResponse;
|
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoResponse;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeResponse;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeResponse;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
||||||
import org.apache.hadoop.yarn.csi.client.CsiClient;
|
import org.apache.hadoop.yarn.csi.client.CsiClient;
|
||||||
import org.apache.hadoop.yarn.csi.client.CsiClientImpl;
|
import org.apache.hadoop.yarn.csi.client.CsiClientImpl;
|
||||||
import org.apache.hadoop.yarn.csi.translator.ProtoTranslatorFactory;
|
import org.apache.hadoop.yarn.csi.translator.ProtoTranslatorFactory;
|
||||||
import org.apache.hadoop.yarn.csi.utils.ConfigUtils;
|
|
||||||
import org.apache.hadoop.yarn.exceptions.YarnException;
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
import org.apache.hadoop.yarn.ipc.YarnRPC;
|
import org.apache.hadoop.yarn.ipc.YarnRPC;
|
||||||
|
import org.apache.hadoop.yarn.server.api.ApplicationInitializationContext;
|
||||||
|
import org.apache.hadoop.yarn.server.api.ApplicationTerminationContext;
|
||||||
|
import org.apache.hadoop.yarn.server.api.AuxiliaryService;
|
||||||
|
import org.apache.hadoop.yarn.util.csi.CsiConfigUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a Hadoop RPC server, we uses the Hadoop RPC framework here
|
* This is a Hadoop RPC server, we uses the Hadoop RPC framework here
|
||||||
* because we need to stick to the security model current Hadoop supports.
|
* because we need to stick to the security model current Hadoop supports.
|
||||||
*/
|
*/
|
||||||
public class CsiAdaptorProtocolService extends AbstractService
|
public class CsiAdaptorProtocolService extends AuxiliaryService
|
||||||
implements CsiAdaptorProtocol {
|
implements CsiAdaptorProtocol {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
|
@ -54,6 +61,12 @@ public class CsiAdaptorProtocolService extends AbstractService
|
||||||
private CsiClient csiClient;
|
private CsiClient csiClient;
|
||||||
private String csiDriverName;
|
private String csiDriverName;
|
||||||
|
|
||||||
|
public CsiAdaptorProtocolService() {
|
||||||
|
super(CsiAdaptorProtocolService.class.getName());
|
||||||
|
// TODO read this from configuration
|
||||||
|
this.csiDriverName = "ch.ctrox.csi.s3-driver";
|
||||||
|
}
|
||||||
|
|
||||||
public CsiAdaptorProtocolService(String driverName,
|
public CsiAdaptorProtocolService(String driverName,
|
||||||
String domainSocketPath) {
|
String domainSocketPath) {
|
||||||
super(CsiAdaptorProtocolService.class.getName());
|
super(CsiAdaptorProtocolService.class.getName());
|
||||||
|
@ -68,7 +81,11 @@ public class CsiAdaptorProtocolService extends AbstractService
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void serviceInit(Configuration conf) throws Exception {
|
protected void serviceInit(Configuration conf) throws Exception {
|
||||||
adaptorServiceAddress = ConfigUtils
|
|
||||||
|
String driverEndpoint = CsiConfigUtils
|
||||||
|
.getCsiDriverEndpoint(csiDriverName, conf);
|
||||||
|
this.csiClient = new CsiClientImpl(driverEndpoint);
|
||||||
|
adaptorServiceAddress = CsiConfigUtils
|
||||||
.getCsiAdaptorAddressForDriver(csiDriverName, conf);
|
.getCsiAdaptorAddressForDriver(csiDriverName, conf);
|
||||||
super.serviceInit(conf);
|
super.serviceInit(conf);
|
||||||
}
|
}
|
||||||
|
@ -119,4 +136,55 @@ public class CsiAdaptorProtocolService extends AbstractService
|
||||||
Csi.ValidateVolumeCapabilitiesResponse.class)
|
Csi.ValidateVolumeCapabilitiesResponse.class)
|
||||||
.convertFrom(response);
|
.convertFrom(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodePublishVolumeResponse nodePublishVolume(
|
||||||
|
NodePublishVolumeRequest request) throws YarnException, IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Received nodePublishVolume call, request: {}",
|
||||||
|
request.toString());
|
||||||
|
}
|
||||||
|
Csi.NodePublishVolumeRequest req = ProtoTranslatorFactory
|
||||||
|
.getTranslator(NodePublishVolumeRequest.class,
|
||||||
|
Csi.NodePublishVolumeRequest.class).convertTo(request);
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Translate to CSI proto message: {}", req.toString());
|
||||||
|
}
|
||||||
|
csiClient.nodePublishVolume(req);
|
||||||
|
return NodePublishVolumeResponse.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeUnpublishVolumeResponse nodeUnpublishVolume(
|
||||||
|
NodeUnpublishVolumeRequest request) throws YarnException, IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Received nodeUnpublishVolume call, request: {}",
|
||||||
|
request.toString());
|
||||||
|
}
|
||||||
|
Csi.NodeUnpublishVolumeRequest req = ProtoTranslatorFactory
|
||||||
|
.getTranslator(NodeUnpublishVolumeRequest.class,
|
||||||
|
Csi.NodeUnpublishVolumeRequest.class).convertTo(request);
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Translate to CSI proto message: {}", req.toString());
|
||||||
|
}
|
||||||
|
csiClient.nodeUnpublishVolume(req);
|
||||||
|
return NodeUnpublishVolumeResponse.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initializeApplication(
|
||||||
|
ApplicationInitializationContext initAppContext) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopApplication(
|
||||||
|
ApplicationTerminationContext stopAppContext) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer getMetaData() {
|
||||||
|
return ByteBuffer.allocate(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,4 +40,10 @@ public interface CsiClient {
|
||||||
|
|
||||||
Csi.ValidateVolumeCapabilitiesResponse validateVolumeCapabilities(
|
Csi.ValidateVolumeCapabilitiesResponse validateVolumeCapabilities(
|
||||||
Csi.ValidateVolumeCapabilitiesRequest request) throws IOException;
|
Csi.ValidateVolumeCapabilitiesRequest request) throws IOException;
|
||||||
|
|
||||||
|
Csi.NodePublishVolumeResponse nodePublishVolume(
|
||||||
|
Csi.NodePublishVolumeRequest request) throws IOException;
|
||||||
|
|
||||||
|
Csi.NodeUnpublishVolumeResponse nodeUnpublishVolume(
|
||||||
|
Csi.NodeUnpublishVolumeRequest request) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,4 +59,24 @@ public class CsiClientImpl implements CsiClient {
|
||||||
.validateVolumeCapabilities(request);
|
.validateVolumeCapabilities(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Csi.NodePublishVolumeResponse nodePublishVolume(
|
||||||
|
Csi.NodePublishVolumeRequest request) throws IOException {
|
||||||
|
try (CsiGrpcClient client = CsiGrpcClient.newBuilder()
|
||||||
|
.setDomainSocketAddress(address).build()) {
|
||||||
|
return client.createNodeBlockingStub()
|
||||||
|
.nodePublishVolume(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Csi.NodeUnpublishVolumeResponse nodeUnpublishVolume(
|
||||||
|
Csi.NodeUnpublishVolumeRequest request) throws IOException {
|
||||||
|
try (CsiGrpcClient client = CsiGrpcClient.newBuilder()
|
||||||
|
.setDomainSocketAddress(address).build()) {
|
||||||
|
return client.createNodeBlockingStub()
|
||||||
|
.nodeUnpublishVolume(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
* Protobuf message translator for GetPluginInfoResponse and
|
* Protobuf message translator for GetPluginInfoResponse and
|
||||||
* Csi.GetPluginInfoResponse.
|
* Csi.GetPluginInfoResponse.
|
||||||
*/
|
*/
|
||||||
public class GetPluginInfoResponseProtoTranslator implements
|
public class GetPluginInfoResponseProtoTranslator<A, B> implements
|
||||||
ProtoTranslator<GetPluginInfoResponse, Csi.GetPluginInfoResponse> {
|
ProtoTranslator<GetPluginInfoResponse, Csi.GetPluginInfoResponse> {
|
||||||
|
|
||||||
@Override public Csi.GetPluginInfoResponse convertTo(
|
@Override public Csi.GetPluginInfoResponse convertTo(
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.csi.translator;
|
||||||
|
|
||||||
|
import csi.v0.Csi;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
||||||
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class helps to transform a YARN side NodePublishVolumeRequest
|
||||||
|
* to corresponding CSI protocol message.
|
||||||
|
* @param <A> YARN NodePublishVolumeRequest
|
||||||
|
* @param <B> CSI NodePublishVolumeRequest
|
||||||
|
*/
|
||||||
|
public class NodePublishVolumeRequestProtoTranslator<A, B> implements
|
||||||
|
ProtoTranslator<NodePublishVolumeRequest,
|
||||||
|
Csi.NodePublishVolumeRequest> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Csi.NodePublishVolumeRequest convertTo(
|
||||||
|
NodePublishVolumeRequest messageA) throws YarnException {
|
||||||
|
Csi.NodePublishVolumeRequest.Builder builder =
|
||||||
|
Csi.NodePublishVolumeRequest.newBuilder();
|
||||||
|
ValidateVolumeCapabilitiesRequest.VolumeCapability cap =
|
||||||
|
messageA.getVolumeCapability();
|
||||||
|
Csi.VolumeCapability csiVolumeCap = Csi.VolumeCapability.newBuilder()
|
||||||
|
.setAccessMode(Csi.VolumeCapability.AccessMode.newBuilder()
|
||||||
|
.setModeValue(cap.getAccessMode().ordinal())) // access mode
|
||||||
|
// TODO support block
|
||||||
|
.setMount(Csi.VolumeCapability.MountVolume.newBuilder()
|
||||||
|
// TODO support fsType
|
||||||
|
.setFsType("xfs") // fs type
|
||||||
|
.addAllMountFlags(cap.getMountFlags())) // mount flags
|
||||||
|
.build();
|
||||||
|
builder.setVolumeCapability(csiVolumeCap);
|
||||||
|
builder.setVolumeId(messageA.getVolumeId());
|
||||||
|
builder.setTargetPath(messageA.getTargetPath());
|
||||||
|
builder.setReadonly(messageA.getReadOnly());
|
||||||
|
builder.putAllNodePublishSecrets(messageA.getSecrets());
|
||||||
|
builder.putAllPublishInfo(messageA.getPublishContext());
|
||||||
|
builder.setStagingTargetPath(messageA.getStagingPath());
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodePublishVolumeRequest convertFrom(
|
||||||
|
Csi.NodePublishVolumeRequest messageB) throws YarnException {
|
||||||
|
Csi.VolumeCapability cap0 = messageB.getVolumeCapability();
|
||||||
|
ValidateVolumeCapabilitiesRequest.VolumeCapability cap =
|
||||||
|
new ValidateVolumeCapabilitiesRequest.VolumeCapability(
|
||||||
|
ValidateVolumeCapabilitiesRequest.AccessMode
|
||||||
|
.valueOf(cap0.getAccessMode().getMode().name()),
|
||||||
|
ValidateVolumeCapabilitiesRequest.VolumeType.FILE_SYSTEM,
|
||||||
|
cap0.getMount().getMountFlagsList());
|
||||||
|
return NodePublishVolumeRequest.newInstance(
|
||||||
|
messageB.getVolumeId(), messageB.getReadonly(),
|
||||||
|
messageB.getTargetPath(), messageB.getStagingTargetPath(),
|
||||||
|
cap, messageB.getPublishInfoMap(),
|
||||||
|
messageB.getNodePublishSecretsMap());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.csi.translator;
|
||||||
|
|
||||||
|
import csi.v0.Csi;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class helps to transform a YARN side NodeUnpublishVolumeRequest
|
||||||
|
* to corresponding CSI protocol message.
|
||||||
|
* @param <A> YARN NodeUnpublishVolumeRequest
|
||||||
|
* @param <B> CSI NodeUnpublishVolumeRequest
|
||||||
|
*/
|
||||||
|
public class NodeUnpublishVolumeRequestProtoTranslator<A, B> implements
|
||||||
|
ProtoTranslator<NodeUnpublishVolumeRequest,
|
||||||
|
Csi.NodeUnpublishVolumeRequest> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Csi.NodeUnpublishVolumeRequest convertTo(
|
||||||
|
NodeUnpublishVolumeRequest messageA) throws YarnException {
|
||||||
|
return Csi.NodeUnpublishVolumeRequest.newBuilder()
|
||||||
|
.setVolumeId(messageA.getVolumeId())
|
||||||
|
.setTargetPath(messageA.getTargetPath())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeUnpublishVolumeRequest convertFrom(
|
||||||
|
Csi.NodeUnpublishVolumeRequest messageB) throws YarnException {
|
||||||
|
return NodeUnpublishVolumeRequest
|
||||||
|
.newInstance(messageB.getVolumeId(), messageB.getTargetPath());
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,11 @@
|
||||||
package org.apache.hadoop.yarn.csi.translator;
|
package org.apache.hadoop.yarn.csi.translator;
|
||||||
|
|
||||||
import csi.v0.Csi;
|
import csi.v0.Csi;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeRequest;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResponse;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.GetPluginInfoResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory class to get desired proto transformer instance.
|
* Factory class to get desired proto transformer instance.
|
||||||
|
@ -57,6 +60,15 @@ public final class ProtoTranslatorFactory {
|
||||||
} else if (yarnProto == ValidateVolumeCapabilitiesResponse.class
|
} else if (yarnProto == ValidateVolumeCapabilitiesResponse.class
|
||||||
&& csiProto == Csi.ValidateVolumeCapabilitiesResponse.class) {
|
&& csiProto == Csi.ValidateVolumeCapabilitiesResponse.class) {
|
||||||
return new ValidationVolumeCapabilitiesResponseProtoTranslator();
|
return new ValidationVolumeCapabilitiesResponseProtoTranslator();
|
||||||
|
} else if (yarnProto == NodePublishVolumeRequest.class
|
||||||
|
&& csiProto == Csi.NodePublishVolumeRequest.class) {
|
||||||
|
return new NodePublishVolumeRequestProtoTranslator();
|
||||||
|
} else if (yarnProto == GetPluginInfoResponse.class
|
||||||
|
&& csiProto == Csi.GetPluginInfoResponse.class) {
|
||||||
|
return new GetPluginInfoResponseProtoTranslator();
|
||||||
|
} else if (yarnProto == NodeUnpublishVolumeRequest.class
|
||||||
|
&& csiProto == Csi.NodeUnpublishVolumeRequest.class) {
|
||||||
|
return new NodeUnpublishVolumeRequestProtoTranslator();
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("A problem is found while processing"
|
throw new IllegalArgumentException("A problem is found while processing"
|
||||||
+ " proto message translating. Unexpected message types,"
|
+ " proto message translating. Unexpected message types,"
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesResp
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesRequestPBImpl;
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.ValidateVolumeCapabilitiesRequestPBImpl;
|
||||||
import org.apache.hadoop.yarn.client.NMProxy;
|
import org.apache.hadoop.yarn.client.NMProxy;
|
||||||
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
import org.apache.hadoop.yarn.csi.client.CsiClient;
|
import org.apache.hadoop.yarn.csi.client.ICsiClientTest;
|
||||||
import org.apache.hadoop.yarn.exceptions.YarnException;
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
import org.apache.hadoop.yarn.ipc.YarnRPC;
|
import org.apache.hadoop.yarn.ipc.YarnRPC;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
@ -81,89 +81,18 @@ public class TestCsiAdaptorService {
|
||||||
conf.setSocketAddr(
|
conf.setSocketAddr(
|
||||||
YarnConfiguration.NM_CSI_ADAPTOR_PREFIX + "test-driver.address",
|
YarnConfiguration.NM_CSI_ADAPTOR_PREFIX + "test-driver.address",
|
||||||
address);
|
address);
|
||||||
|
conf.set(
|
||||||
|
YarnConfiguration.NM_CSI_DRIVER_PREFIX + "test-driver.endpoint",
|
||||||
|
"unix:///tmp/test-driver.scok");
|
||||||
CsiAdaptorProtocolService service =
|
CsiAdaptorProtocolService service =
|
||||||
new CsiAdaptorProtocolService("test-driver", domainSocket);
|
new CsiAdaptorProtocolService("test-driver", domainSocket);
|
||||||
|
|
||||||
// inject a fake CSI client
|
|
||||||
// this client validates if the ValidateVolumeCapabilitiesRequest
|
|
||||||
// is integrity, and then reply a fake response
|
|
||||||
service.setCsiClient(new CsiClient() {
|
|
||||||
@Override
|
|
||||||
public Csi.GetPluginInfoResponse getPluginInfo() {
|
|
||||||
return Csi.GetPluginInfoResponse.newBuilder()
|
|
||||||
.setName("test-plugin")
|
|
||||||
.setVendorVersion("0.1")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Csi.ValidateVolumeCapabilitiesResponse validateVolumeCapabilities(
|
|
||||||
Csi.ValidateVolumeCapabilitiesRequest request) {
|
|
||||||
// validate we get all info from the request
|
|
||||||
Assert.assertEquals("volume-id-0000123", request.getVolumeId());
|
|
||||||
Assert.assertEquals(1, request.getVolumeCapabilitiesCount());
|
|
||||||
Assert.assertEquals(Csi.VolumeCapability.AccessMode
|
|
||||||
.newBuilder().setModeValue(5).build(),
|
|
||||||
request.getVolumeCapabilities(0).getAccessMode());
|
|
||||||
Assert.assertTrue(request.getVolumeCapabilities(0).hasMount());
|
|
||||||
Assert.assertEquals(2, request.getVolumeCapabilities(0)
|
|
||||||
.getMount().getMountFlagsCount());
|
|
||||||
Assert.assertTrue(request.getVolumeCapabilities(0)
|
|
||||||
.getMount().getMountFlagsList().contains("mountFlag1"));
|
|
||||||
Assert.assertTrue(request.getVolumeCapabilities(0)
|
|
||||||
.getMount().getMountFlagsList().contains("mountFlag2"));
|
|
||||||
Assert.assertEquals(2, request.getVolumeAttributesCount());
|
|
||||||
Assert.assertEquals("v1", request.getVolumeAttributesMap().get("k1"));
|
|
||||||
Assert.assertEquals("v2", request.getVolumeAttributesMap().get("k2"));
|
|
||||||
// return a fake result
|
|
||||||
return Csi.ValidateVolumeCapabilitiesResponse.newBuilder()
|
|
||||||
.setSupported(false)
|
|
||||||
.setMessage("this is a test")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
service.init(conf);
|
service.init(conf);
|
||||||
service.start();
|
service.start();
|
||||||
|
|
||||||
try (CsiAdaptorProtocolPBClientImpl client =
|
|
||||||
new CsiAdaptorProtocolPBClientImpl(1L, address, new Configuration())) {
|
|
||||||
ValidateVolumeCapabilitiesRequest request =
|
|
||||||
ValidateVolumeCapabilitiesRequestPBImpl
|
|
||||||
.newInstance("volume-id-0000123",
|
|
||||||
ImmutableList.of(
|
|
||||||
new ValidateVolumeCapabilitiesRequest
|
|
||||||
.VolumeCapability(
|
|
||||||
MULTI_NODE_MULTI_WRITER, FILE_SYSTEM,
|
|
||||||
ImmutableList.of("mountFlag1", "mountFlag2"))),
|
|
||||||
ImmutableMap.of("k1", "v1", "k2", "v2"));
|
|
||||||
|
|
||||||
ValidateVolumeCapabilitiesResponse response = client
|
|
||||||
.validateVolumeCapacity(request);
|
|
||||||
|
|
||||||
Assert.assertEquals(false, response.isSupported());
|
|
||||||
Assert.assertEquals("this is a test", response.getResponseMessage());
|
|
||||||
} finally {
|
|
||||||
service.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidateVolumeWithNMProxy() throws Exception {
|
|
||||||
ServerSocket ss = new ServerSocket(0);
|
|
||||||
ss.close();
|
|
||||||
InetSocketAddress address = new InetSocketAddress(ss.getLocalPort());
|
|
||||||
Configuration conf = new Configuration();
|
|
||||||
conf.setSocketAddr(
|
|
||||||
YarnConfiguration.NM_CSI_ADAPTOR_PREFIX + "test-driver.address",
|
|
||||||
address);
|
|
||||||
CsiAdaptorProtocolService service =
|
|
||||||
new CsiAdaptorProtocolService("test-driver", domainSocket);
|
|
||||||
|
|
||||||
// inject a fake CSI client
|
// inject a fake CSI client
|
||||||
// this client validates if the ValidateVolumeCapabilitiesRequest
|
// this client validates if the ValidateVolumeCapabilitiesRequest
|
||||||
// is integrity, and then reply a fake response
|
// is integrity, and then reply a fake response
|
||||||
service.setCsiClient(new CsiClient() {
|
service.setCsiClient(new ICsiClientTest() {
|
||||||
@Override
|
@Override
|
||||||
public Csi.GetPluginInfoResponse getPluginInfo() {
|
public Csi.GetPluginInfoResponse getPluginInfo() {
|
||||||
return Csi.GetPluginInfoResponse.newBuilder()
|
return Csi.GetPluginInfoResponse.newBuilder()
|
||||||
|
@ -199,9 +128,84 @@ public class TestCsiAdaptorService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try (CsiAdaptorProtocolPBClientImpl client =
|
||||||
|
new CsiAdaptorProtocolPBClientImpl(1L, address, new Configuration())) {
|
||||||
|
ValidateVolumeCapabilitiesRequest request =
|
||||||
|
ValidateVolumeCapabilitiesRequestPBImpl
|
||||||
|
.newInstance("volume-id-0000123",
|
||||||
|
ImmutableList.of(
|
||||||
|
new ValidateVolumeCapabilitiesRequest
|
||||||
|
.VolumeCapability(
|
||||||
|
MULTI_NODE_MULTI_WRITER, FILE_SYSTEM,
|
||||||
|
ImmutableList.of("mountFlag1", "mountFlag2"))),
|
||||||
|
ImmutableMap.of("k1", "v1", "k2", "v2"));
|
||||||
|
|
||||||
|
ValidateVolumeCapabilitiesResponse response = client
|
||||||
|
.validateVolumeCapacity(request);
|
||||||
|
|
||||||
|
Assert.assertEquals(false, response.isSupported());
|
||||||
|
Assert.assertEquals("this is a test", response.getResponseMessage());
|
||||||
|
} finally {
|
||||||
|
service.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateVolumeWithNMProxy() throws Exception {
|
||||||
|
ServerSocket ss = new ServerSocket(0);
|
||||||
|
ss.close();
|
||||||
|
InetSocketAddress address = new InetSocketAddress(ss.getLocalPort());
|
||||||
|
Configuration conf = new Configuration();
|
||||||
|
conf.setSocketAddr(
|
||||||
|
YarnConfiguration.NM_CSI_ADAPTOR_PREFIX + "test-driver.address",
|
||||||
|
address);
|
||||||
|
conf.set(
|
||||||
|
YarnConfiguration.NM_CSI_DRIVER_PREFIX + "test-driver.endpoint",
|
||||||
|
"unix:///tmp/test-driver.scok");
|
||||||
|
CsiAdaptorProtocolService service =
|
||||||
|
new CsiAdaptorProtocolService("test-driver", domainSocket);
|
||||||
service.init(conf);
|
service.init(conf);
|
||||||
service.start();
|
service.start();
|
||||||
|
|
||||||
|
// inject a fake CSI client
|
||||||
|
// this client validates if the ValidateVolumeCapabilitiesRequest
|
||||||
|
// is integrity, and then reply a fake response
|
||||||
|
service.setCsiClient(new ICsiClientTest() {
|
||||||
|
@Override
|
||||||
|
public Csi.GetPluginInfoResponse getPluginInfo() {
|
||||||
|
return Csi.GetPluginInfoResponse.newBuilder()
|
||||||
|
.setName("test-plugin")
|
||||||
|
.setVendorVersion("0.1")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Csi.ValidateVolumeCapabilitiesResponse validateVolumeCapabilities(
|
||||||
|
Csi.ValidateVolumeCapabilitiesRequest request) {
|
||||||
|
// validate we get all info from the request
|
||||||
|
Assert.assertEquals("volume-id-0000123", request.getVolumeId());
|
||||||
|
Assert.assertEquals(1, request.getVolumeCapabilitiesCount());
|
||||||
|
Assert.assertEquals(Csi.VolumeCapability.AccessMode
|
||||||
|
.newBuilder().setModeValue(5).build(),
|
||||||
|
request.getVolumeCapabilities(0).getAccessMode());
|
||||||
|
Assert.assertTrue(request.getVolumeCapabilities(0).hasMount());
|
||||||
|
Assert.assertEquals(2, request.getVolumeCapabilities(0)
|
||||||
|
.getMount().getMountFlagsCount());
|
||||||
|
Assert.assertTrue(request.getVolumeCapabilities(0)
|
||||||
|
.getMount().getMountFlagsList().contains("mountFlag1"));
|
||||||
|
Assert.assertTrue(request.getVolumeCapabilities(0)
|
||||||
|
.getMount().getMountFlagsList().contains("mountFlag2"));
|
||||||
|
Assert.assertEquals(2, request.getVolumeAttributesCount());
|
||||||
|
Assert.assertEquals("v1", request.getVolumeAttributesMap().get("k1"));
|
||||||
|
Assert.assertEquals("v2", request.getVolumeAttributesMap().get("k2"));
|
||||||
|
// return a fake result
|
||||||
|
return Csi.ValidateVolumeCapabilitiesResponse.newBuilder()
|
||||||
|
.setSupported(false)
|
||||||
|
.setMessage("this is a test")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
YarnRPC rpc = YarnRPC.create(conf);
|
YarnRPC rpc = YarnRPC.create(conf);
|
||||||
UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
|
UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
|
||||||
CsiAdaptorProtocol adaptorClient = NMProxy
|
CsiAdaptorProtocol adaptorClient = NMProxy
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.csi.adaptor;
|
||||||
|
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.impl.pb.NodePublishVolumeRequestPBImpl;
|
||||||
|
import org.apache.hadoop.yarn.proto.CsiAdaptorProtos;
|
||||||
|
import org.apache.hadoop.yarn.proto.CsiAdaptorProtos.VolumeCapability.AccessMode;
|
||||||
|
import org.apache.hadoop.yarn.proto.CsiAdaptorProtos.VolumeCapability.VolumeType;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UT for NodePublishVolumeRequest.
|
||||||
|
*/
|
||||||
|
public class TestNodePublishVolumeRequest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPBRecord() {
|
||||||
|
CsiAdaptorProtos.VolumeCapability capability =
|
||||||
|
CsiAdaptorProtos.VolumeCapability.newBuilder()
|
||||||
|
.setAccessMode(AccessMode.MULTI_NODE_READER_ONLY)
|
||||||
|
.setVolumeType(VolumeType.FILE_SYSTEM)
|
||||||
|
.build();
|
||||||
|
CsiAdaptorProtos.NodePublishVolumeRequest proto =
|
||||||
|
CsiAdaptorProtos.NodePublishVolumeRequest.newBuilder()
|
||||||
|
.setReadonly(false)
|
||||||
|
.setVolumeId("test-vol-000001")
|
||||||
|
.setTargetPath("/mnt/data")
|
||||||
|
.setStagingTargetPath("/mnt/staging")
|
||||||
|
.setVolumeCapability(capability)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
NodePublishVolumeRequestPBImpl pbImpl =
|
||||||
|
new NodePublishVolumeRequestPBImpl(proto);
|
||||||
|
Assert.assertEquals("test-vol-000001", pbImpl.getVolumeId());
|
||||||
|
Assert.assertEquals("/mnt/data", pbImpl.getTargetPath());
|
||||||
|
Assert.assertEquals("/mnt/staging", pbImpl.getStagingPath());
|
||||||
|
Assert.assertFalse(pbImpl.getReadOnly());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.csi.client;
|
||||||
|
|
||||||
|
import csi.v0.Csi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface is used only in testing. It gives default implementation
|
||||||
|
* of all methods.
|
||||||
|
*/
|
||||||
|
public interface ICsiClientTest extends CsiClient {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Csi.GetPluginInfoResponse getPluginInfo()
|
||||||
|
throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Csi.ValidateVolumeCapabilitiesResponse validateVolumeCapabilities(
|
||||||
|
Csi.ValidateVolumeCapabilitiesRequest request) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Csi.NodePublishVolumeResponse nodePublishVolume(
|
||||||
|
Csi.NodePublishVolumeRequest request) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Csi.NodeUnpublishVolumeResponse nodeUnpublishVolume(
|
||||||
|
Csi.NodeUnpublishVolumeRequest request) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,6 +75,10 @@ public interface Container extends EventHandler<ContainerEvent> {
|
||||||
|
|
||||||
void setWorkDir(String workDir);
|
void setWorkDir(String workDir);
|
||||||
|
|
||||||
|
String getCsiVolumesRootDir();
|
||||||
|
|
||||||
|
void setCsiVolumesRootDir(String volumesRootDir);
|
||||||
|
|
||||||
String getLogDir();
|
String getLogDir();
|
||||||
|
|
||||||
void setLogDir(String logDir);
|
void setLogDir(String logDir);
|
||||||
|
|
|
@ -172,6 +172,7 @@ public class ContainerImpl implements Container {
|
||||||
private SlidingWindowRetryPolicy.RetryContext windowRetryContext;
|
private SlidingWindowRetryPolicy.RetryContext windowRetryContext;
|
||||||
private SlidingWindowRetryPolicy retryPolicy;
|
private SlidingWindowRetryPolicy retryPolicy;
|
||||||
|
|
||||||
|
private String csiVolumesRootDir;
|
||||||
private String workDir;
|
private String workDir;
|
||||||
private String logDir;
|
private String logDir;
|
||||||
private String host;
|
private String host;
|
||||||
|
@ -936,6 +937,16 @@ public class ContainerImpl implements Container {
|
||||||
this.workDir = workDir;
|
this.workDir = workDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCsiVolumesRootDir() {
|
||||||
|
return csiVolumesRootDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCsiVolumesRootDir(String volumesRootDir) {
|
||||||
|
this.csiVolumesRootDir = volumesRootDir;
|
||||||
|
}
|
||||||
|
|
||||||
private void clearIpAndHost() {
|
private void clearIpAndHost() {
|
||||||
LOG.info("{} clearing ip and host", containerId);
|
LOG.info("{} clearing ip and host", containerId);
|
||||||
this.ips = null;
|
this.ips = null;
|
||||||
|
|
|
@ -250,6 +250,10 @@ public class ContainerLaunch implements Callable<Integer> {
|
||||||
Path containerWorkDir = deriveContainerWorkDir();
|
Path containerWorkDir = deriveContainerWorkDir();
|
||||||
recordContainerWorkDir(containerID, containerWorkDir.toString());
|
recordContainerWorkDir(containerID, containerWorkDir.toString());
|
||||||
|
|
||||||
|
// Select a root dir for all csi volumes for the container
|
||||||
|
Path csiVolumesRoot = deriveCsiVolumesRootDir();
|
||||||
|
recordContainerCsiVolumesRootDir(containerID, csiVolumesRoot.toString());
|
||||||
|
|
||||||
String pidFileSubpath = getPidFileSubpath(appIdStr, containerIdStr);
|
String pidFileSubpath = getPidFileSubpath(appIdStr, containerIdStr);
|
||||||
// pid file should be in nm private dir so that it is not
|
// pid file should be in nm private dir so that it is not
|
||||||
// accessible by users
|
// accessible by users
|
||||||
|
@ -358,6 +362,7 @@ public class ContainerLaunch implements Callable<Integer> {
|
||||||
.setUser(user)
|
.setUser(user)
|
||||||
.setAppId(appIdStr)
|
.setAppId(appIdStr)
|
||||||
.setContainerWorkDir(containerWorkDir)
|
.setContainerWorkDir(containerWorkDir)
|
||||||
|
.setContainerCsiVolumesRootDir(csiVolumesRoot)
|
||||||
.setLocalDirs(localDirs)
|
.setLocalDirs(localDirs)
|
||||||
.setLogDirs(logDirs)
|
.setLogDirs(logDirs)
|
||||||
.setFilecacheDirs(filecacheDirs)
|
.setFilecacheDirs(filecacheDirs)
|
||||||
|
@ -388,6 +393,27 @@ public class ContainerLaunch implements Callable<Integer> {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Volumes mount point root:
|
||||||
|
* ${YARN_LOCAL_DIR}/usercache/${user}/filecache/csiVolumes/app/container
|
||||||
|
* CSI volumes may creates the mount point with different permission bits.
|
||||||
|
* If we create the volume mount under container work dir, it may
|
||||||
|
* mess up the existing permission structure, which is restricted by
|
||||||
|
* linux container executor. So we put all volume mounts under a same
|
||||||
|
* root dir so it is easier cleanup.
|
||||||
|
**/
|
||||||
|
private Path deriveCsiVolumesRootDir() throws IOException {
|
||||||
|
final String containerVolumePath =
|
||||||
|
ContainerLocalizer.USERCACHE + Path.SEPARATOR
|
||||||
|
+ container.getUser() + Path.SEPARATOR
|
||||||
|
+ ContainerLocalizer.FILECACHE + Path.SEPARATOR
|
||||||
|
+ ContainerLocalizer.CSI_VOLIUME_MOUNTS_ROOT + Path.SEPARATOR
|
||||||
|
+ app.getAppId().toString() + Path.SEPARATOR
|
||||||
|
+ container.getContainerId().toString();
|
||||||
|
return dirsHandler.getLocalPathForWrite(containerVolumePath,
|
||||||
|
LocalDirAllocator.SIZE_UNKNOWN, false);
|
||||||
|
}
|
||||||
|
|
||||||
private Path deriveContainerWorkDir() throws IOException {
|
private Path deriveContainerWorkDir() throws IOException {
|
||||||
|
|
||||||
final String containerWorkDirPath =
|
final String containerWorkDirPath =
|
||||||
|
@ -1752,6 +1778,12 @@ public class ContainerLaunch implements Callable<Integer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void recordContainerCsiVolumesRootDir(ContainerId containerId,
|
||||||
|
String volumesRoot) throws IOException {
|
||||||
|
container.setCsiVolumesRootDir(volumesRoot);
|
||||||
|
// TODO persistent to the NM store...
|
||||||
|
}
|
||||||
|
|
||||||
protected Path getContainerWorkDir() throws IOException {
|
protected Path getContainerWorkDir() throws IOException {
|
||||||
String containerWorkDir = container.getWorkDir();
|
String containerWorkDir = container.getWorkDir();
|
||||||
if (containerWorkDir == null
|
if (containerWorkDir == null
|
||||||
|
|
|
@ -24,7 +24,10 @@ import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.apache.hadoop.hdfs.protocol.datatransfer.IOStreamPair;
|
import org.apache.hadoop.hdfs.protocol.datatransfer.IOStreamPair;
|
||||||
import org.apache.hadoop.security.Credentials;
|
import org.apache.hadoop.security.Credentials;
|
||||||
import org.apache.hadoop.yarn.api.ApplicationConstants.Environment;
|
import org.apache.hadoop.yarn.api.ApplicationConstants.Environment;
|
||||||
|
import org.apache.hadoop.yarn.api.CsiAdaptorProtocol;
|
||||||
|
import org.apache.hadoop.yarn.api.impl.pb.client.CsiAdaptorProtocolPBClientImpl;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.Context;
|
import org.apache.hadoop.yarn.server.nodemanager.Context;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerCommand;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerCommand;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerCommandExecutor;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerCommandExecutor;
|
||||||
|
@ -35,7 +38,9 @@ import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerVolumeCommand;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerVolumeCommand;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.DockerCommandPlugin;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.DockerCommandPlugin;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.ResourcePlugin;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.ResourcePlugin;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.volume.csi.ContainerVolumePublisher;
|
||||||
import org.apache.hadoop.yarn.util.DockerClientConfigHandler;
|
import org.apache.hadoop.yarn.util.DockerClientConfigHandler;
|
||||||
|
import org.apache.hadoop.yarn.util.csi.CsiConfigUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
|
@ -69,6 +74,7 @@ import org.apache.hadoop.yarn.server.nodemanager.executor.ContainerExecContext;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -76,6 +82,7 @@ import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -262,6 +269,7 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
|
||||||
private Configuration conf;
|
private Configuration conf;
|
||||||
private Context nmContext;
|
private Context nmContext;
|
||||||
private DockerClient dockerClient;
|
private DockerClient dockerClient;
|
||||||
|
private Map<String, CsiAdaptorProtocol> csiClients = new HashMap<>();
|
||||||
private PrivilegedOperationExecutor privilegedOperationExecutor;
|
private PrivilegedOperationExecutor privilegedOperationExecutor;
|
||||||
private String defaultImageName;
|
private String defaultImageName;
|
||||||
private Set<String> allowedNetworks = new HashSet<>();
|
private Set<String> allowedNetworks = new HashSet<>();
|
||||||
|
@ -363,6 +371,9 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
|
||||||
throw new ContainerExecutionException(message);
|
throw new ContainerExecutionException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize csi adaptors if necessary
|
||||||
|
initiateCsiClients(conf);
|
||||||
|
|
||||||
privilegedContainersAcl = new AccessControlList(conf.getTrimmed(
|
privilegedContainersAcl = new AccessControlList(conf.getTrimmed(
|
||||||
YarnConfiguration.NM_DOCKER_PRIVILEGED_CONTAINERS_ACL,
|
YarnConfiguration.NM_DOCKER_PRIVILEGED_CONTAINERS_ACL,
|
||||||
YarnConfiguration.DEFAULT_NM_DOCKER_PRIVILEGED_CONTAINERS_ACL));
|
YarnConfiguration.DEFAULT_NM_DOCKER_PRIVILEGED_CONTAINERS_ACL));
|
||||||
|
@ -398,6 +409,10 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
|
||||||
YarnConfiguration.NM_DOCKER_DEFAULT_TMPFS_MOUNTS)));
|
YarnConfiguration.NM_DOCKER_DEFAULT_TMPFS_MOUNTS)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, CsiAdaptorProtocol> getCsiClients() {
|
||||||
|
return csiClients;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRuntimeRequested(Map<String, String> env) {
|
public boolean isRuntimeRequested(Map<String, String> env) {
|
||||||
return isDockerContainerRequested(conf, env);
|
return isDockerContainerRequested(conf, env);
|
||||||
|
@ -942,6 +957,18 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContainerVolumePublisher publisher = new ContainerVolumePublisher(
|
||||||
|
container, container.getCsiVolumesRootDir(), this);
|
||||||
|
try {
|
||||||
|
Map<String, String> volumeMounts = publisher.publishVolumes();
|
||||||
|
volumeMounts.forEach((local, remote) ->
|
||||||
|
runCommand.addReadWriteMountLocation(local, remote));
|
||||||
|
} catch (YarnException | IOException e) {
|
||||||
|
throw new ContainerExecutionException(
|
||||||
|
"Container requests for volume resource but we are failed"
|
||||||
|
+ " to publish volumes on this node");
|
||||||
|
}
|
||||||
|
|
||||||
if (environment.containsKey(ENV_DOCKER_CONTAINER_TMPFS_MOUNTS)) {
|
if (environment.containsKey(ENV_DOCKER_CONTAINER_TMPFS_MOUNTS)) {
|
||||||
String[] tmpfsMounts = environment.get(ENV_DOCKER_CONTAINER_TMPFS_MOUNTS)
|
String[] tmpfsMounts = environment.get(ENV_DOCKER_CONTAINER_TMPFS_MOUNTS)
|
||||||
.split(",");
|
.split(",");
|
||||||
|
@ -1442,6 +1469,14 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
|
||||||
ContainerExecutor.Signal signal) throws ContainerExecutionException {
|
ContainerExecutor.Signal signal) throws ContainerExecutionException {
|
||||||
Container container = ctx.getContainer();
|
Container container = ctx.getContainer();
|
||||||
|
|
||||||
|
ContainerVolumePublisher publisher = new ContainerVolumePublisher(
|
||||||
|
container, container.getCsiVolumesRootDir(), this);
|
||||||
|
try {
|
||||||
|
publisher.unpublishVolumes();
|
||||||
|
} catch (YarnException | IOException e) {
|
||||||
|
throw new ContainerExecutionException(e);
|
||||||
|
}
|
||||||
|
|
||||||
// Only need to check whether the container was asked to be privileged.
|
// Only need to check whether the container was asked to be privileged.
|
||||||
// If the container had failed the permissions checks upon launch, it
|
// If the container had failed the permissions checks upon launch, it
|
||||||
// would have never been launched and thus we wouldn't be here
|
// would have never been launched and thus we wouldn't be here
|
||||||
|
@ -1537,4 +1572,33 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate CSI clients to talk to the CSI adaptors on this node and
|
||||||
|
* cache the clients for easier fetch.
|
||||||
|
* @param config configuration
|
||||||
|
* @throws ContainerExecutionException
|
||||||
|
*/
|
||||||
|
private void initiateCsiClients(Configuration config)
|
||||||
|
throws ContainerExecutionException {
|
||||||
|
String[] driverNames = CsiConfigUtils.getCsiDriverNames(config);
|
||||||
|
if (driverNames != null && driverNames.length > 0) {
|
||||||
|
for (String driverName : driverNames) {
|
||||||
|
try {
|
||||||
|
// find out the adaptors service address
|
||||||
|
InetSocketAddress adaptorServiceAddress =
|
||||||
|
CsiConfigUtils.getCsiAdaptorAddressForDriver(driverName, config);
|
||||||
|
LOG.info("Initializing a csi-adaptor-client for csi-adaptor {},"
|
||||||
|
+ " csi-driver {}", adaptorServiceAddress.toString(), driverName);
|
||||||
|
CsiAdaptorProtocolPBClientImpl client =
|
||||||
|
new CsiAdaptorProtocolPBClientImpl(1L, adaptorServiceAddress,
|
||||||
|
config);
|
||||||
|
csiClients.put(driverName, client);
|
||||||
|
} catch (IOException e1) {
|
||||||
|
throw new ContainerExecutionException(e1.getMessage());
|
||||||
|
} catch (YarnException e2) {
|
||||||
|
throw new ContainerExecutionException(e2.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,7 @@ public class ContainerLocalizer {
|
||||||
new FsPermission((short)0710);
|
new FsPermission((short)0710);
|
||||||
private static final FsPermission USERCACHE_FOLDER_PERMS =
|
private static final FsPermission USERCACHE_FOLDER_PERMS =
|
||||||
new FsPermission((short) 0755);
|
new FsPermission((short) 0755);
|
||||||
|
public static final String CSI_VOLIUME_MOUNTS_ROOT = "csivolumes";
|
||||||
|
|
||||||
private final String user;
|
private final String user;
|
||||||
private final String appId;
|
private final String appId;
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.yarn.server.nodemanager.containermanager.volume.csi;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import org.apache.hadoop.yarn.api.CsiAdaptorProtocol;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodePublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.NodeUnpublishVolumeRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.protocolrecords.ValidateVolumeCapabilitiesRequest;
|
||||||
|
import org.apache.hadoop.yarn.api.records.Resource;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ResourceInformation;
|
||||||
|
import org.apache.hadoop.yarn.exceptions.YarnException;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.DockerLinuxContainerRuntime;
|
||||||
|
import org.apache.hadoop.yarn.server.volume.csi.VolumeMetaData;
|
||||||
|
import org.apache.hadoop.yarn.server.volume.csi.exception.InvalidVolumeException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish/un-publish CSI volumes on node manager.
|
||||||
|
*/
|
||||||
|
public class ContainerVolumePublisher {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
LoggerFactory.getLogger(ContainerVolumePublisher.class);
|
||||||
|
|
||||||
|
private final Container container;
|
||||||
|
private final String localMountRoot;
|
||||||
|
private final DockerLinuxContainerRuntime runtime;
|
||||||
|
|
||||||
|
public ContainerVolumePublisher(Container container, String localMountRoot,
|
||||||
|
DockerLinuxContainerRuntime runtime) {
|
||||||
|
LOG.info("Initiate container volume publisher, containerID={},"
|
||||||
|
+ " volume local mount rootDir={}",
|
||||||
|
container.getContainerId().toString(), localMountRoot);
|
||||||
|
this.container = container;
|
||||||
|
this.localMountRoot = localMountRoot;
|
||||||
|
this.runtime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It first discovers the volume info from container resource;
|
||||||
|
* then negotiates with CSI driver adaptor to publish the volume on this
|
||||||
|
* node manager, on a specific directory under container's work dir;
|
||||||
|
* and then map the local mounted directory to volume target mount in
|
||||||
|
* the docker container.
|
||||||
|
*
|
||||||
|
* CSI volume publish is a two phase work, by reaching up here
|
||||||
|
* we can assume the 1st phase is done on the RM side, which means
|
||||||
|
* YARN is already called the controller service of csi-driver
|
||||||
|
* to publish the volume; here we only need to call the node service of
|
||||||
|
* csi-driver to publish the volume on this local node manager.
|
||||||
|
*
|
||||||
|
* @return a map where each key is the local mounted path on current node,
|
||||||
|
* and value is the remote mount path on the container.
|
||||||
|
* @throws YarnException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public Map<String, String> publishVolumes() throws YarnException,
|
||||||
|
IOException {
|
||||||
|
LOG.info("publishing volumes");
|
||||||
|
Map<String, String> volumeMounts = new HashMap<>();
|
||||||
|
List<VolumeMetaData> volumes = getVolumes();
|
||||||
|
LOG.info("Found {} volumes to be published on this node", volumes.size());
|
||||||
|
for (VolumeMetaData volume : volumes) {
|
||||||
|
Map<String, String> bindings = publishVolume(volume);
|
||||||
|
if (bindings != null && !bindings.isEmpty()) {
|
||||||
|
volumeMounts.putAll(bindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return volumeMounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unpublishVolumes() throws YarnException, IOException {
|
||||||
|
LOG.info("Un-publishing Volumes");
|
||||||
|
List<VolumeMetaData> volumes = getVolumes();
|
||||||
|
LOG.info("Volumes to un-publish {}", volumes.size());
|
||||||
|
for (VolumeMetaData volume : volumes) {
|
||||||
|
this.unpublishVolume(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getLocalVolumeMountPath(
|
||||||
|
String containerWorkDir, String volumeId) {
|
||||||
|
return new File(containerWorkDir, volumeId + "_mount");
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getLocalVolumeStagingPath(
|
||||||
|
String containerWorkDir, String volumeId) {
|
||||||
|
return new File(containerWorkDir, volumeId + "_staging");
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<VolumeMetaData> getVolumes() throws InvalidVolumeException {
|
||||||
|
List<VolumeMetaData> volumes = new ArrayList<>();
|
||||||
|
Resource containerResource = container.getResource();
|
||||||
|
if (containerResource != null) {
|
||||||
|
for (ResourceInformation resourceInformation :
|
||||||
|
containerResource.getAllResourcesListCopy()) {
|
||||||
|
if (resourceInformation.getTags().contains("system:csi-volume")) {
|
||||||
|
volumes.addAll(VolumeMetaData.fromResource(resourceInformation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (volumes.size() > 0) {
|
||||||
|
LOG.info("Total number of volumes require provisioning is {}",
|
||||||
|
volumes.size());
|
||||||
|
}
|
||||||
|
return volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> publishVolume(VolumeMetaData volume)
|
||||||
|
throws IOException, YarnException {
|
||||||
|
Map<String, String> bindVolumes = new HashMap<>();
|
||||||
|
// compose a local mount for CSI volume with the container ID
|
||||||
|
File localMount = getLocalVolumeMountPath(
|
||||||
|
localMountRoot, volume.getVolumeId().toString());
|
||||||
|
File localStaging = getLocalVolumeStagingPath(
|
||||||
|
localMountRoot, volume.getVolumeId().toString());
|
||||||
|
LOG.info("Volume {}, local mount path: {}, local staging path {}",
|
||||||
|
volume.getVolumeId().toString(), localMount, localStaging);
|
||||||
|
|
||||||
|
NodePublishVolumeRequest publishRequest = NodePublishVolumeRequest
|
||||||
|
.newInstance(volume.getVolumeId().getId(), // volume Id
|
||||||
|
false, // read only flag
|
||||||
|
localMount.getAbsolutePath(), // target path
|
||||||
|
localStaging.getAbsolutePath(), // staging path
|
||||||
|
new ValidateVolumeCapabilitiesRequest.VolumeCapability(
|
||||||
|
ValidateVolumeCapabilitiesRequest
|
||||||
|
.AccessMode.SINGLE_NODE_WRITER,
|
||||||
|
ValidateVolumeCapabilitiesRequest.VolumeType.FILE_SYSTEM,
|
||||||
|
ImmutableList.of()), // capability
|
||||||
|
ImmutableMap.of(), // publish context
|
||||||
|
ImmutableMap.of()); // secrets
|
||||||
|
|
||||||
|
// make sure the volume is a known type
|
||||||
|
if (runtime.getCsiClients().get(volume.getDriverName()) == null) {
|
||||||
|
throw new YarnException("No csi-adaptor is found that can talk"
|
||||||
|
+ " to csi-driver " + volume.getDriverName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish volume to node
|
||||||
|
LOG.info("Publish volume on NM, request {}",
|
||||||
|
publishRequest.toString());
|
||||||
|
runtime.getCsiClients().get(volume.getDriverName())
|
||||||
|
.nodePublishVolume(publishRequest);
|
||||||
|
// once succeed, bind the container to this mount
|
||||||
|
String containerMountPath = volume.getMountPoint();
|
||||||
|
bindVolumes.put(localMount.getAbsolutePath(), containerMountPath);
|
||||||
|
return bindVolumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unpublishVolume(VolumeMetaData volume)
|
||||||
|
throws YarnException, IOException {
|
||||||
|
CsiAdaptorProtocol csiClient =
|
||||||
|
runtime.getCsiClients().get(volume.getDriverName());
|
||||||
|
if (csiClient == null) {
|
||||||
|
throw new YarnException(
|
||||||
|
"No csi-adaptor is found that can talk"
|
||||||
|
+ " to csi-driver " + volume.getDriverName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// When container is launched, the container work dir is memorized,
|
||||||
|
// and that is also the dir we mount the volume to.
|
||||||
|
File localMount = getLocalVolumeMountPath(container.getCsiVolumesRootDir(),
|
||||||
|
volume.getVolumeId().toString());
|
||||||
|
if (!localMount.exists()) {
|
||||||
|
LOG.info("Local mount {} no longer exist, skipping cleaning"
|
||||||
|
+ " up the volume", localMount.getAbsolutePath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NodeUnpublishVolumeRequest unpublishRequest =
|
||||||
|
NodeUnpublishVolumeRequest.newInstance(
|
||||||
|
volume.getVolumeId().getId(), // volume id
|
||||||
|
localMount.getAbsolutePath()); // target path
|
||||||
|
|
||||||
|
// un-publish volume from node
|
||||||
|
LOG.info("Un-publish volume {}, request {}",
|
||||||
|
volume.getVolumeId().toString(), unpublishRequest.toString());
|
||||||
|
csiClient.nodeUnpublishVolume(unpublishRequest);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSI volumes.
|
||||||
|
*/
|
||||||
|
package org.apache.hadoop.yarn.server.nodemanager.containermanager.volume.csi;
|
|
@ -45,6 +45,7 @@ public final class ContainerStartContext {
|
||||||
private final String user;
|
private final String user;
|
||||||
private final String appId;
|
private final String appId;
|
||||||
private final Path containerWorkDir;
|
private final Path containerWorkDir;
|
||||||
|
private final Path csiVolumesRootDir;
|
||||||
private final List<String> localDirs;
|
private final List<String> localDirs;
|
||||||
private final List<String> logDirs;
|
private final List<String> logDirs;
|
||||||
private final List<String> filecacheDirs;
|
private final List<String> filecacheDirs;
|
||||||
|
@ -64,6 +65,7 @@ public final class ContainerStartContext {
|
||||||
private String user;
|
private String user;
|
||||||
private String appId;
|
private String appId;
|
||||||
private Path containerWorkDir;
|
private Path containerWorkDir;
|
||||||
|
private Path csiVolumesRoot;
|
||||||
private List<String> localDirs;
|
private List<String> localDirs;
|
||||||
private List<String> logDirs;
|
private List<String> logDirs;
|
||||||
private List<String> filecacheDirs;
|
private List<String> filecacheDirs;
|
||||||
|
@ -118,6 +120,11 @@ public final class ContainerStartContext {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder setContainerCsiVolumesRootDir(Path csiVolumesRootDir) {
|
||||||
|
this.csiVolumesRoot = csiVolumesRootDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setContainerWorkDir(Path containerWorkDir) {
|
public Builder setContainerWorkDir(Path containerWorkDir) {
|
||||||
this.containerWorkDir = containerWorkDir;
|
this.containerWorkDir = containerWorkDir;
|
||||||
return this;
|
return this;
|
||||||
|
@ -188,6 +195,7 @@ public final class ContainerStartContext {
|
||||||
this.containerLogDirs = builder.containerLogDirs;
|
this.containerLogDirs = builder.containerLogDirs;
|
||||||
this.userFilecacheDirs = builder.userFilecacheDirs;
|
this.userFilecacheDirs = builder.userFilecacheDirs;
|
||||||
this.applicationLocalDirs = builder.applicationLocalDirs;
|
this.applicationLocalDirs = builder.applicationLocalDirs;
|
||||||
|
this.csiVolumesRootDir = builder.csiVolumesRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Container getContainer() {
|
public Container getContainer() {
|
||||||
|
@ -262,4 +270,8 @@ public final class ContainerStartContext {
|
||||||
public List<String> getApplicationLocalDirs() {
|
public List<String> getApplicationLocalDirs() {
|
||||||
return Collections.unmodifiableList(this.applicationLocalDirs);
|
return Collections.unmodifiableList(this.applicationLocalDirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Path getCsiVolumesRootDir() {
|
||||||
|
return this.csiVolumesRootDir;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -173,6 +173,16 @@ public class MockContainer implements Container {
|
||||||
public void setWorkDir(String workDir) {
|
public void setWorkDir(String workDir) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCsiVolumesRootDir() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCsiVolumesRootDir(String volumesRootDir) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLogDir() {
|
public String getLogDir() {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -146,6 +146,8 @@ public class VolumeImpl implements Volume {
|
||||||
@Override
|
@Override
|
||||||
public VolumeState transition(VolumeImpl volume,
|
public VolumeState transition(VolumeImpl volume,
|
||||||
VolumeEvent volumeEvent) {
|
VolumeEvent volumeEvent) {
|
||||||
|
// Some of CSI driver implementation does't provide the capability
|
||||||
|
// to validate volumes. Skip this for now.
|
||||||
try {
|
try {
|
||||||
// this call could cross node, we should keep the message tight
|
// this call could cross node, we should keep the message tight
|
||||||
// TODO we should parse the capability from volume resource spec
|
// TODO we should parse the capability from volume resource spec
|
||||||
|
|
Loading…
Reference in New Issue