ObsObjStorage.java
// 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.doris.fs.obj;
import org.apache.doris.cloud.storage.ObjectInfoAdapter;
import org.apache.doris.common.DdlException;
import org.apache.doris.datasource.property.storage.OBSProperties;
import com.huaweicloud.sdk.iam.v3.model.AgencyAuth;
import com.huaweicloud.sdk.iam.v3.model.AgencyAuthIdentity;
import com.huaweicloud.sdk.iam.v3.model.CreateTemporaryAccessKeyByAgencyRequest;
import com.huaweicloud.sdk.iam.v3.model.CreateTemporaryAccessKeyByAgencyRequestBody;
import com.huaweicloud.sdk.iam.v3.model.CreateTemporaryAccessKeyByAgencyResponse;
import com.huaweicloud.sdk.iam.v3.model.Credential;
import com.huaweicloud.sdk.iam.v3.model.IdentityAssumerole;
import com.obs.services.model.HttpMethodEnum;
import com.obs.services.model.TemporarySignatureRequest;
import com.obs.services.model.TemporarySignatureResponse;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Huawei Cloud OBS-specific {@link ObjStorage} implementation.
*
* <p>Inherits generic CRUD and {@code listObjectsWithPrefix} from {@link S3ObjStorage}
* (OBS supports S3-compatible list API). Overrides STS (IAM agency) and
* presigned URL (OBS TemporarySignature).
*
* <p>Note: in OBS, the {@code sts.role_arn} field carries the <em>domain name</em>
* and {@code sts.role_name} carries the <em>agency name</em>.
*/
public class ObsObjStorage extends S3ObjStorage {
private static final Logger LOG = LogManager.getLogger(ObsObjStorage.class);
private static final long SESSION_EXPIRE_SECONDS = 3600L;
private final OBSProperties obsProperties;
public ObsObjStorage(OBSProperties properties) {
super(properties);
this.obsProperties = properties;
}
// ----------------------------------------------------------------
// STS: Huawei Cloud IAM createTemporaryAccessKeyByAgency
// (sts.role_name = agencyName, sts.role_arn = domainName)
// ----------------------------------------------------------------
@Override
public Triple<String, String, String> getStsToken() throws DdlException {
// OBS maps arn → domainName, roleName → agencyName
String agencyName = obsProperties.getOrigProps().get(ObjectInfoAdapter.STS_ROLE_NAME_KEY);
String domainName = obsProperties.getOrigProps().get(ObjectInfoAdapter.STS_ROLE_ARN_KEY);
if (agencyName == null || domainName == null) {
throw new DdlException("OBS STS requires sts.role_name (agency name) and sts.role_arn (domain name)");
}
try (ObsNativeClient nativeClient = new ObsNativeClient(obsProperties)) {
IdentityAssumerole assumerole = new IdentityAssumerole();
assumerole.withAgencyName(agencyName)
.withDomainName(domainName)
.withDurationSeconds(ObjectInfoAdapter.getDurationSeconds());
List<AgencyAuthIdentity.MethodsEnum> methods = new ArrayList<>();
methods.add(AgencyAuthIdentity.MethodsEnum.fromValue("assume_role"));
AgencyAuthIdentity identity = new AgencyAuthIdentity();
identity.withMethods(methods).withAssumeRole(assumerole);
AgencyAuth auth = new AgencyAuth().withIdentity(identity);
CreateTemporaryAccessKeyByAgencyRequestBody body =
new CreateTemporaryAccessKeyByAgencyRequestBody().withAuth(auth);
CreateTemporaryAccessKeyByAgencyRequest req =
new CreateTemporaryAccessKeyByAgencyRequest().withBody(body);
CreateTemporaryAccessKeyByAgencyResponse resp =
nativeClient.getIamClient().createTemporaryAccessKeyByAgency(req);
Credential cred = resp.getCredential();
return Triple.of(cred.getAccess(), cred.getSecret(), cred.getSecuritytoken());
} catch (Throwable e) {
LOG.warn("Failed to get OBS STS token", e);
throw new DdlException("Failed to get OBS STS token: " + e.getMessage());
}
}
// ----------------------------------------------------------------
// Presigned URL: OBS TemporarySignature
// ----------------------------------------------------------------
@Override
public String getPresignedUrl(String objectKey) throws IOException {
try (ObsNativeClient nativeClient = new ObsNativeClient(obsProperties)) {
TemporarySignatureRequest req = new TemporarySignatureRequest(
HttpMethodEnum.PUT, SESSION_EXPIRE_SECONDS);
req.setBucketName(obsProperties.getBucket());
req.setObjectKey(objectKey);
req.setHeaders(new HashMap<>());
TemporarySignatureResponse resp =
nativeClient.getObsClient().createTemporarySignature(req);
String url = resp.getSignedUrl();
LOG.info("Generated OBS presigned URL for key={}", objectKey);
return url;
} catch (Throwable e) {
throw new IOException("Failed to generate OBS presigned URL: " + e.getMessage(), e);
}
}
// listObjectsWithPrefix inherits S3ObjStorage (OBS supports S3-compatible list API)
}