Skip to content

Commit 8f36ebf

Browse files
author
root
committed
Adding New Dell ECS Plugin to the API & editing Frontend for the ECS Provider
1 parent 6dc259c commit 8f36ebf

File tree

10 files changed

+2153
-68
lines changed

10 files changed

+2153
-68
lines changed

client/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,11 @@
632632
<artifactId>cloud-plugin-storage-object-minio</artifactId>
633633
<version>${project.version}</version>
634634
</dependency>
635+
<dependency>
636+
<groupId>org.apache.cloudstack</groupId>
637+
<artifactId>cloud-plugin-storage-object-ecs</artifactId>
638+
<version>${project.version}</version>
639+
</dependency>
635640
<dependency>
636641
<groupId>org.apache.cloudstack</groupId>
637642
<artifactId>cloud-plugin-storage-object-ceph</artifactId>

plugins/storage/object/ECS/pom.xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
21+
<modelVersion>4.0.0</modelVersion>
22+
<artifactId>cloud-plugin-storage-object-ecs</artifactId>
23+
<name>Apache CloudStack Plugin - ECS object storage provider</name>
24+
<parent>
25+
<groupId>org.apache.cloudstack</groupId>
26+
<artifactId>cloudstack-plugins</artifactId>
27+
<version>4.23.0.0-SNAPSHOT</version>
28+
<relativePath>../../../pom.xml</relativePath>
29+
</parent>
30+
<dependencies>
31+
<dependency>
32+
<groupId>org.apache.cloudstack</groupId>
33+
<artifactId>cloud-engine-storage</artifactId>
34+
<version>${project.version}</version>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.apache.cloudstack</groupId>
38+
<artifactId>cloud-engine-storage-object</artifactId>
39+
<version>${project.version}</version>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.apache.cloudstack</groupId>
43+
<artifactId>cloud-engine-schema</artifactId>
44+
<version>${project.version}</version>
45+
</dependency>
46+
</dependencies>
47+
</project>

plugins/storage/object/ECS/src/main/java/org/apache/cloudstack/storage/datastore/driver/EcsObjectStoreDriverImpl.java

