Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c033947
Ecs File IO.
wang-x-xia Oct 25, 2021
e1eea69
Add unit test with client-side mock.
wang-x-xia Oct 26, 2021
af7e750
1. Fix the copyright.
wang-x-xia Oct 26, 2021
ba9dcc3
1. Separate EcsFile to EcsInputFile and EcsOutputFile.
wang-x-xia Oct 27, 2021
c9b785a
Fix Copyright.
wang-x-xia Oct 27, 2021
96f85a1
Fix checkStyle.
wang-x-xia Oct 27, 2021
aaacceb
1. Create factory method for EcsAppendOutputStream.
wang-x-xia Nov 4, 2021
ba7beb0
Rename factory methods of EcsAppendOutputStream.
wang-x-xia Nov 4, 2021
3a21b44
Replace IOUtils to Guava ByteStreams.
wang-x-xia Nov 4, 2021
d79d2b0
Remove closed flag in EcsFileIO.
wang-x-xia Nov 4, 2021
ef996a0
1. Move EcsURI to package-private.
wang-x-xia Nov 5, 2021
ab0e5b0
1. Fix checkstyle.
wang-x-xia Nov 5, 2021
aa9c843
Use AssertHelpers.assertThrows.
wang-x-xia Nov 5, 2021
7a5dce3
Merge branch 'master' into feature-ecs-fileio
Jan 11, 2022
7eff9a6
Unify with existed codes.
wang-x-xia Jan 11, 2022
d1f6128
Check code style of test code.
wang-x-xia Jan 12, 2022
a751aa3
Add prefix for properties' keys.
wang-x-xia Jan 12, 2022
ef7e267
Merge branch 'master' into feature-ecs-fileio
wang-x-xia Feb 14, 2022
1e8419b
Fix the class name.
wang-x-xia Feb 16, 2022
57978d8
Merge remote-tracking branch 'github/master' into feature-ecs-fileio
wang-x-xia Feb 16, 2022
48807d8
Merge remote-tracking branch 'github/master' into feature-ecs-fileio
wang-x-xia Feb 16, 2022
4a1b982
1. Fix the fields' name and comment.
wang-x-xia Feb 17, 2022
54f369f
Remove unused imports.
wang-x-xia Feb 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,23 @@ project(':iceberg-nessie') {
}
}

project(':iceberg-dell') {
dependencies {
implementation project(':iceberg-core')
implementation project(':iceberg-common')
implementation project(path: ':iceberg-bundled-guava', configuration: 'shadow')
implementation 'com.emc.ecs:object-client-bundle'
Comment thread
jackye1995 marked this conversation as resolved.
Outdated

testImplementation("org.apache.hadoop:hadoop-common") {
exclude group: 'org.apache.avro', module: 'avro'
exclude group: 'org.slf4j', module: 'slf4j-log4j12'
}
testImplementation "javax.xml.bind:jaxb-api"
testImplementation "javax.activation:activation"
testImplementation "org.glassfish.jaxb:jaxb-runtime"
}
}

