Skip to content

Commit

Permalink
[feature] Support Redfish protocol to monitoring server (#1867)
Browse files Browse the repository at this point in the history
Co-authored-by: tomsun28 <tomsun28@outlook.com>
  • Loading branch information
gjjjj0101 and tomsun28 authored Apr 28, 2024
1 parent fd56652 commit d3eff79
Show file tree
Hide file tree
Showing 13 changed files with 1,064 additions and 1 deletion.
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.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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<String> 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<Object> 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<String> 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<String> res = new ArrayList<>();
for (String value : fragment) {
List<String> 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<String> t = getCollectionResource(s, connectSession);
temp.addAll(t);
}
res.clear();
res.addAll(temp);
}
return res;
}

private List<String> parseCollectionResource(String resp) {
if (!StringUtils.hasText(resp)) {
return Collections.emptyList();
}
String resourceIdPath = "$.Members[*].['@odata.id']";
List<Object> resourceIds = JsonPathParser.parseContentWithJsonPath(resp, resourceIdPath);
List<String> res = resourceIds.stream().filter(Objects::nonNull).map(String::valueOf).toList();
return res;
}

private List<String> 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<String> aliasFields = metrics.getAliasFields();
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String alias : aliasFields) {
List<Object> 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());
}
}
Loading

0 comments on commit d3eff79

Please sign in to comment.