Skip to content

Commit

Permalink
feat: add a function to wait for the device to become ready
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Ege <[email protected]>
  • Loading branch information
graugans committed Apr 13, 2024
1 parent 7e8afd8 commit 4f784b5
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 6 deletions.
52 changes: 52 additions & 0 deletions cmd/ovp8xx/cmd/waitforonline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright © 2023 Christian Ege <[email protected]>
*/
package cmd

import (
"fmt"
"time"

"github.com/graugans/go-ovp8xx/pkg/ovp8xx"
"github.com/spf13/cobra"
)

func waitForOnlineCommand(cmd *cobra.Command, args []string) error {
var ok = false
var err error
helper, err := NewHelper(cmd)
if err != nil {
return err
}

o3r := ovp8xx.NewClient(
ovp8xx.WithHost(helper.hostname()),
)

timeout, err := cmd.Flags().GetUint("timeout")
if err != nil {
return err
}

if ok, err = o3r.IsAvailable(time.Duration(timeout) * time.Second); err != nil {
return err
}
if ok {
fmt.Printf("The device is available now\n")
}
return nil
}

// waitForOnlineCmd represents the wait command
var waitForOnlineCmd = &cobra.Command{
Use: "WaitForOnline",
Short: "Wait until the device is accessible",
Long: `This command is maybe useful after a reboot or power on.
It can be used to wait until the device can handle requests`,
RunE: waitForOnlineCommand,
}

func init() {
rootCmd.AddCommand(waitForOnlineCmd)
waitForOnlineCmd.Flags().Uint("timeout", 120, "Timeout in Seconds to wait until the device is accessible")
}
43 changes: 43 additions & 0 deletions pkg/ovp8xx/client.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package ovp8xx

import (
"context"
"fmt"
"time"
)

type (
Expand All @@ -16,6 +18,13 @@ type (
}
)