@Memoized
boolean isVersionFileExists() {
return file('version.txt').exists()
Expand Down
56 changes: 56 additions & 0 deletions dell/src/main/java/org/apache/iceberg/dell/BaseEcsFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.iceberg.dell;

import com.emc.object.s3.S3Client;
import com.emc.object.s3.S3Exception;

public class BaseEcsFile {

protected final S3Client client;
protected final String location;
protected final EcsURI uri;

public BaseEcsFile(S3Client client, String location) {
this.client = client;
this.location = location;
this.uri = EcsURI.create(location);
}

public String location() {
return location;
}

/**
* Check whether data file exists.
*/
public boolean exists() {
try {
client.getObjectMetadata(uri.getBucket(), uri.getName());
return true;
} catch (S3Exception e) {
if (e.getHttpCode() == 404) {
return false;
} else {
throw e;
}
}
}
}
149 changes: 149 additions & 0 deletions dell/src/main/java/org/apache/iceberg/dell/EcsAppendOutputStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* 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.iceberg.dell;

import com.emc.object.s3.S3Client;
import com.emc.object.s3.request.PutObjectRequest;
import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
import org.apache.iceberg.io.PositionOutputStream;

/**
* Use ECS append API to write data.
*/
class EcsAppendOutputStream extends PositionOutputStream {

private final S3Client client;

private final EcsURI key;

/**
* Local bytes cache that avoid too many requests
* <p>
* Use {@link ByteBuffer} to maintain offset.
*/
private final ByteBuffer localCache;

/**
* A marker for data file to put first part instead of append first part.
*/
private boolean firstPart = true;

/**
* Pos for {@link PositionOutputStream}
*/
private long pos;

private EcsAppendOutputStream(S3Client client, EcsURI key, byte[] localCache) {
this.client = client;
this.key = key;
this.localCache = ByteBuffer.wrap(localCache);
}

/**
* Use built-in 1 KiB byte buffer
*/
static EcsAppendOutputStream create(S3Client client, EcsURI uri) {
return createWithBufferSize(client, uri, 1024);
}

/**
* Create {@link PositionOutputStream} with specific buffer size.
*/
static EcsAppendOutputStream createWithBufferSize(S3Client client, EcsURI uri, int size) {
return new EcsAppendOutputStream(client, uri, new byte[size]);
}

/**
* Write a byte. If buffer is full, upload the buffer.
*/
@Override
public void write(int b) {
if (!checkBuffer(1)) {
flush();
}

localCache.put((byte) b);
pos += 1;
}

/**
* Write a byte.
* If buffer is full, upload the buffer.
* If buffer size &lt; input bytes, upload input bytes.
*/
@Override
public void write(byte[] b, int off, int len) {
if (!checkBuffer(len)) {
flush();
}

if (checkBuffer(len)) {
localCache.put(b, off, len);
} else {
// if content > cache, directly flush itself.
flushBuffer(b, off, len);
}

pos += len;
}

private boolean checkBuffer(int nextWrite) {
return localCache.remaining() >= nextWrite;
}

private void flushBuffer(byte[] buffer, int offset, int length) {
if (firstPart) {
client.putObject(new PutObjectRequest(key.getBucket(), key.getName(),
new ByteArrayInputStream(buffer, offset, length)));
firstPart = false;
} else {
client.appendObject(key.getBucket(), key.getName(), new ByteArrayInputStream(buffer, offset, length));
}
}

/**
* Pos of the file
*/
@Override
public long getPos() {
return pos;
}

/**
* Write cached bytes if present.
*/
@Override
public void flush() {
if (localCache.remaining() < localCache.capacity()) {
localCache.flip();
flushBuffer(localCache.array(), localCache.arrayOffset(), localCache.remaining());
localCache.clear();
}
}

/**
* Trigger flush() when closing stream.
*/
@Override
public void close() {
flush();
}
}
96 changes: 96 additions & 0 deletions dell/src/main/java/org/apache/iceberg/dell/EcsClientFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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.iceberg.dell;

import com.emc.object.s3.S3Client;
import com.emc.object.s3.S3Config;
import com.emc.object.s3.jersey.S3JerseyClient;
import java.net.URI;
import java.util.Map;
import java.util.Optional;
import org.apache.iceberg.common.DynConstructors;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;

public interface EcsClientFactory {

/**
* Create the ECS S3 Client from properties
*/
static S3Client create(Map<String, String> properties) {
return createWithFactory(properties).orElseGet(() -> createDefault(properties));
}

/**
* Try to create the ECS S3 client from factory method.
*/
static Optional<S3Client> createWithFactory(Map<String, String> properties) {
Comment thread
jackye1995 marked this conversation as resolved.
Outdated
String factory = properties.get(EcsClientProperties.ECS_CLIENT_FACTORY);
if (factory == null || factory.isEmpty()) {
return Optional.empty();
}

DynConstructors.Ctor<EcsClientFactory> ctor;
try {
ctor = DynConstructors.builder(EcsClientFactory.class).impl(factory).buildChecked();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(String.format(
"Cannot find EcsClientFactory implementation %s: %s", factory, e.getMessage()), e);
}

EcsClientFactory clientFactory;
try {
clientFactory = ctor.newInstance();
} catch (ClassCastException e) {
throw new IllegalArgumentException(
String.format("Cannot initialize Catalog, %s does not implement EcsClientFactory.", factory), e);
Comment thread
jackye1995 marked this conversation as resolved.
Outdated
}

S3Client client = clientFactory.createS3Client(properties);

if (client == null) {
throw new IllegalArgumentException(String.format(
"Invalid EcsClientFactory %s that return null client",
factory));
}

return Optional.of(client);
}

/**
* Get built-in ECS S3 client.
*/
static S3Client createDefault(Map<String, String> properties) {
Preconditions.checkNotNull(properties.get(EcsClientProperties.ENDPOINT),
"Endpoint(%s) cannot be null", EcsClientProperties.ENDPOINT);
Comment thread
jackye1995 marked this conversation as resolved.
Outdated
Preconditions.checkNotNull(properties.get(EcsClientProperties.ACCESS_KEY_ID),
"Access key(%s) cannot be null", EcsClientProperties.ACCESS_KEY_ID);
Preconditions.checkNotNull(properties.get(EcsClientProperties.SECRET_ACCESS_KEY),
"Secret key(%s) cannot be null", EcsClientProperties.SECRET_ACCESS_KEY);

S3Config config = new S3Config(URI.create(properties.get(EcsClientProperties.ENDPOINT)));

config.withIdentity(properties.get(EcsClientProperties.ACCESS_KEY_ID))
Comment thread
jackye1995 marked this conversation as resolved.
Outdated
.withSecretKey(properties.get(EcsClientProperties.SECRET_ACCESS_KEY));

return new S3JerseyClient(config);
}

S3Client createS3Client(Map<String, String> properties);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.iceberg.dell;

/**
* Property constants of catalog
Comment thread
jackye1995 marked this conversation as resolved.
Outdated
*/
public interface EcsClientProperties {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering, if you want to make this class name more generic, like EmcProperties or DellProperties, in case you want to introduce other stuffs in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We assume to use this class name. I think other staffs should use their own properties.

Comment thread
jackye1995 marked this conversation as resolved.
Outdated

/**
* Access key id
*/
String ACCESS_KEY_ID = "ecs.s3.access.key.id";
Comment thread
jackye1995 marked this conversation as resolved.
Outdated
Comment thread
jackye1995 marked this conversation as resolved.
Outdated

/**
* Secret access key
*/
String SECRET_ACCESS_KEY = "ecs.s3.secret.access.key";

/**
* S3 endpoint
*/
String ENDPOINT = "ecs.s3.endpoint";

/**
* Factory class of {@link EcsClientFactory}.
* <p>
* The config is optional. If properties above aren't enough, use this.
Comment thread
jackye1995 marked this conversation as resolved.
Outdated
*/
String ECS_CLIENT_FACTORY = "ecs.client.factory";
}
Loading