Documentation
¶
Overview ¶
This package contains shared utility functions for HMS Go code.
HMS Type and Component Definitions ¶
These are functions that define common types that are used to describe HMS component data that represents system components and valid values for that data.
HMS Errors and RFC 7807 ProblemDetails RFC 7807 ProblemDetails" aria-label="Go to HMS Errors and RFC 7807 ProblemDetails">¶
These are common methods for defining a custom HMS error type and producing RFC 7807-compliant ProblemDetails payloads for reporting problems that occur during HMS API calls.
Index ¶
- Constants
- Variables
- func GetHMSArchList() []string
- func GetHMSClassList() []string
- func GetHMSFlagList() []string
- func GetHMSNetTypeList() []string
- func GetHMSRoleList() []string
- func GetHMSStateList() []string
- func GetHMSSubRoleList() []string
- func GetServiceInstanceName() (string, error)
- func GetValidStartStateWForce(afterState string, force bool) (beforeStates []string, err error)
- func GetValidStartStates(afterState string) (beforeStates []string, ok bool)
- func InitTypes(configpath string) error
- func IsAlphaNum(s string) bool
- func IsHMSError(err error) bool
- func IsHMSErrorClass(err error, class string) bool
- func IsHMSErrorClassIgnCase(err error, class string) bool
- func IsNumeric(s string) bool
- func IsPostBootState(stateStr string) bool
- func RemoveLeadingZeros(s string) string
- func SendProblemDetails(w http.ResponseWriter, p *ProblemDetails, status int) error
- func SendProblemDetailsGeneric(w http.ResponseWriter, status int, msg string) error
- func SetHTTPUserAgent(req *http.Request, instName string)
- func VerifyNormalizeArch(archStr string) string
- func VerifyNormalizeClass(classStr string) string
- func VerifyNormalizeFlag(flagStr string) string
- func VerifyNormalizeFlagOK(flag string) string
- func VerifyNormalizeNetType(netTypeStr string) string
- func VerifyNormalizeRole(roleStr string) string
- func VerifyNormalizeState(stateStr string) string
- func VerifyNormalizeSubRole(subRoleStr string) string
- type Auth
- type Component
- type ComponentArray
- type HMSArch
- type HMSClass
- type HMSError
- func (e *HMSError) AddProblem(p *ProblemDetails)
- func (e *HMSError) Error() string
- func (e *HMSError) GetProblem() *ProblemDetails
- func (e *HMSError) IsClass(class string) bool
- func (e *HMSError) IsClassIgnCase(class string) bool
- func (e *HMSError) NewChild(msg string) *HMSError
- func (e *HMSError) NewChildWithProblem(msg, instance string) *HMSError
- type HMSFlag
- type HMSNetType
- type HMSRole
- type HMSState
- type HMSSubRole
- type HTTPRequest
- type Job
- type JobStatus
- type JobType
- type ProblemDetails
- type Worker
- type WorkerPool
Constants ¶
const HMSErrorUnsetDefault = "no error message or class set"
const ProblemDetailContentType = "application/problem+json"
const ProblemDetailsHTTPStatusType = "about:blank"
const USERAGENT = "User-Agent"
Variables ¶
var ErrHMSNeedForce = e.NewChild("operation not allowed and not forced.")
var ErrHMSStateInvalid = e.NewChild("was not a valid HMS state")
var ErrHMSStateUnsupported = e.NewChild("HMSState value not supported for this operation")
var ErrHMSTypeInvalid = e.NewChild("got HMSTypeInvalid instead of valid type")
var ErrHMSTypeUnsupported = e.NewChild("HMSType value not supported for this operation") // TODO should this be in base?
var JStatString = map[JobStatus]string{ JSTAT_DEFAULT: "JSTAT_DEFAULT", JSTAT_QUEUED: "JSTAT_QUEUED", JSTAT_PROCESSING: "JSTAT_PROCESSING", JSTAT_COMPLETE: "JSTAT_COMPLETE", JSTAT_CANCELLED: "JSTAT_CANCELLED", JSTAT_ERROR: "JSTAT_ERROR", JSTAT_MAX: "JSTAT_MAX", }
Functions ¶
func GetHMSStateList ¶
func GetHMSStateList() []string
func GetServiceInstanceName ¶
func GetValidStartStateWForce ¶
Same as above, but with force flag. If not found, returns ErrHMSStateInvalid. If can only be forced, and force = false, error will be ErrHMSNeedForce. Otherwise list of starting states. If force = true and no errors, an empty array means no restrictions.
func GetValidStartStates ¶
If ok == true, beforeStates contain valid current states a component can be in if it is being transitioned to afterState without being forced (either because it is a bad idea, or the state should only be set by HSM and not by other software). An empty array means 'None without force=true
If ok == false, afterState matched no valid HMS State (case insensitive)
func IsAlphaNum ¶
Returns true if s is alphanumeric only (only letters and numbers, no punctuation or spaces.
func IsHMSError ¶
See if an error (some thing implementing Error() interface is an HMSError
Returns 'true' if err is HMSError Returns 'false' if err is something else that implements Error()
func IsHMSErrorClass ¶
Returns false if 'err' is not an HMSError, or, if it is, if 'class' doesn't match the HMSError's Class field.
func IsHMSErrorClassIgnCase ¶
Returns false if 'err' is not an HMSError, or, if it is, if 'class' doesn't match the HMSError's Class field (case insensitive).
func IsNumeric ¶
Returns true if s is numeric only (only numbers, no letters, punctuation or spaces.
func IsPostBootState ¶
Check to see if the state is above on (on is the highest we will get from Redfish, so these are state set by higher software layers)
func RemoveLeadingZeros ¶
Remove leading zeros, i.e. for each run of numbers, trim off leading zeros so each run starts with either non-zero, or is a single zero.
func SendProblemDetails ¶
func SendProblemDetails(w http.ResponseWriter, p *ProblemDetails, status int) error
Send specially-coded RFC7807 Problem Details response. A set ProblemDetails struct is expected ('p') and should always contain at least Type to conform to the spec (we will always print the Type field, but it would contain the empty string if unset which isn't technically valid for the spec.
The HTTP status code in the header is set to the 'status' arg if it is NON-zero. If 'status' = 0, then 'p'.Status will be used. If that is unset (i.e. 0) also, we will use a default error code of 400.
For example:
p := base.NewProblemDetails( "https://example.com/probs/MyProblem", "My Problem's Title", "Detail for this problem", "/instances/myprob/1", http.StatusConflict) // 409 ... if err != nil { base.SendProblemDetails(w, p, 0) return }
...will include the following in the response header:
HTTP/1.1 409 Conflict Content-Type: application/problem+json
With the following body:
{ "Type": "https://example.com/probs/MyProblem", "Title": "My Problem's Title", "Detail": "Detail for this problem", "Instance": "/instances/myprob/1", "Status": 409 }
func SendProblemDetailsGeneric ¶
func SendProblemDetailsGeneric(w http.ResponseWriter, status int, msg string) error
Generate and then send a generic RFC 7807 problem response based on a HTTP status code ('status') and a message string ('msg'). This generates a generic title and type based on the HTTP 'status' code. The ProblemDetails "Detail" field is filled in using 'msg'.
If you are already returning just a simple error message and want an easy way to convert this into a basic (but valid) RFC7807 ProblemDetails, this is almost a drop-in replacement.
// If request was for a component that is not in DB, send RFC 7807 // ProblemDetails and exit HTTP handler err := GetComponentFromDB(component_from_query) if err != nil { base.SendProblemDetailsGeneric(w, http.StatusNotFound, "No such component") return }
The above will include the following in the response header:
HTTP/1.1 404 Not Found Content-Type: application/problem+json
With the following body:
{ "Type": "about:blank", "Title": "Not Found", "Detail": "No such component", "Status": 404 }
func SetHTTPUserAgent ¶
func VerifyNormalizeArch ¶
Returns the given arch string (adjusting any capitalization differences), if a valid arch was given. Else, return the empty string.
func VerifyNormalizeClass ¶
Returns the given class string (adjusting any capitalization differences), if a valid class was given. Else, return the empty string.
func VerifyNormalizeFlag ¶
Returns the given flag string (adjusting any capitalization differences), if a valid flag was given. Else, return the empty string.
func VerifyNormalizeFlagOK ¶
As above, but if flag is the empty string, then return FlagOK. If non-empty and invalid, return the empty string.
func VerifyNormalizeNetType ¶
Returns the given net type string (adjusting any capitalization differences), if a valid netType was given. Else, return the empty string.
func VerifyNormalizeRole ¶
Returns the given role string (adjusting any capitalization differences), if a valid role was given. Else, return the empty string.
func VerifyNormalizeState ¶
Returns the given state string (adjusting any capitalization differences), if a valid state is given. Else, return the empty string.
func VerifyNormalizeSubRole ¶
Returns the given SubRole string (adjusting any capitalization differences), if a valid SubRole was given. Else, return the empty string.
Types ¶
type Component ¶
type Component struct { ID string `json:"ID"` Type string `json:"Type"` State string `json:"State,omitempty"` Flag string `json:"Flag,omitempty"` Enabled *bool `json:"Enabled,omitempty"` SwStatus string `json:"SoftwareStatus,omitempty"` Role string `json:"Role,omitempty"` SubRole string `json:"SubRole,omitempty"` NID json.Number `json:"NID,omitempty"` Subtype string `json:"Subtype,omitempty"` NetType string `json:"NetType,omitempty"` Arch string `json:"Arch,omitempty"` Class string `json:"Class,omitempty"` ReservationDisabled bool `json:"ReservationDisabled,omitempty"` Locked bool `json:"Locked,omitempty"` }
This is the equivalent to rs_node_t in Cascade. It is the minimal amount of of information for tracking component state and other vital info at an abstract level. The hwinv is for component-type specific fields and detailed HW attributes, i.e. just like XC.
For most HMS operations (and non-inventory ones in the managed plane) this info should be sufficient. We want to keep it minimal for speed. Those fields that are not fixed at discovery should be those that can change outside of discovery in response to system activity, i.e. hwinv should contain only fields that are basically static between discoveries of the endpoint. Things like firmware versions might be an exception, but that would be a separate process SM would
1.0.0
type ComponentArray ¶
type ComponentArray struct {
Components []*Component `json:"Components"`
}
A collection of 0-n Components. It could just be an ordinary array but we want to save the option to have indentifying info, etc. packaged with it, e.g. the query parameters or options that produced it, especially if there are fewer fields than normal being included.
type HMSError ¶
type HMSError struct { // Used to identify similar groups of errors to higher-level software. Class string `json:"class"` // Message, used as error string for Error() Message string `json:"message"` // Optional full ProblemDetails Problem *ProblemDetails `json:"problem,omitempty"` }
Custom Go 'error' type for HMS - Implements Error()
Works like a standard Go 'error', but can be distinguished as an HMS-specific error with an optional class for better handling upstream. An RFC7807 error can optionally be added in case we need to pass those through multiple layers- but without forcing us to (since they still look like regular Go errors). Note that this is just a starting point. We can attach other types of info later if we want.
Part of the motivation is so we can determine which errors are safe to return to users, i.e. we don't want to give them things that expose details about the database structure.
Can attach custom RFC7807 response if needed.
func GetHMSError ¶
Test and retrieve HMSError info, if error is in fact of that type.
If bool is 'true', HMSError will be a non-nil HMSError with the expected extended HMSError fields.
If bool is 'false', err is not an HMSError and *HMSError will be nil
func NewHMSError ¶
New HMSError, with message string and optional class.
A subsequent call to AddProblem() can associate a ProblemDetails with it. By default Problem pointer is nil (again, this is optional functionality)
func (*HMSError) AddProblem ¶
func (e *HMSError) AddProblem(p *ProblemDetails)
Add an ProblemDetails to be associated with e
Note: This simply associates the pointer to p with e. There is no new ProblemDetails created and no deep copy is performed.
func (*HMSError) Error ¶
Implement Error() interface - This makes it so an HMSError can be returned anywhere an ordinary Go 'error' is returned.
The receiver of an 'error' can then use one of the IsHMSError* or HasHMSError functions to test if it is an HMSError, and if so, to obtain the additional fields.
func (*HMSError) GetProblem ¶
func (e *HMSError) GetProblem() *ProblemDetails
If e has a ProblemDetails associated with it, return a pointer to it If not, return nil.
func (*HMSError) IsClassIgnCase ¶
Return true if 'class' matches Class field for HMSError (case insensitive)
func (*HMSError) NewChild ¶
Create a new HMSError that is a copy of e, except with an updated "Message" field if 'msg' is not the empty string. Conversely, if 'msg' IS the empty string, i.e. "", the operation is equivalent to a copy.
Note: This DOES NOT COPY ProblemDetails if added with AddProblem. Instead, use NewChildWithProblem() for cases like this (or if you're unsure).
The (newerr).Problem pointer is always set to nil with this function.
func (*HMSError) NewChildWithProblem ¶
Create a newly-allocated child HMSError, deep-copying the parent, including any ProblemDetails that (may) have been associated using AddProblem().
In addition to copying, the 'msg' arg (if non-empty) is used to overwrite HMSError's "Message" and (if ProblemDetails are present) the ProblemDetails "Detail" field is also set to the same string.
The ProblemDetails 'Instance' is also updated if the 'instance' arg is a non-empty string.
If both 'msg' and 'instance' are unset the result is effectively a simple (deep) copy of e (including a deep copy of e.Problem).
This method basically combines the functionality of both the HMSError and ProblemDetails NewChild() functions.
type HMSFlag ¶
type HMSFlag string
const ( FlagUnknown HMSFlag = "Unknown" FlagOK HMSFlag = "OK" // Functioning properly FlagWarning HMSFlag = "Warning" // Continues to operate, but has an issue that may require attention. FlagAlert HMSFlag = "Alert" // No longer operating as expected. The state may also have changed due to error. FlagLocked HMSFlag = "Locked" // Another service has reserved this component. )
Valid flag values.
type HMSNetType ¶
type HMSNetType string
const ( NetSling HMSNetType = "Sling" NetInfiniband HMSNetType = "Infiniband" NetEthernet HMSNetType = "Ethernet" NetOEM HMSNetType = "OEM" // Placeholder for non-slingshot NetNone HMSNetType = "None" )
func (HMSNetType) String ¶
func (r HMSNetType) String() string
Allow HMSNetType to be treated as a standard string type.
type HMSRole ¶
type HMSRole string
type HMSState ¶
type HMSState string
State field used in component, set in response to events by state manager. 1.0.0
const ( StateUnknown HMSState = "Unknown" // The State is unknown. Appears missing but has not been confirmed as empty. StateEmpty HMSState = "Empty" // The location is not populated with a component StatePopulated HMSState = "Populated" // Present (not empty), but no further track can or is being done. StateOff HMSState = "Off" // Present but powered off StateOn HMSState = "On" // Powered on. If no heartbeat mechanism is available, it's software state may be unknown. StateStandby HMSState = "Standby" // No longer Ready and presumed dead. It typically means HB has been lost (w/alert). StateHalt HMSState = "Halt" // No longer Ready and halted. OS has been gracefully shutdown or panicked (w/ alert). StateReady HMSState = "Ready" // Both On and Ready to provide its expected services, i.e. used for jobs. )
Valid state values for components - should refect hardware state Enabled/Disabled is a separate boolean field, as the component should still have it's actual physical state known and tracked at all times, so we know what it is when it is enabled. It also avoids the primary case where admins need to modify the state field manually.
NOTE: there will be no state between on and ready. If the managed plane software does not have heartbeats, On is as high as it will ever get. So "active" is not useful. 'Paused' is not in scope now that the software status field exists.
type HMSSubRole ¶
type HMSSubRole string
const ( SubRoleMaster HMSSubRole = "Master" SubRoleWorker HMSSubRole = "Worker" SubRoleStorage HMSSubRole = "Storage" )
Valid SubRole values.
func (HMSSubRole) String ¶
func (r HMSSubRole) String() string
Allow HMSSubRole to be treated as a standard string type.
type HTTPRequest ¶
type HTTPRequest struct { Context context.Context // Context to pass to the underlying HTTP client. FullURL string // The full URL to pass to the HTTP client. Method string // HTTP method to use. Payload []byte // Bytes payload to pass if desired of ContentType. Auth *Auth // Basic authentication if necessary using Auth struct. Timeout time.Duration // Timeout for entire transaction. SkipTLSVerify bool // Ignore TLS verification errors? ExpectedStatusCode int // Expected HTTP status return code. ContentType string // HTTP content type of Payload. }
Package to slightly abstract some of the most mundane of HTTP interactions. Primary intention is as a JSON getter and parser, with the latter being a generic interface that can be converted to a custom structure.
func NewHTTPRequest ¶
func NewHTTPRequest(fullURL string) *HTTPRequest
NewHTTPRequest creates a new HTTPRequest with default settings.
func (*HTTPRequest) DoHTTPAction ¶
func (request *HTTPRequest) DoHTTPAction() (payloadBytes []byte, err error)
Given a HTTPRequest this function will facilitate the desired operation using the retryablehttp package to gracefully retry should the connection fail.
func (*HTTPRequest) GetBodyForHTTPRequest ¶
func (request *HTTPRequest) GetBodyForHTTPRequest() (v interface{}, err error)
Returns an interface for the response body for a given request by calling DoHTTPAction and unmarshaling. As such, do NOT call this method unless you expect a JSON body in return!
A powerful way to use this function is by feeding its result to the mapstructure package's Decode method:
v := request.GetBodyForHTTPRequest() myTypeInterface := v.(map[string]interface{}) var myPopulatedStruct MyType mapstructure.Decode(myTypeInterface, &myPopulatedStruct)
In this way you can generically make all your HTTP requests and essentially "cast" the resulting interface to a structure of your choosing using it as normal after that point. Just make sure to infer the correct type for `v`.
func (*HTTPRequest) String ¶
func (request *HTTPRequest) String() string
type ProblemDetails ¶
type ProblemDetails struct { Type string `json:"type"` // either url or "about:blank" Title string `json:"title,omitempty"` Detail string `json:"detail,omitempty"` Instance string `json:"instance,omitempty"` Status int `json:"status,omitempty"` }
RFC 7807 Problem Details
These are the officially-specified fields. The implementation is allowed to add new ones, but we'll stick with these for now. Almost all are optional, however, and blank fields are not encoded if they are the empty string (which is fine
The only required field is Type and is expected to be a URL that should describe the problem type when dereferenced. It's not intended to be a dead link, but I don't think clients are allowed to assume it's not, either. We don't implement a way of serving these URLs here, in any case. It's not obligatory to have a lot of different problem types, or even desirable if the client can just andling them all the same basic way.
The only non-URL that is allowed for Type is "about:blank", but only in the situation where the semantics of the problem can be understood okay as a basic HTTP error. In this case Title should be the official HTTP status code string, not something custom. You are free to put anything you want in Detail however, so if you already have an error function that returns an error string, you can easily convert it into one of these generic HTTP errors. In fact, we offer a shortcut to doing this.
If Type is a URL, Title should describe the problem type. If you are doing this, and not just using type:"about:blank" and the HTTP error, for the title, it should be because you have some kind of problem that needs, or could benefit from, special or more involved treatment that the URL describes when dereferenced.
Detail explains in a human readible way what happened when a specific problem occurred.
Instance is another URI describing a specific occurrence of a problem. So within a particular Type and Title (a general problem type) the Detail and Instance document a specific incident in which that problem occurred. Or at least that's the general idea.
Status is the numerical HTTP status code. It is optional, and if present, should match at least the intended HTTP code in the header, though this is not strictly required.
For more info, reading RFC 7807 would obviously be the authoritative source.
func NewProblemDetails ¶
func NewProblemDetails(ptype, title, detail, instance string, status int) *ProblemDetails
New full ProblemDetails, will all fields specified. Type is the only required field, and if any of the other args are left as the empty string, the fields won't appear in the JSON output at all.
p := base.NewProblemDetails( "https://example.com/probs/MyProblem", "My Problem's Title", "Details for this problem", "/instances/myprob/1", http.StatusBadRequest, )
func NewProblemDetailsStatus ¶
func NewProblemDetailsStatus(detail string, status int) *ProblemDetails
New generic ProblemDetails for errors that are just based on the HTTP status code. Type and Title are filled in based on the status code.
There is no need for a URL in this case, as the Type is allowed to be "about:blank" if title is just the Status code text and there is no special handling needed beside the usual for that HTTP (error) StatusCode.
NOTE: Status should only be filled in using http.Status* instead of a literal number.
'status' will be treated as http.StatusBadRequest (400) if it does not match a valid http status code.
Example:
p := base.NewProblemDetailsStatus(http.StatusNotFound, "No such component")
Produces a struct p with the following fields
&ProblemDetails{ Type: "about:blank", Title: "Not Found", Detail: "No such component", Status: 404, }
func (*ProblemDetails) NewChild ¶
func (p *ProblemDetails) NewChild(detail, instance string) *ProblemDetails
Create a new ProblemDetails struct copied from p with only detail and instance (optionally) updated. If either field is the empty string, it will not be updated and the parent values will be used.
The basic idea here is to be able to define a few prototype ProblemDetails with the Type and Title filled in (along with a default Detail and HTTP Status code, if desired), and then create a child copy when a problem actually occurs with the specific Detail and (optionally) instance URI filled in.
Example:
p := base.NewProblemDetails( "https://example.com/probs/MyProblem", "My Problem's Title", "Generic details for this problem type", "/instances/myprobs/", http.StatusBadRequest, ) // Copy updates Detail and instance pChild := p.NewChild("Specific details for this problem", "/instances/myprobs/1") // Copy has updated Detail field only // i.e. p.Instance == pChildDetailOnly.Instance pChildDetailOnly := p.NewChild("Specific details for this problem", "") // Strict copy only, new ProblemDetails has identical fields. pCopy := p.NewChild("", "")
type Worker ¶
///////////////////////////////////////////////////////////////////////////// Workers /////////////////////////////////////////////////////////////////////////////
type WorkerPool ¶
type WorkerPool struct { Workers []Worker Pool chan chan Job JobQueue chan Job StopChannel chan bool }
///////////////////////////////////////////////////////////////////////////// WorkerPool /////////////////////////////////////////////////////////////////////////////
func NewWorkerPool ¶
func NewWorkerPool(maxWorkers, maxJobQueue int) *WorkerPool
Create a new pool of workers
func (*WorkerPool) Queue ¶
func (p *WorkerPool) Queue(job Job) int
Queue a job. Returns 1 if the operation would block because the work queue is full.
func (*WorkerPool) Run ¶
func (p *WorkerPool) Run()
Starts all of the workers and the job dispatcher
func (*WorkerPool) Stop ¶
func (p *WorkerPool) Stop()
Command the dispatcher to stop itself and all workers