// NewClient creates a new OVP8xx client with the provided options.
// The opts parameter is a variadic parameter that allows specifying
// multiple client options.
// Example usage:
//
// client := NewClient(WithTimeout(10 * time.Second), WithRetry(3))
// // ...
func NewClient(opts ...ClientOption) *Client {
// Initialise with default values
client := &Client{
Expand All @@ -30,15 +39,49 @@ func NewClient(opts ...ClientOption) *Client {
return client
}

// WithHost sets the host for the OVP8xx client.
// It returns a ClientOption function that can be used to configure the client.
func WithHost(host string) ClientOption {
return func(c *Client) {
c.host = host
}
}

// GetDiagnosticClient returns a new instance of DiagnosisClient that can be used to perform diagnostic operations on the OVP8xx device.
func (device *Client) GetDiagnosticClient() *DiagnosisClient {
client := &DiagnosisClient{}
client.host = device.host
client.url = fmt.Sprintf("http://%s/api/rpc/v1/com.ifm.diagnostic/", client.host)
return client
}

// IsAvailable checks if the client is available by making a XML-RPC get request to query the "/device" object.
// This is useful to wait until a device is ready for communication.
// It returns a boolean indicating the availability status and an error if any.
// The function uses a timeout duration to limit the execution time of the request.
func (d *Client) IsAvailable(timeout time.Duration) (bool, error) {
var err error
proc := make(chan struct{}, 1)

go func() {
for {
if _, err := d.Get([]string{"/device"}); err != nil {
// In case of an error retry, regardless of an timeout
continue
}
// we are done, the get call was successful
proc <- struct{}{}
}
}()

ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

select {
case <-ctx.Done():
return false, fmt.Errorf("timeout occurred while checking if the device is available: %w", ctx.Err())
case <-proc:
return true, err
}
}
47 changes: 41 additions & 6 deletions pkg/ovp8xx/rpc.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
package ovp8xx

import (
"errors"
"net"

"alexejk.io/go-xmlrpc"
)

// IsTimeoutError checks if the given error is a network timeout error.
// It returns true if the error is a network error and the timeout flag is set, otherwise it returns false.
func IsTimeoutError(err error) bool {
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
return true
}
return false
}

// Get retrieves the configuration for the specified pointers from the OVP8xx device.
// The pointers parameter is a slice of strings that contains the pointers to retrieve the configuration for.
// The function returns the retrieved configuration as a Config struct and an error if any occurred.
// Example usage: config, err := device.Get([]string{"/device", "/ports"})
func (device *Client) Get(pointers []string) (Config, error) {
client, err := xmlrpc.NewClient(device.url)
if err != nil {
Expand All @@ -26,6 +43,8 @@ func (device *Client) Get(pointers []string) (Config, error) {
return *NewConfig(WitJSONString(result.JSON)), nil
}

// Set sets the configuration of the OVP8xx device.
// It takes a Config object as input and returns an error if any.
func (device *Client) Set(conf Config) error {
client, err := xmlrpc.NewClient(device.url)
if err != nil {
Expand All @@ -37,13 +56,11 @@ func (device *Client) Set(conf Config) error {
Data string
}{Data: conf.String()}

if err := client.Call("set", arg, nil); err != nil {
return err
}

return nil
return client.Call("set", arg, nil)
}

// GetInit retrieves the initial configuration from the OVP8xx device.
// It returns a Config struct representing the device configuration and an error if any.
func (device *Client) GetInit() (Config, error) {
client, err := xmlrpc.NewClient(device.url)
if err != nil {
Expand All @@ -62,6 +79,10 @@ func (device *Client) GetInit() (Config, error) {
return *NewConfig(WitJSONString(result.JSON)), nil
}

// SaveInit saves the configuration of the OVP8xx device.
// If no pointers are provided, it saves the complete configuration.
// If pointers are provided, it saves only the specified configuration pointers.
// It returns an error if there was a problem saving the configuration.
func (device *Client) SaveInit(pointers []string) error {
client, err := xmlrpc.NewClient(device.url)
if err != nil {
Expand All @@ -76,10 +97,15 @@ func (device *Client) SaveInit(pointers []string) error {

arg := &struct {
Pointers []string
}{Pointers: pointers}
}{
Pointers: pointers,
}
return client.Call("saveInit", arg, nil)
}

// FactoryReset performs a factory reset on the OVP8xx device.
// If keepNetworkSettings is set to true, the network settings will be preserved after the reset.
// Returns an error if the factory reset fails or if there is an issue with the XML-RPC client.
func (device *Client) FactoryReset(keepNetworkSettings bool) error {
client, err := xmlrpc.NewClient(device.url)
if err != nil {
Expand All @@ -95,6 +121,9 @@ func (device *Client) FactoryReset(keepNetworkSettings bool) error {
return client.Call("factoryReset", arg, nil)
}

// GetSchema retrieves the schema for the specified pointers from the OVP8xx device.
// It returns the schema in JSON format as a string.
// If an error occurs during the retrieval process, it returns an empty string and the error.
func (device *Client) GetSchema(pointers []string) (string, error) {
client, err := xmlrpc.NewClient(device.url)
if err != nil {
Expand All @@ -114,6 +143,8 @@ func (device *Client) GetSchema(pointers []string) (string, error) {
return result.JSON, nil
}

// Reboot sends a reboot command to the OVP8xx device.
// If an error occurs during the connection or the method call, it is returned.
func (device *Client) Reboot() error {
client, err := xmlrpc.NewClient(device.url)
if err != nil {
Expand All @@ -137,6 +168,8 @@ func (device *Client) RebootToSWUpdate() error {
return client.Call("rebootToRecovery", nil, nil)
}

// GetFiltered retrieves a filtered configuration from the DiagnosisClient.
// It takes a Config object as input and returns a Config object and an error.
func (device *DiagnosisClient) GetFiltered(conf Config) (Config, error) {
client, err := xmlrpc.NewClient(device.url)
if err != nil {
Expand All @@ -159,6 +192,8 @@ func (device *DiagnosisClient) GetFiltered(conf Config) (Config, error) {
return *NewConfig(WitJSONString(result.JSON)), nil
}

// GetFilterSchema retrieves the filter schema from the DiagnosisClient.
// It returns a Config object representing the filter schema and an error if any.
func (device *DiagnosisClient) GetFilterSchema() (Config, error) {
client, err := xmlrpc.NewClient(device.url)
if err != nil {
Expand Down

0 comments on commit 4f784b5

Please sign in to comment.