Lines changed: 1490 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.cloudstack.storage.datastore.lifecycle;
20+
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
import javax.inject.Inject;
25+
import javax.net.ssl.SSLContext;
26+
27+
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
28+
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
29+
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
30+
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
31+
import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
32+
import org.apache.cloudstack.storage.object.datastore.ObjectStoreHelper;
33+
import org.apache.cloudstack.storage.object.datastore.ObjectStoreProviderManager;
34+
import org.apache.cloudstack.storage.object.store.lifecycle.ObjectStoreLifeCycle;
35+
import org.apache.logging.log4j.LogManager;
36+
import org.apache.logging.log4j.Logger;
37+
import org.apache.http.auth.UsernamePasswordCredentials;
38+
import org.apache.http.client.methods.CloseableHttpResponse;
39+
import org.apache.http.client.methods.HttpGet; // change to POST if ECS needs it
40+
import org.apache.http.conn.ssl.NoopHostnameVerifier;
41+
import org.apache.http.impl.auth.BasicScheme;
42+
import org.apache.http.impl.client.CloseableHttpClient;
43+
import org.apache.http.impl.client.HttpClients;
44+
import org.apache.http.ssl.SSLContextBuilder;
45+
import org.apache.http.ssl.TrustStrategy;
46+
47+
import com.cloud.agent.api.StoragePoolInfo;
48+
import com.cloud.hypervisor.Hypervisor.HypervisorType;
49+
import com.cloud.utils.exception.CloudRuntimeException;
50+
51+
public class EcsObjectStoreLifeCycleImpl implements ObjectStoreLifeCycle {
52+
53+
private static final Logger LOG = LogManager.getLogger(EcsObjectStoreLifeCycleImpl.class);
54+
55+
// detail keys coming from the API
56+
private static final String MGMT_URL = "mgmt_url";
57+
private static final String SA_USER = "sa_user";
58+
private static final String SA_PASS = "sa_password";
59+
private static final String INSECURE = "insecure";
60+
61+
// optional details (currently not used in persistence logic but accepted)
62+
private static final String S3_HOST = "s3_host";
63+
private static final String NAMESPACE = "namespace";
64+
65+
private static final String PROVIDER_NAME = "ECS";
66+
67+
@Inject
68+
ObjectStoreHelper objectStoreHelper;
69+
70+
@Inject
71+
ObjectStoreProviderManager objectStoreMgr;
72+
73+
public EcsObjectStoreLifeCycleImpl() {
74+
}
75+
76+
@SuppressWarnings("unchecked")
77+
@Override
78+
public DataStore initialize(Map<String, Object> dsInfos) {
79+
if (objectStoreHelper == null) {
80+
throw new CloudRuntimeException("ECS: ObjectStoreHelper is not injected");
81+
}
82+
if (objectStoreMgr == null) {
83+
throw new CloudRuntimeException("ECS: ObjectStoreProviderManager is not injected");
84+
}
85+
86+
// Top-level params (follow Ceph pattern)
87+
String url = (String) dsInfos.get("url");
88+
String name = (String) dsInfos.get("name");
89+
Long size = (Long) dsInfos.get("size"); // may be null
90+
String providerName = (String) dsInfos.get("providerName"); // should be "ECS"
91+
92+
Map<String, String> details = (Map<String, String>) dsInfos.get("details");
93+
if (details == null) {
94+
throw new CloudRuntimeException("ECS: details map is missing");
95+
}
96+
97+
// Validate required details
98+
String mgmtUrl = trim(details.get(MGMT_URL));
99+
String saUser = safe(details.get(SA_USER));
100+
String saPass = safe(details.get(SA_PASS));
101+
boolean insecure = Boolean.parseBoolean(details.getOrDefault(INSECURE, "false"));
102+
103+
if (mgmtUrl == null || mgmtUrl.isEmpty()) {
104+
throw new CloudRuntimeException("ECS: missing required detail '" + MGMT_URL + "'");
105+
}
106+
if (saUser.isEmpty()) {
107+
throw new CloudRuntimeException("ECS: missing required detail '" + SA_USER + "'");
108+
}
109+
if (saPass.isEmpty()) {
110+
throw new CloudRuntimeException("ECS: missing required detail '" + SA_PASS + "'");
111+
}
112+
113+
if (providerName == null || providerName.isEmpty()) {
114+
providerName = PROVIDER_NAME;
115+
}
116+
117+
LOG.info("ECS initialize: provider='{}', name='{}', url='{}', mgmt_url='{}', insecure={}, s3_host='{}', namespace='{}'",
118+
providerName, name, url, mgmtUrl, insecure,
119+
details.get(S3_HOST), details.get(NAMESPACE));
120+
121+
// Try ECS login up-front so we fail fast on bad config
122+
loginAndGetToken(mgmtUrl, saUser, saPass, insecure);
123+
124+
// Put “canonical” values back into details (so DB keeps what we validated)
125+
details.put(MGMT_URL, mgmtUrl);
126+
details.put(SA_USER, saUser);
127+
details.put(SA_PASS, saPass);
128+
details.put(INSECURE, Boolean.toString(insecure));
129+
130+
// Build objectStore parameters exactly like Ceph does
131+
Map<String, Object> objectStoreParameters = new HashMap<>();
132+
objectStoreParameters.put("name", name);
133+
objectStoreParameters.put("url", url);
134+
objectStoreParameters.put("size", size);
135+
objectStoreParameters.put("providerName", providerName);
136+
137+
try {
138+
LOG.info("ECS: creating ObjectStore in DB: name='{}', provider='{}', url='{}'",
139+
name, providerName, url);
140+
ObjectStoreVO objectStore = objectStoreHelper.createObjectStore(objectStoreParameters, details);
141+
if (objectStore == null) {
142+
throw new CloudRuntimeException("ECS: createObjectStore returned null");
143+
}
144+
145+
DataStore store = objectStoreMgr.getObjectStore(objectStore.getId());
146+
if (store == null) {
147+
throw new CloudRuntimeException("ECS: getObjectStore returned null for id=" + objectStore.getId());
148+
}
149+
150+
LOG.info("ECS: object store created: id={}, name='{}'", objectStore.getId(), name);
151+
return store;
152+
} catch (RuntimeException e) {
153+
String msg = "ECS: failed to persist object store '" + name + "': " + safeMsg(e);
154+
LOG.error(msg, e);
155+
throw new CloudRuntimeException(msg, e);
156+
}
157+
}
158+
159+
@Override
160+
public boolean attachCluster(DataStore store, ClusterScope scope) {
161+
return false;
162+
}
163+
164+
@Override
165+
public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
166+
return false;
167+
}
168+
169+
@Override
170+
public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) {
171+
return false;
172+
}
173+
174+
@Override
175+
public boolean maintain(DataStore store) {
176+
return false;
177+
}
178+
179+
@Override
180+
public boolean cancelMaintain(DataStore store) {
181+
return false;
182+
}
183+
184+
@Override
185+
public boolean deleteDataStore(DataStore store) {
186+
return false;
187+
}
188+
189+
@Override
190+
public boolean migrateToObjectStore(DataStore store) {
191+
return false;
192+
}
193+
194+
// ---------- helpers ----------
195+
196+
private static String safe(String v) {
197+
return v == null ? "" : v.trim();
198+
}
199+
200+
private static String trim(String v) {
201+
if (v == null) {
202+
return null;
203+
}
204+
v = v.trim();
205+
return v.endsWith("/") ? v.substring(0, v.length() - 1) : v;
206+
}
207+
208+
private static String safeMsg(Throwable t) {
209+
if (t == null) {
210+
return "unknown";
211+
}
212+
String m = t.getMessage();
213+
return (m == null || m.isEmpty()) ? t.getClass().getSimpleName() : m;
214+
}
215+
216+
private CloseableHttpClient buildHttpClient(boolean insecure) {
217+
if (!insecure) {
218+
return HttpClients.createDefault();
219+
}
220+
try {
221+
TrustStrategy trustAll = (chain, authType) -> true;
222+
SSLContext sslContext = SSLContextBuilder.create()
223+
.loadTrustMaterial(null, trustAll)
224+
.build();
225+
return HttpClients.custom()
226+
.setSSLContext(sslContext)
227+
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
228+
.build();
229+
} catch (Exception e) {
230+
throw new CloudRuntimeException("ECS: failed to build HttpClient", e);
231+
}
232+
}
233+
234+
private String loginAndGetToken(String mgmtUrl, String user, String pass, boolean insecure) {
235+
try (CloseableHttpClient http = buildHttpClient(insecure)) {
236+
HttpGet get = new HttpGet(mgmtUrl + "/login");
237+
get.addHeader(new BasicScheme().authenticate(
238+
new UsernamePasswordCredentials(user, pass), get, null));
239+
try (CloseableHttpResponse resp = http.execute(get)) {
240+
int status = resp.getStatusLine().getStatusCode();
241+
if (status != 200 && status != 201) {
242+
throw new CloudRuntimeException("ECS /login failed: HTTP " + status);
243+
}
244+
if (resp.getFirstHeader("X-SDS-AUTH-TOKEN") == null) {
245+
throw new CloudRuntimeException("ECS /login missing X-SDS-AUTH-TOKEN");
246+
}
247+
return resp.getFirstHeader("X-SDS-AUTH-TOKEN").getValue();
248+
}
249+
} catch (Exception e) {
250+
String msg = "ECS: management login error: " + safeMsg(e);
251+
LOG.error(msg, e);
252+
throw new CloudRuntimeException(msg, e);
253+
}
254+
}
255+
}

0 commit comments

Comments
 (0)