Skip to content

Commit

Permalink
Added subscription support for OpenBMC Nvidia redfish implementations (
Browse files Browse the repository at this point in the history
…#54)

- Changed to use https when making subscriptions for OpenBMC
- Changed to check the length of the returned registryPrefixes
- Added a cache that holds the redfish implementation type for each endpoint

CASMHMS-6127
CASMHMS-6133
  • Loading branch information
shunr-hpe authored Feb 7, 2024
1 parent 32f28de commit 2e32669
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.28.0
2.29.0
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ Removed - for now removed features
Fixed - for any bug fixes
Security - in case of vulnerabilities
-->
## [2.29.0] - 2024-01-24

### Changed
- CASMHMS-6127 - Added support for creating subscriptions on OpenBMC Nodes

## [2.28.0] - 2024-01-12

### Changed
Expand Down
115 changes: 115 additions & 0 deletions cmd/hmcollector/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// MIT License
//
// (C) Copyright [2024] Hewlett Packard Enterprise Development LP
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

package main

import (
"sync"

"go.uber.org/zap"
)

type EndpointsCache struct {
lock sync.RWMutex
cache map[string]*EndpointCache
}

type RfType int

const (
CrayRfType RfType = iota
GigabyteRfType
HpeRfType
IntelRfType
OpenBmcRfType
UnknownRfType // The redfish type is not recognized
UnsetRfType // The cache has not been set
)

type EndpointCache struct {
LastDiscoveryAttempt string // The LastDiscoveryAttempt from hsm redfishEndpoints
Type RfType
}

func NewEndpointsCache() *EndpointsCache {
c := &EndpointsCache{}
c.lock = sync.RWMutex{}
c.cache = make(map[string]*EndpointCache)
return c
}

func (c *EndpointsCache) ReadRfType(endpointId string, lastDiscoveryAttempt string) RfType {
c.lock.RLock()
defer c.lock.RUnlock()

if v, found := c.cache[endpointId]; found {
if lastDiscoveryAttempt == v.LastDiscoveryAttempt {
return v.Type
}
// cache has a value, however, hsm has since rediscovered the redfish endpoint,
// and so return UnsetRfType as though there is no cached value.
logger.Info("Ignoring cached value because timestamp for last discovery attempt is new.",
zap.String("endpoint", endpointId),
zap.String("CachedTimestamp", v.LastDiscoveryAttempt),
zap.String("NewTimestamp", lastDiscoveryAttempt))
}
return UnsetRfType
}

func (c *EndpointsCache) WriteRfType(endpointId string, lastDiscoveryAttempt string, rfType RfType) {
c.lock.Lock()
defer c.lock.Unlock()

logger.Info("Write endpoint type to the cache",
zap.String("endpoint", endpointId),
zap.String("type", rfTypeToString(rfType)),
zap.String("timestamp", lastDiscoveryAttempt))

if _, found := c.cache[endpointId]; !found {
c.cache[endpointId] = &EndpointCache{}
}

endpointCache := c.cache[endpointId]
endpointCache.Type = rfType
endpointCache.LastDiscoveryAttempt = lastDiscoveryAttempt
}

func rfTypeToString(rfType RfType) string {
switch rfType {
case CrayRfType:
return "Cray"
case GigabyteRfType:
return "Gigabyte"
case HpeRfType:
return "HPE"
case IntelRfType:
return "Intel"
case OpenBmcRfType:
return "OpenBMC"
case UnknownRfType:
return "Unknown"
case UnsetRfType:
return "Unset"
default:
return "UnknownCase"
}
}
9 changes: 8 additions & 1 deletion cmd/hmcollector/hmcollector.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,14 @@ func doUpdateHSMEndpoints() {

// Make sure this is a new endpoint.
HSMEndpointsLock.Lock()
_, endpointIsKnown := HSMEndpoints[newEndpoint.ID]
e, endpointIsKnown := HSMEndpoints[newEndpoint.ID]
if endpointIsKnown {
// The credentials are in the endpoint object, therefore,
// only update the fields that need to stay up to date
e.DiscInfo.LastAttempt = newEndpoint.DiscInfo.LastAttempt
e.DiscInfo.LastStatus = newEndpoint.DiscInfo.LastStatus
e.DiscInfo.RedfishVersion = newEndpoint.DiscInfo.RedfishVersion
}
HSMEndpointsLock.Unlock()

if endpointIsKnown {
Expand Down
31 changes: 31 additions & 0 deletions cmd/hmcollector/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// MIT License
//
// (C) Copyright [2024] Hewlett Packard Enterprise Development LP
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

package main

var (
endpointsCache *EndpointsCache
)

func init() {
endpointsCache = NewEndpointsCache()
}
26 changes: 26 additions & 0 deletions cmd/hmcollector/polling.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,25 @@ func monitorPollingEndpoints() {
model = chassis.Model
}

// OpenBMC NVIDIA
// OpenBMC needs to be checked after HPE, because, some implementations
// of OpenBMC mistakenly return success for /redfish/v1/Chassis/1
fullURL = "https://" + endpoint.FQDN + "/redfish/v1/Chassis/BMC_0"
payloadBytes, statusCode, err = doHTTPAction(endpoint, http.MethodGet, fullURL, nil)
if err == nil && statusCode == http.StatusOK {
var chassis hmcollector.Chassis
decodeErr := json.Unmarshal(payloadBytes, &chassis)
if decodeErr != nil {
logger.Error("Failed to decode model information, will not poll endpoint.",
zap.Any("endpoint", endpoint),
zap.Error(err))

return
}

model = chassis.Model
}

// At this point we have what we need, use process of elimination.
if strings.HasPrefix(model, "R272") ||
strings.HasPrefix(model, "R282") ||
Expand All @@ -373,6 +392,13 @@ func monitorPollingEndpoints() {
Endpoint: endpoint,
RiverCollector: hpeCollector,
}
} else if isOpenBmcModel(model) {
logger.Info("Found OpenBMC endpoint eligible for polling.",
zap.Any("endpoint", endpoint))
newEndpoint = &EndpointWithCollector{
Endpoint: endpoint,
RiverCollector: hpeCollector,
}
} else {
// We have to ignore it if we can't determine what it is.
logger.Warn("Unable to determine model number from endpoint, "+
Expand Down
112 changes: 112 additions & 0 deletions cmd/hmcollector/rf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// MIT License
//
// (C) Copyright [2024] Hewlett Packard Enterprise Development LP
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

package main

import (
"encoding/json"
"net/http"
"strings"

rf "github.com/Cray-HPE/hms-smd/pkg/redfish"
"github.com/Cray-HPE/hms-xname/xnametypes"
"go.uber.org/zap"
)

const (
CrayRedfishPath = "/redfish/v1/Chassis/Enclosure"
GigabyteRedfishPath = "/redfish/v1/Chassis/Self"
HpeRedfishPath = "/redfish/v1/Chassis/1"
IntelRedfishPath = "/redfish/v1/Chassis/RackMount"
OpenBmcRedfishPath = "/redfish/v1/Chassis/BMC_0"
)

type rfChassis struct {
Members []rfChassisMembers
}

type rfChassisMembers struct {
ID string `json:"@odata.id"`
}

func httpGetRedfishType(endpoint *rf.RedfishEPDescription) RfType {
if xnametypes.GetHMSType(endpoint.ID) != xnametypes.NodeBMC {
// Skip anything that is not a NodeBMC
// GET /redfish/v1/Chassis will fail for other types of endpoints
return UnknownRfType
}

URL := "https://" + endpoint.FQDN + "/redfish/v1/Chassis"
payloadBytes, statusCode, err := doHTTPAction(endpoint, http.MethodGet, URL, nil)
if err != nil {
logger.Error("Could not determine the redfish implementation because get /redfish/v1/Chassis failed.",
zap.Any("endpoint", endpoint),
zap.Error(err))
return UnsetRfType
}
if statusCode != 200 {
logger.Error("Could not determine the redfish implementation because get /redfish/v1/Chassis failed.",
zap.Any("endpoint", endpoint),
zap.Int("statusCode", statusCode))
return UnsetRfType

}
var chassis rfChassis
decodeErr := json.Unmarshal(payloadBytes, &chassis)
if decodeErr != nil {
logger.Error("Could not determine the redfish implementation, failed to parse response.",
zap.Any("endpoint", endpoint),
zap.Error(err))
return UnknownRfType
}

for _, member := range chassis.Members {
switch {
case strings.EqualFold(member.ID, CrayRedfishPath):
return CrayRfType
case strings.EqualFold(member.ID, GigabyteRedfishPath):
return GigabyteRfType
case strings.EqualFold(member.ID, HpeRedfishPath):
return HpeRfType
case strings.EqualFold(member.ID, IntelRedfishPath):
return IntelRfType
case strings.EqualFold(member.ID, OpenBmcRedfishPath):
return OpenBmcRfType
}
}
return UnknownRfType
}

func isOpenBmcModel(model string) bool {
// Paradise has the model P4352/P4353
return strings.Contains(model, "P4352") ||
strings.Contains(model, "P4353")
}

func GetRedfishType(endpoint *rf.RedfishEPDescription) RfType {
rfType := endpointsCache.ReadRfType(endpoint.ID, endpoint.DiscInfo.LastAttempt)
if rfType == UnsetRfType {
rfType = httpGetRedfishType(endpoint)
endpointsCache.WriteRfType(endpoint.ID, endpoint.DiscInfo.LastAttempt, rfType)
}
return rfType
}
19 changes: 17 additions & 2 deletions cmd/hmcollector/rf_subscriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ func checkILO(endpoint *rf.RedfishEPDescription) bool {
return err == nil && statusCode < 300
}

func checkOpenBmc(endpoint *rf.RedfishEPDescription) bool {
rfType := GetRedfishType(endpoint)
return rfType == OpenBmcRfType
}

func getDestination(endpoint *rf.RedfishEPDescription) string {
destination, err := url.Parse(*restURL)
if err != nil {
Expand All @@ -59,6 +64,9 @@ func getDestination(endpoint *rf.RedfishEPDescription) string {
if destination.Scheme == "http" && checkILO(endpoint) {
// iLO requires https
destination.Scheme = "https"
} else if destination.Scheme == "http" && checkOpenBmc(endpoint) {
// Open BMC requires https
destination.Scheme = "https"
}
if destination.Port() == "" && *restPort != 80 && *restPort != 443 {
destination.Host += ":" + strconv.Itoa(*restPort)
Expand Down Expand Up @@ -246,7 +254,8 @@ func isDupRFSubscription(endpoint *rf.RedfishEPDescription, registryPrefixes []s
if eventSub.Destination == getDestination(endpoint) {
// Matches this destination, make sure the registry prefix is one we created.
match := false
if registryPrefixes == nil && eventSub.RegistryPrefixes == nil {
if (registryPrefixes == nil || len(registryPrefixes) == 0) &&
(eventSub.RegistryPrefixes == nil || len(eventSub.RegistryPrefixes) == 0) {
match = true
} else if registryPrefixes != nil {
hasAllRegistryPrefixes := true
Expand Down Expand Up @@ -448,7 +457,10 @@ func rfSubscribe(pendingRFSubscriptions <-chan hmcollector.RFSub) {
registryPrefixGroups := [][]string{nil}
if *rfStreamingEnabled {
// Only create the streaming subscription if enabled.
registryPrefixGroups = append(registryPrefixGroups, []string{"CrayTelemetry"})
rfType := GetRedfishType(sub.Endpoint)
if rfType != OpenBmcRfType {
registryPrefixGroups = append(registryPrefixGroups, []string{"CrayTelemetry"})
}
}

if *pruneOldSubscriptions {
Expand Down Expand Up @@ -568,6 +580,9 @@ func doRFSubscribe() {
}
pendingRFSubscriptions <- endpoints[newEndpoint.ID]
} else if subCheckCnt%subCheckFreq == 0 {
currentEndpoint := endpoints[newEndpoint.ID]
currentEndpoint.Endpoint = newEndpoint
endpoints[newEndpoint.ID] = currentEndpoint
// Endpoint has a subscription, check that sub is still there and correct.
// NOTE: Don't need to do this at the same frequency as picking up new additions so as to not
// hammer the endpoint.
Expand Down

0 comments on commit 2e32669

Please sign in to comment.