diff --git a/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/RedfishConnect.java b/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/RedfishConnect.java new file mode 100644 index 00000000000..199a8350ec1 --- /dev/null +++ b/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/RedfishConnect.java @@ -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.hertzbeat.collector.collect.common.cache; + +import lombok.extern.slf4j.Slf4j; +import org.apache.hertzbeat.collector.collect.redfish.ConnectSession; + +/** + * redfish connect session + */ +@Slf4j +public class RedfishConnect implements CacheCloseable{ + private final ConnectSession reddishConnectSession; + + public RedfishConnect(ConnectSession reddishConnectSession) { + this.reddishConnectSession = reddishConnectSession; + } + + @Override + public void close() { + try { + if (reddishConnectSession != null) { + reddishConnectSession.close(); + } + } catch (Exception e) { + log.error("[connection common cache] close redfish connect error: {}", e.getMessage()); + } + } + + public ConnectSession getConnection() { + return reddishConnectSession; + } +} diff --git a/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/ConnectSession.java b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/ConnectSession.java new file mode 100644 index 00000000000..38b8bb14c65 --- /dev/null +++ b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/ConnectSession.java @@ -0,0 +1,27 @@ +/* + * 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.hertzbeat.collector.collect.redfish; + +/** + * redfish client interface + */ +public interface ConnectSession extends AutoCloseable { + boolean isOpen(); + + String getRedfishResource(String uri) throws Exception; +} diff --git a/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishClient.java b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishClient.java new file mode 100644 index 00000000000..b813b6653c9 --- /dev/null +++ b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishClient.java @@ -0,0 +1,112 @@ +/* + * 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.hertzbeat.collector.collect.redfish; + +import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient; +import org.apache.hertzbeat.common.constants.CollectorConstants; +import org.apache.hertzbeat.common.entity.job.protocol.RedfishProtocol; +import org.apache.hertzbeat.common.util.IpDomainUtil; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpHost; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.entity.StringEntity; + +/** + * redfish client impl + */ +public class RedfishClient { + private final String host; + private final Integer port; + private final String username; + private final String password; + private final Integer timeout; + public static final String REDFISH_SESSION_SERVICE = "/redfish/v1/SessionService/Sessions"; + + protected RedfishClient(String host, int port, String username, String password, Integer timeout) { + this.host = host; + this.port = port; + this.username = username; + this.password = password; + this.timeout = timeout; + } + + public static RedfishClient create(RedfishProtocol redfishProtocol) { + return new RedfishClient(redfishProtocol.getHost(), Integer.parseInt(redfishProtocol.getPort()), + redfishProtocol.getUsername(), redfishProtocol.getPassword(), Integer.parseInt(redfishProtocol.getTimeout())); + } + + public ConnectSession connect() throws Exception { + HttpHost host = new HttpHost(this.host, this.port); + HttpClientContext httpClientContext = new HttpClientContext(); + httpClientContext.setTargetHost(host); + RequestBuilder requestBuilder = RequestBuilder.post(); + + String uri = REDFISH_SESSION_SERVICE; + if (IpDomainUtil.isHasSchema(this.host)) { + requestBuilder.setUri(this.host + ":" + this.port + uri); + } else { + String ipAddressType = IpDomainUtil.checkIpAddressType(this.host); + String baseUri = CollectorConstants.IPV6.equals(ipAddressType) + ? String.format("[%s]:%s", this.host, this.port + uri) + : String.format("%s:%s", this.host, this.port + uri); + + requestBuilder.setUri(CollectorConstants.HTTP_HEADER + baseUri); + } + + requestBuilder.addHeader(HttpHeaders.CONNECTION, "Keep-Alive"); + requestBuilder.addHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + requestBuilder.addHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36"); + requestBuilder.addHeader(HttpHeaders.CONTENT_ENCODING, "UTF-8"); + + final String json = "{\"UserName\": \"" + this.username + "\", \"Password\": \"" + this.password + "\"}"; + StringEntity entity = new StringEntity(json, "UTF-8"); + requestBuilder.setEntity(entity); + + if (this.timeout > 0) { + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(this.timeout) + .setSocketTimeout(this.timeout) + .setRedirectsEnabled(true) + .build(); + requestBuilder.setConfig(requestConfig); + } + + HttpUriRequest request = requestBuilder.build(); + + Session session; + try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpClientContext)) { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_CREATED) { + throw new Exception("Http Status Code: " + statusCode); + } + String location = response.getFirstHeader("Location").getValue(); + String auth = response.getFirstHeader("X-Auth-Token").getValue(); + session = new Session(auth, location, this.host, this.port); + } catch (Exception e) { + throw new Exception("Redfish session create error: " + e.getMessage()); + } finally { + request.abort(); + } + return new RedfishConnectSession(session); + } +} diff --git a/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishCollectImpl.java b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishCollectImpl.java new file mode 100644 index 00000000000..7ba22fede98 --- /dev/null +++ b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishCollectImpl.java @@ -0,0 +1,193 @@ +/* + * 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.hertzbeat.collector.collect.redfish; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.apache.hertzbeat.collector.collect.AbstractCollect; +import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier; +import org.apache.hertzbeat.collector.collect.common.cache.ConnectionCommonCache; +import org.apache.hertzbeat.collector.collect.common.cache.RedfishConnect; +import org.apache.hertzbeat.collector.dispatch.DispatchConstants; +import org.apache.hertzbeat.collector.util.JsonPathParser; +import org.apache.hertzbeat.common.constants.CommonConstants; +import org.apache.hertzbeat.common.entity.job.Metrics; +import org.apache.hertzbeat.common.entity.job.protocol.RedfishProtocol; +import org.apache.hertzbeat.common.entity.message.CollectRep; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * redfish collect impl + */ +@Slf4j +public class RedfishCollectImpl extends AbstractCollect { + + @Override + public void collect(CollectRep.MetricsData.Builder builder, long monitorId, String app, Metrics metrics) { + try { + validateParams(metrics); + } catch (Exception e) { + builder.setCode(CollectRep.Code.FAIL); + builder.setMsg(e.getMessage()); + return; + } + ConnectSession connectSession = null; + try { + connectSession = getRedfishConnectSession(metrics.getRedfish()); + } catch (Exception e) { + log.error("Redfish session create error: {}", e.getMessage()); + builder.setCode(CollectRep.Code.FAIL); + builder.setMsg(e.getMessage()); + return; + } + List resourcesUri = getResourcesUri(metrics, connectSession); + if (resourcesUri == null || resourcesUri.isEmpty()) { + builder.setCode(CollectRep.Code.FAIL); + builder.setMsg("Get redfish resources uri error"); + return; + } + for (String uri : resourcesUri) { + String resp = null; + try { + resp = connectSession.getRedfishResource(uri); + } catch (Exception e) { + log.error("Get redfish {} detail resource error: {}", uri, e.getMessage()); + continue; + } + parseRedfishResource(builder, resp, metrics); + } + } + + private ConnectSession getRedfishConnectSession(RedfishProtocol redfishProtocol) throws Exception { + CacheIdentifier identifier = CacheIdentifier.builder() + .ip(redfishProtocol.getHost()) + .port(redfishProtocol.getPort()) + .password(redfishProtocol.getPassword()) + .username(redfishProtocol.getUsername()) + .build(); + ConnectSession redfishConnectSession = null; + Optional cacheOption = ConnectionCommonCache.getInstance().getCache(identifier, true); + if (cacheOption.isPresent()) { + RedfishConnect redfishConnect = (RedfishConnect) cacheOption.get(); + redfishConnectSession = redfishConnect.getConnection(); + if (redfishConnectSession == null || !redfishConnectSession.isOpen()) { + redfishConnectSession = null; + ConnectionCommonCache.getInstance().removeCache(identifier); + } + } + if (redfishConnectSession != null) { + return redfishConnectSession; + } + RedfishClient redfishClient = RedfishClient.create(redfishProtocol); + redfishConnectSession = redfishClient.connect(); + ConnectionCommonCache.getInstance().addCache(identifier, new RedfishConnect(redfishConnectSession)); + return redfishConnectSession; + } + + @Override + public String supportProtocol() { + return DispatchConstants.PROTOCOL_REDFISH; + } + + private void validateParams(Metrics metrics) throws Exception { + if (metrics == null || metrics.getRedfish() == null) { + throw new Exception("Redfish collect must has redfish params"); + } + RedfishProtocol redfishProtocol = metrics.getRedfish(); + Assert.hasText(redfishProtocol.getHost(), "Redfish Protocol host is required."); + Assert.hasText(redfishProtocol.getPort(), "Redfish Protocol port is required."); + Assert.hasText(redfishProtocol.getUsername(), "Redfish Protocol username is required."); + Assert.hasText(redfishProtocol.getPassword(), "Redfish Protocol password is required."); + } + + + private List getResourcesUri(Metrics metrics, ConnectSession connectSession) { + String name = metrics.getName(); + String collectionSchema = metrics.getRedfish().getSchema(); + String schema = (collectionSchema != null) ? collectionSchema : RedfishCollectionSchema.getSchema(name); + if (!StringUtils.hasText(schema)) { + return null; + } + String pattern = "\\{\\w+\\}"; + Pattern r = Pattern.compile(pattern); + String[] fragment = r.split(schema); + List res = new ArrayList<>(); + for (String value : fragment) { + List temp = new ArrayList<>(); + if (res.isEmpty()) { + res.add(value); + } else { + res = res.stream().map(s -> s + value).collect(Collectors.toList()); + } + + for (String s : res) { + List t = getCollectionResource(s, connectSession); + temp.addAll(t); + } + res.clear(); + res.addAll(temp); + } + return res; + } + + private List parseCollectionResource(String resp) { + if (!StringUtils.hasText(resp)) { + return Collections.emptyList(); + } + String resourceIdPath = "$.Members[*].['@odata.id']"; + List resourceIds = JsonPathParser.parseContentWithJsonPath(resp, resourceIdPath); + List res = resourceIds.stream().filter(Objects::nonNull).map(String::valueOf).toList(); + return res; + } + + private List getCollectionResource(String uri, ConnectSession connectSession) { + String resp = null; + try { + resp = connectSession.getRedfishResource(uri); + } catch (Exception e) { + log.error("Get redfish {} collection resource error: {}", uri, e.getMessage()); + return Collections.emptyList(); + } + return parseCollectionResource(resp); + } + + private void parseRedfishResource(CollectRep.MetricsData.Builder builder, String resp, Metrics metrics) { + if (!StringUtils.hasText(resp)) { + return; + } + List aliasFields = metrics.getAliasFields(); + CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder(); + for (String alias : aliasFields) { + List res = JsonPathParser.parseContentWithJsonPath(resp, alias); + if (res != null && !res.isEmpty()) { + Object value = res.get(0); + valueRowBuilder.addColumns(value == null ? CommonConstants.NULL_VALUE : String.valueOf(value)); + } else { + valueRowBuilder.addColumns(CommonConstants.NULL_VALUE); + } + } + builder.addValues(valueRowBuilder.build()); + } +} diff --git a/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishCollectionSchema.java b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishCollectionSchema.java new file mode 100644 index 00000000000..5d0740e87c7 --- /dev/null +++ b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishCollectionSchema.java @@ -0,0 +1,37 @@ +/* + * 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.hertzbeat.collector.collect.redfish; + +import java.util.Map; + +/** + * redfish collection schema + */ +public class RedfishCollectionSchema { + + private static final Map schemaMap = Map.of( + "Chassis", "/redfish/v1/Chassis", + "Fan", "/redfish/v1/Chassis/{ChassisId}/ThermalSubsystem/Fans", + "Battery", "/redfish/v1/Chassis/{ChassisId}/PowerSubsystem/Batteries", + "PowerSupply", "/redfish/v1/Chassis/{ChassisId}/PowerSubsystem/PowerSupplies"); + + public static String getSchema(String key) { + return schemaMap.get(key); + } + +} diff --git a/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishConnectSession.java b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishConnectSession.java new file mode 100644 index 00000000000..4cec1c77588 --- /dev/null +++ b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/RedfishConnectSession.java @@ -0,0 +1,100 @@ +/* + * 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.hertzbeat.collector.collect.redfish; + +import java.nio.charset.StandardCharsets; +import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient; +import org.apache.hertzbeat.common.constants.CollectorConstants; +import org.apache.hertzbeat.common.util.IpDomainUtil; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.util.EntityUtils; + + +/** + * Redfish connect session + */ +public class RedfishConnectSession implements ConnectSession { + + private final Session session; + + private volatile boolean active = true; + + + public RedfishConnectSession(Session session) { + this.session = session; + } + + @Override + public boolean isOpen() { + return this.active; + } + + @Override + public void close() throws Exception { + this.active = false; + String url = RedfishClient.REDFISH_SESSION_SERVICE + session.location(); + HttpDelete httpDelete = new HttpDelete(url); + httpDelete.setHeader("X-Auth-Token", session.token()); + httpDelete.setHeader("Location", session.location()); + try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(httpDelete)) { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + throw new Exception("Http State code: " + statusCode); + } + } catch (Exception e) { + throw new Exception("Redfish session close error:" + e.getMessage()); + } finally { + httpDelete.abort(); + } + } + + @Override + public String getRedfishResource(String uri) throws Exception { + if (uri.endsWith("/")) { + uri = uri.substring(0, uri.length() - 1); + } + String url = null; + if (IpDomainUtil.isHasSchema(this.session.host())) { + url = this.session.host() + ":" + this.session.port() + uri; + } else { + String ipAddressType = IpDomainUtil.checkIpAddressType(this.session.host()); + String baseUri = CollectorConstants.IPV6.equals(ipAddressType) + ? String.format("[%s]:%s", this.session.host(), this.session.port() + uri) + : String.format("%s:%s", this.session.host(), this.session.port() + uri); + url = CollectorConstants.HTTPS_HEADER + baseUri; + } + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("X-Auth-Token", session.token()); + httpGet.setHeader("Location", session.location()); + try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(httpGet)) { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + throw new Exception("Http State code: " + statusCode); + } + String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + return resp; + } catch (Exception e) { + throw new Exception("Redfish session get resource error:" + e.getMessage()); + } finally { + httpGet.abort(); + } + } +} diff --git a/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/Session.java b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/Session.java new file mode 100644 index 00000000000..25d5c2e34dc --- /dev/null +++ b/collector/src/main/java/org/apache/hertzbeat/collector/collect/redfish/Session.java @@ -0,0 +1,23 @@ +/* + * 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.hertzbeat.collector.collect.redfish; + +/** + * Redfish session + */ +public record Session(String token, String location, String host, Integer port) {} diff --git a/collector/src/main/java/org/apache/hertzbeat/collector/dispatch/DispatchConstants.java b/collector/src/main/java/org/apache/hertzbeat/collector/dispatch/DispatchConstants.java index 81357b56973..b5beeeeb03f 100644 --- a/collector/src/main/java/org/apache/hertzbeat/collector/dispatch/DispatchConstants.java +++ b/collector/src/main/java/org/apache/hertzbeat/collector/dispatch/DispatchConstants.java @@ -119,6 +119,10 @@ public interface DispatchConstants { * protocol http_sd */ String PROTOCOL_HTTP_SD = "httpsd"; + /** + * protocol redfish + */ + String PROTOCOL_REDFISH = "redfish"; // Protocol type related - end // 协议类型相关 - end // diff --git a/collector/src/main/resources/META-INF/services/org.apache.hertzbeat.collector.collect.AbstractCollect b/collector/src/main/resources/META-INF/services/org.apache.hertzbeat.collector.collect.AbstractCollect index b4a4b61296a..b2d5149bf62 100644 --- a/collector/src/main/resources/META-INF/services/org.apache.hertzbeat.collector.collect.AbstractCollect +++ b/collector/src/main/resources/META-INF/services/org.apache.hertzbeat.collector.collect.AbstractCollect @@ -21,3 +21,4 @@ org.apache.hertzbeat.collector.collect.memcached.MemcachedCollectImpl org.apache.hertzbeat.collector.collect.nebulagraph.NebulaGraphCollectImpl org.apache.hertzbeat.collector.collect.pop3.Pop3CollectImpl org.apache.hertzbeat.collector.collect.httpsd.HttpsdImpl +org.apache.hertzbeat.collector.collect.redfish.RedfishCollectImpl diff --git a/collector/src/test/java/org/apache/hertzbeat/collector/collect/redfish/RedfishCollectImplTest.java b/collector/src/test/java/org/apache/hertzbeat/collector/collect/redfish/RedfishCollectImplTest.java new file mode 100644 index 00000000000..26ffe9f340a --- /dev/null +++ b/collector/src/test/java/org/apache/hertzbeat/collector/collect/redfish/RedfishCollectImplTest.java @@ -0,0 +1,164 @@ +/* + * 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.hertzbeat.collector.collect.redfish; + +import org.apache.hertzbeat.common.entity.job.Metrics; +import org.apache.hertzbeat.common.entity.job.protocol.RedfishProtocol; +import org.apache.hertzbeat.common.entity.message.CollectRep; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MockitoExtension.class) +public class RedfishCollectImplTest { + @Mock + private RedfishProtocol redfishProtocol; + + @Mock + private ConnectSession redfishConnectSession; + + @Mock + private RedfishClient redfishClient; + + @InjectMocks + private RedfishCollectImpl redfishCollect; + + @BeforeEach + void setUp() { + redfishProtocol = RedfishProtocol.builder() + .host("https://127.0.0.1") + .port("5000") + .username("Administrator") + .password("Password") + .timeout("5000") + .build(); + } + + @Test + void collect() { + CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder(); + List aliasField = new ArrayList<>(); + aliasField.add("$.Id"); + Metrics metrics = new Metrics(); + metrics.setRedfish(redfishProtocol); + metrics.setAliasFields(aliasField); + metrics.setName("Chassis"); + RedfishClient.create(redfishProtocol); + redfishCollect.collect(builder, 1L, "test", metrics); + } + + @Test + void mockCollect() throws Exception { + CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder(); + List aliasField = new ArrayList<>(); + aliasField.add("$.['@odata.id']"); + redfishProtocol.setSchema("/redfish/v1/Chassis/{ChassisId}/PowerSubsystem/PowerSupplies"); + Metrics metrics = new Metrics(); + metrics.setRedfish(redfishProtocol); + metrics.setAliasFields(aliasField); + metrics.setName("PowerSupply"); + String Chassis = "{\n" + + " \"@odata.type\": \"#ChassisCollection.ChassisCollection\",\n" + + " \"Name\": \"Chassis Collection\",\n" + + " \"Members@odata.count\": 2,\n" + + " \"Members\": [\n" + + " {\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/1U\"\n" + + " },\n" + + " {\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/2U\"\n" + + " }\n" + + " ]\n" + + "}"; + String powerSupplies1U = "{\n" + + " \"@odata.type\": \"#PowerSupplyCollection.PowerSupplyCollection\",\n" + + " \"Name\": \"Power Supply Collection\",\n" + + " \"Members@odata.count\": 2,\n" + + " \"Members\": [\n" + + " {\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1\"\n" + + " },\n" + + " {\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay2\"\n" + + " }\n" + + " ]\n" + + "}"; + String powerSupplies2U = "{\n" + + " \"@odata.type\": \"#PowerSupplyCollection.PowerSupplyCollection\",\n" + + " \"Name\": \"Power Supply Collection\",\n" + + " \"Members@odata.count\": 2,\n" + + " \"Members\": [\n" + + " {\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/2U/PowerSubsystem/PowerSupplies/Bay1\"\n" + + " },\n" + + " {\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/2U/PowerSubsystem/PowerSupplies/Bay2\"\n" + + " }\n" + + " ]\n" + + "}"; + String bay1U1 = "{\n" + + " \"@odata.type\": \"#PowerSupply.v1_5_3.PowerSupply\",\n" + + " \"Id\": \"Bay1\",\n" + + " \"Name\": \"Power Supply Bay 1\",\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1\"\n" + + "}"; + String bay2U1 = "{\n" + + " \"@odata.type\": \"#PowerSupply.v1_5_3.PowerSupply\",\n" + + " \"Id\": \"Bay2\",\n" + + " \"Name\": \"Power Supply Bay 2\",\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay2\"\n" + + "}"; + String bay1U2 = "{\n" + + " \"@odata.type\": \"#PowerSupply.v1_5_3.PowerSupply\",\n" + + " \"Id\": \"Bay1\",\n" + + " \"Name\": \"Power Supply Bay 1\",\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/2U/PowerSubsystem/PowerSupplies/Bay1\"\n" + + "}"; + String bay2U2 = "{\n" + + " \"@odata.type\": \"#PowerSupply.v1_5_3.PowerSupply\",\n" + + " \"Id\": \"Bay2\",\n" + + " \"Name\": \"Power Supply Bay 2\",\n" + + " \"@odata.id\": \"/redfish/v1/Chassis/2U/PowerSubsystem/PowerSupplies/Bay2\"\n" + + "}"; + Mockito.when(redfishConnectSession.getRedfishResource("/redfish/v1/Chassis/")).thenReturn(Chassis); + Mockito.when(redfishConnectSession.getRedfishResource("/redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies")).thenReturn(powerSupplies1U); + Mockito.when(redfishConnectSession.getRedfishResource("/redfish/v1/Chassis/2U/PowerSubsystem/PowerSupplies")).thenReturn(powerSupplies2U); + Mockito.when(redfishConnectSession.getRedfishResource("/redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1")).thenReturn(bay1U1); + Mockito.when(redfishConnectSession.getRedfishResource("/redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay2")).thenReturn(bay2U1); + Mockito.when(redfishConnectSession.getRedfishResource("/redfish/v1/Chassis/2U/PowerSubsystem/PowerSupplies/Bay1")).thenReturn(bay1U2); + Mockito.when(redfishConnectSession.getRedfishResource("/redfish/v1/Chassis/2U/PowerSubsystem/PowerSupplies/Bay2")).thenReturn(bay2U2); + MockedStatic clientMockedStatic = Mockito.mockStatic(RedfishClient.class); + clientMockedStatic.when(() -> RedfishClient.create(redfishProtocol)).thenReturn(redfishClient); + Mockito.when(redfishClient.connect()).thenReturn(redfishConnectSession); + redfishCollect.collect(builder, 1L, "test", metrics); + assertEquals("/redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1", builder.getValues(0).getColumns(0)); + assertEquals("/redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay2", builder.getValues(1).getColumns(0)); + assertEquals("/redfish/v1/Chassis/2U/PowerSubsystem/PowerSupplies/Bay1", builder.getValues(2).getColumns(0)); + assertEquals("/redfish/v1/Chassis/2U/PowerSubsystem/PowerSupplies/Bay2", builder.getValues(3).getColumns(0)); + } +} diff --git a/common/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java b/common/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java index 1e517fd9001..67349ea7b60 100644 --- a/common/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java +++ b/common/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java @@ -43,6 +43,7 @@ import org.apache.hertzbeat.common.entity.job.protocol.Pop3Protocol; import org.apache.hertzbeat.common.entity.job.protocol.PrometheusProtocol; import org.apache.hertzbeat.common.entity.job.protocol.PushProtocol; +import org.apache.hertzbeat.common.entity.job.protocol.RedfishProtocol; import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol; import org.apache.hertzbeat.common.entity.job.protocol.RocketmqProtocol; import org.apache.hertzbeat.common.entity.job.protocol.SmtpProtocol; @@ -205,7 +206,10 @@ public class Metrics { * Monitoring configuration information using the public http_sd protocol */ private HttpsdProtocol httpsd; - + /** + * Monitoring configuration information using the public redfish protocol + */ + private RedfishProtocol redfish; /** * collector use - Temporarily store subTask metrics response data */ diff --git a/common/src/main/java/org/apache/hertzbeat/common/entity/job/protocol/RedfishProtocol.java b/common/src/main/java/org/apache/hertzbeat/common/entity/job/protocol/RedfishProtocol.java new file mode 100644 index 00000000000..b8042e12f29 --- /dev/null +++ b/common/src/main/java/org/apache/hertzbeat/common/entity/job/protocol/RedfishProtocol.java @@ -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.hertzbeat.common.entity.job.protocol; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Redfish Protocol + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RedfishProtocol { + /** + * IP ADDRESS OR DOMAIN NAME OF THE PEER HOST + */ + private String host; + + /** + * Peer host port + */ + private String port; + + /** + * UserName + */ + private String username; + + /** + * Password + */ + private String password; + + /** + * TIME OUT PERIOD + */ + private String timeout; + + /** + * Redfish Resource Name and Corresponding Collection URI + */ + private String schema; +} diff --git a/manager/src/main/resources/define/app-redfish.yml b/manager/src/main/resources/define/app-redfish.yml new file mode 100644 index 00000000000..af3e4d3a7eb --- /dev/null +++ b/manager/src/main/resources/define/app-redfish.yml @@ -0,0 +1,288 @@ +# 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. + +# The monitoring type category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring +# 监控类型所属类别:service-应用服务 program-应用程序 db-数据库 custom-自定义 os-操作系统 bigdata-大数据 mid-中间件 webserver-web服务器 cache-缓存 cn-云原生 network-网络监控等等 +category: os +# The monitoring type eg: linux windows tomcat mysql aws... +# 监控类型 eg: linux windows tomcat mysql aws... +app: redfish +# The monitoring i18n name +# 监控类型国际化名称 +name: + zh-CN: Redfish + en-US: Redfish +# The description and help of this monitoring type +# 监控类型的帮助描述信息 +help: + zh-CN: Hertzbeat 对支持 Redfish 服务的服务器进行测量监控。
您可以点击 “新建 Redfish” 并进行配置,或者选择“更多操作”,导入已有配置。 + en-US: Hertzbeat monitoring servers supporting Redfish services. You could click the "New Redfish" button and proceed with the configuration or import an existing setup through the "More Actions" menu. + zh-TW: Hertzbeat 對支援 Redfish 服務的伺服器進行測量監控。
您可以點擊“Redfish”並進行配寘,或者選擇“更多操作”,導入已有配寘。 +helpLink: + zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/redfish + en-US: https://hertzbeat.apache.org/docs/help/redfish +# 监控所需输入参数定义(根据定义渲染页面UI) +# Input params define for monitoring(render web ui by the definition) +params: + # field-param field key + # field-变量字段标识符 + - field: host + # name-param field display i18n name + # name-参数字段显示名称 + name: + zh-CN: 目标Host + en-US: Target Host + # type-param field type(most mapping the html input type) + # type-字段类型,样式(大部分映射input标签type属性) + type: host + # required-true or false + # required-是否是必输项 true-必填 false-可选 + required: true + # field-param field key + # field-变量字段标识符 + - field: port + # name-param field display i18n name + # name-参数字段显示名称 + name: + zh-CN: 端口 + en-US: Port + # type-param field type(most mapping the html input type) + # type-字段类型,样式(大部分映射input标签type属性) + type: number + # when type is number, range is required + # 当type为number时,用range表示范围 + range: '[0,65535]' + # required-true or false + # required-是否是必输项 true-必填 false-可选 + required: true + # default value + # 默认值 + defaultValue: 443 + # field-param field key + # field-变量字段标识符 + - field: timeout + # name-param field display i18n name + # name-参数字段显示名称 + name: + zh-CN: 查询超时时间 + en-US: Query Timeout + # type-param field type(most mapping the html input type) + # type-字段类型,样式(大部分映射input标签type属性) + type: number + # required-true or false + # required-是否是必输项 true-必填 false-可选 + required: false + # hide param-true or false + # 是否隐藏字段 true or false + hide: true + # default value + # 默认值 + defaultValue: 6000 + # field-param field key + # field-变量字段标识符 + - field: username + # name-param field display i18n name + # name-参数字段显示名称 + name: + zh-CN: 用户名 + en-US: Username + # type-param field type(most mapping the html input type) + # type-字段类型,样式(大部分映射input标签type属性) + type: text + # when type is text, use limit to limit string length + # 当type为text时,用limit表示字符串限制大小 + limit: 20 + # required-true or false + # required-是否是必输项 true-必填 false-可选 + required: false + # field-param field key + # field-变量字段标识符 + - field: password + # name-param field display i18n name + # name-参数字段显示名称 + name: + zh-CN: 密码 + en-US: Password + # type-param field type(most mapping the html input tag) + # type-字段类型,样式(大部分映射input标签type属性) + type: password + # required-true or false + # required-是否是必输项 true-必填 false-可选 + required: false +# collect metrics config list +# 采集指标配置列表 +metrics: + # metrics - cpu + # 监控指标 - cpu + - name: Chassis + # metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel + # priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue + # 指标采集调度优先级(0->127)->(优先级高->低) 优先级低的指标会等优先级高的指标采集完成后才会被调度, 相同优先级的指标会并行调度采集 + # 优先级为0的指标为可用性指标,即它会被首先调度,采集成功才会继续调度其它指标,采集失败则中断调度 + priority: 0 + # collect metrics content + # 具体监控指标列表 + fields: + # field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field + # field-指标名称, type-指标类型(0-number数字,1-string字符串), unit-指标单位('%','ms','MB'), label-是否是指标标签字段 + - field: id + type: 1 + i18n: + zh-CN: 机箱唯一标识符 + en-US: Chassis Unique Identifier + - field: name + type: 1 + i18n: + zh-CN: 机箱名称 + en-US: Chassis Name + - field: type + type: 1 + i18n: + zh-CN: 机箱类型 + en-US: Chasis Type + - field: state + type: 1 + i18n: + zh-CN: 机箱状态 + en-US: Chasis State + - field: health + type: 1 + i18n: + zh-CN: 机箱健康状态 + en-US: Chasis Health + # (optional)metrics field alias name, it is used as an alias field to map and convert the collected data and metrics field + # (可选)监控指标别名, 做为中间字段与采集数据字段和指标字段映射转换 + aliasFields: + - $.['@odata.id'] + - $.Name + - $.ChassisType + - $.Status.State + - $.Status.Health + # the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk + protocol: redfish + # the config content when protocol is redfish + redfish: + # redfish host: ipv4 ipv6 domain + host: ^_^host^_^ + # redfish port + port: ^_^port^_^ + # redfish username + username: ^_^username^_^ + # redfish password + password: ^_^password^_^ + # timeout unit:ms + timeout: ^_^timeout^_^ + + - name: Battery + priority: 1 + fields: + - field: id + type: 1 + i18n: + zh-CN: 电池唯一标识符 + en-US: Battery Unique Identifier + - field: name + type: 1 + i18n: + zh-CN: 电池名称 + en-US: Battery Name + - field: state + type: 1 + i18n: + zh-CN: 电池状态 + en-US: Battery State + - field: health + type: 1 + i18n: + zh-CN: 电池健康状态 + en-US: Battery Health + - field: charge_status + type: 1 + i18n: + zh-CN: 电池充电状态 + en-US: Battery Charge Status + aliasFields: + - $.['@odata.id'] + - $.Name + - $.Status.State + - $.Status.Health + - $.ChargeState + protocol: redfish + redfish: + # redfish host: ipv4 ipv6 domain + host: ^_^host^_^ + # redfish port + port: ^_^port^_^ + # redfish username + username: ^_^username^_^ + # redfish password + password: ^_^password^_^ + # timeout unit:ms + timeout: ^_^timeout^_^ + + - name: Fan + priority: 2 + fields: + - field: id + type: 1 + i18n: + zh-CN: 风扇唯一标识符 + en-US: Fan Unique Identifier + - field: name + type: 1 + i18n: + zh-CN: 风扇名称 + en-US: Fan Name + - field: state + type: 1 + i18n: + zh-CN: 风扇状态 + en-US: Fan State + - field: health + type: 1 + i18n: + zh-CN: 风扇健康状态 + en-US: Fan Health + - field: temperature + type: 0 + i18n: + zh-CN: 传感器温度 + en-US: Sensor Temperature + - field: speed + type: 0 + i18n: + zh-CN: 风扇转速 + en-US: Fan Speed + aliasFields: + - $.['@odata.id'] + - $.Name + - $.Status.State + - $.Status.Health + - $.SpeedPercent.Reading + - $.SpeedPercent.SpeedRPM + protocol: redfish + redfish: + # redfish host: ipv4 ipv6 domain + host: ^_^host^_^ + # redfish port + port: ^_^port^_^ + # redfish username + username: ^_^username^_^ + # redfish password + password: ^_^password^_^ + # timeout unit:ms + timeout: ^_^timeout^_^ + # redfish fan collection schema + schema: /redfish/v1/Chassis/{ChassisId}/ThermalSubsystem/Fans