Enhance baremetal subcommand to list hosts

* added NodeName field in the remote Client interface
* added new subcommand 'list-hosts' to list hosts from
  host-inventory from site manifests

Closes: #359
Signed-off-by: Shon Phand <shonphand@gmail.com>
Change-Id: I560a8117b1d55cad2a634df0d05a4aaedae2a873
This commit is contained in:
shon phand 2021-10-18 18:26:40 +00:00 committed by Shon Phand
parent 5050c327c0
commit d38b9b5f0b
31 changed files with 592 additions and 135 deletions

View File

@ -85,6 +85,7 @@ func NewBaremetalCommand(cfgFactory config.Factory) *cobra.Command {
baremetalRootCmd.AddCommand(NewPowerStatusCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewRebootCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewRemoteDirectCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewListHostsCommand(cfgFactory, options))
return baremetalRootCmd
}

View File

@ -59,6 +59,11 @@ func TestBaremetal(t *testing.T) {
CmdLine: "-h",
Cmd: baremetal.NewRemoteDirectCommand(nil, &inventory.CommandOptions{}),
},
{
Name: "baremetal-list-hosts-with-help",
CmdLine: "-h",
Cmd: baremetal.NewListHostsCommand(nil, &inventory.CommandOptions{}),
},
}
for _, tt := range tests {

View File

@ -0,0 +1,58 @@
/*
Licensed 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
https://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 baremetal
import (
"time"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/inventory"
)
var (
listHostsCommand = "list-hosts"
listLong = "List bare metal host(s)."
listExample = `
Retrieve list of baremetal hosts, default output option is 'table'
# airshipctl baremetal list-hosts
# airshipctl baremetal list-hosts --namespace default
# airshipctl baremetal list-hosts --namespace default --output table
# airshipctl baremetal list-hosts --output yaml
`
)
// NewListHostsCommand provides a command to list a remote host.
func NewListHostsCommand(cfgFactory config.Factory, options *inventory.CommandOptions) *cobra.Command {
l := &inventory.ListHostsCommand{Options: options}
cmd := &cobra.Command{
Use: listHostsCommand,
Short: "Airshipctl command to list bare metal host(s)",
Long: listLong,
Example: listExample,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
l.Writer = cmd.OutOrStdout()
return l.RunE()
},
}
flags := cmd.Flags()
flags.StringVarP(&l.OutputFormat, "output", "o", "table", "output formats. Supported options are 'table' and 'yaml'")
flags.StringVarP(&options.Namespace, flagNamespace, flagNamespaceSort, "", flagNamespaceDescription)
flags.StringVarP(&options.Labels, flagLabel, flagLabelShort, "", flagLabelDescription)
flags.DurationVar(&options.Timeout, flagTimeout, 10*time.Minute, flagTimeoutDescription)
return cmd
}

View File

@ -0,0 +1,20 @@
List bare metal host(s).
Usage:
list-hosts [flags]
Examples:
Retrieve list of baremetal hosts, default output option is 'table'
# airshipctl baremetal list-hosts
# airshipctl baremetal list-hosts --namespace default
# airshipctl baremetal list-hosts --namespace default --output table
# airshipctl baremetal list-hosts --output yaml
Flags:
-h, --help help for list-hosts
-l, --labels string label(s) to filter desired bare metal host from site manifest documents
-n, --namespace string airshipctl phase that contains the desired bare metal host from site manifest document(s)
-o, --output string output formats. Supported options are 'table' and 'yaml' (default "table")
--timeout duration timeout on bare metal action (default 10m0s)

View File

@ -7,6 +7,7 @@ Usage:
Available Commands:
ejectmedia Airshipctl command to eject virtual media attached to a bare metal host
help Help about any command
list-hosts Airshipctl command to list bare metal host(s)
poweroff Airshipctl command to shutdown bare metal host(s)
poweron Airshipctl command to power on host(s)
powerstatus Airshipctl command to retrieve the power status of a bare metal host

View File

@ -33,6 +33,7 @@ SEE ALSO
* :ref:`airshipctl <airshipctl>` - A unified command line tool for management of end-to-end kubernetes cluster deployment on cloud infrastructure environments.
* :ref:`airshipctl baremetal ejectmedia <airshipctl_baremetal_ejectmedia>` - Airshipctl command to eject virtual media attached to a bare metal host
* :ref:`airshipctl baremetal list-hosts <airshipctl_baremetal_list-hosts>` - Airshipctl command to list bare metal host(s)
* :ref:`airshipctl baremetal poweroff <airshipctl_baremetal_poweroff>` - Airshipctl command to shutdown bare metal host(s)
* :ref:`airshipctl baremetal poweron <airshipctl_baremetal_poweron>` - Airshipctl command to power on host(s)
* :ref:`airshipctl baremetal powerstatus <airshipctl_baremetal_powerstatus>` - Airshipctl command to retrieve the power status of a bare metal host

View File

@ -0,0 +1,54 @@
.. _airshipctl_baremetal_list-hosts:
airshipctl baremetal list-hosts
-------------------------------
Airshipctl command to list bare metal host(s)
Synopsis
~~~~~~~~
List bare metal host(s).
::
airshipctl baremetal list-hosts [flags]
Examples
~~~~~~~~
::
Retrieve list of baremetal hosts, default output option is 'table'
# airshipctl baremetal list-hosts
# airshipctl baremetal list-hosts --namespace default
# airshipctl baremetal list-hosts --namespace default --output table
# airshipctl baremetal list-hosts --output yaml
Options
~~~~~~~
::
-h, --help help for list-hosts
-l, --labels string label(s) to filter desired bare metal host from site manifest documents
-n, --namespace string airshipctl phase that contains the desired bare metal host from site manifest document(s)
-o, --output string output formats. Supported options are 'table' and 'yaml' (default "table")
--timeout duration timeout on bare metal action (default 10m0s)
Options inherited from parent commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
--airshipconf string path to the airshipctl configuration file. Defaults to "$HOME/.airship/config"
--debug enable verbose output
SEE ALSO
~~~~~~~~
* :ref:`airshipctl baremetal <airshipctl_baremetal>` - Airshipctl command to manage bare metal host(s)

View File

@ -7,6 +7,7 @@ baremetal
airshipctl_baremetal
airshipctl_baremetal_ejectmedia
airshipctl_baremetal_list-hosts
airshipctl_baremetal_poweroff
airshipctl_baremetal_poweron
airshipctl_baremetal_powerstatus

View File

@ -68,6 +68,8 @@ spec:
required:
- isoURL
type: object
required:
- remoteDirect
type: object
timeout:
description: Timeout in seconds

View File

@ -35,10 +35,6 @@ spec:
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
env-vars:
description: EnvVars if set to true, allows to source variables for cluster-api
components for environment variables.
type: boolean
images:
additionalProperties:
description: ImageMeta is part of clusterctl config
@ -53,66 +49,24 @@ spec:
description: InitOptions container with exposed clusterctl InitOptions
properties:
bootstrap-providers:
description: BootstrapProviders and versions (e.g. kubeadm:v0.3.0)
to add to the management cluster. If unspecified, the kubeadm bootstrap
provider's latest release is used.
items:
type: string
type: array
description: BootstrapProviders and versions (comma separated, e.g.
kubeadm:v0.3.0) to add to the management cluster. If unspecified,
the kubeadm bootstrap provider's latest release is used.
type: string
control-plane-providers:
description: ControlPlaneProviders and versions (e.g. kubeadm:v0.3.0)
to add to the management cluster. If unspecified, the kubeadm control
plane provider latest release is used.
items:
type: string
type: array
description: ControlPlaneProviders and versions (comma separated,
e.g. kubeadm:v0.3.0) to add to the management cluster. If unspecified,
the kubeadm control plane provider latest release is used.
type: string
core-provider:
description: CoreProvider version (e.g. cluster-api:v0.3.0) to add
to the management cluster. If unspecified, the cluster-api core
provider's latest release is used.
type: string
infrastructure-providers:
description: InfrastructureProviders and versions (e.g. aws:v0.5.0)
to add to the management cluster.
items:
type: string
type: array
kubeConfigRef:
description: KubeConfigRef reference to KubeConfig document
properties:
apiVersion:
description: API version of the referent.
type: string
fieldPath:
description: 'If referring to a piece of an object instead of
an entire object, this string should contain a valid JSON/Go
field access statement, such as desiredState.manifest.containers[2].
For example, if the object reference is to a container within
a pod, this would take on a value like: "spec.containers{name}"
(where "name" refers to the name of the container that triggered
the event) or if no container name is specified "spec.containers[2]"
(container with index 2 in this pod). This syntax is chosen
only to have some well-defined way of referencing a part of
an object. TODO: this design is not final and this field is
subject to change in the future.'
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
resourceVersion:
description: 'Specific resourceVersion to which this reference
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
type: string
uid:
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
type: string
type: object
description: InfrastructureProviders and versions (comma separated,
e.g. aws:v0.5.0,metal3:v0.4.0) to add to the management cluster.
type: string
type: object
kind:
description: 'Kind is a string value representing the REST resource this
@ -125,39 +79,22 @@ spec:
description: MoveOptions carries the options supported by move.
properties:
namespace:
description: The namespace where the workload cluster is hosted. If
unspecified, the target context's namespace is used.
description: Namespace where the objects describing the workload cluster
exists. If unspecified, the current namespace will be used.
type: string
type: object
providers:
items:
description: Provider is part of clusterctl config
properties:
clusterctl-repository:
description: IsClusterctlRepository if set to true, clusterctl provider's
repository implementation will be used if omitted or set to false,
airshipctl repository implementation will be used.
type: boolean
name:
type: string
type:
type: string
url:
description: URL can contain remote URL of upstream Provider or
relative to target path of the manifest
type: string
variable-substitution:
description: VariableSubstitution indicates weather you want to
substitute variables in the cluster-api manifests if set to true,
variables will be substituted only if they are defined either
in Environment or in AdditionalComponentVariables, if not they
will be left as is.
type: boolean
versions:
additionalProperties:
type: string
description: Map of versions where each key is a version and value
is path relative to target path of the manifest ignored if IsClusterctlRepository
is set to true
type: object
required:
- name
- type

View File

@ -0,0 +1,51 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.6.1
creationTimestamp: null
name: hosts.airshipit.org
spec:
group: airshipit.org
names:
kind: Host
listKind: HostList
plural: hosts
singular: host
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Host object to represent baremetal host
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
nodeid:
type: string
nodename:
type: string
required:
- nodeid
- nodename
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -51,6 +51,11 @@ spec:
type: string
group:
type: string
isClusterScoped:
description: isClusterScoped is true if the object is known,
per the openapi data in use, to be cluster scoped, and false
otherwise.
type: boolean
kind:
type: string
labelSelector:
@ -59,8 +64,11 @@ spec:
It matches with the resource labels.
type: string
name:
description: Name of the resource.
type: string
namespace:
description: Namespace the resource belongs to, if it can
belong to a namespace.
type: string
version:
type: string
@ -90,6 +98,11 @@ spec:
type: string
group:
type: string
isClusterScoped:
description: isClusterScoped is true if the object is known,
per the openapi data in use, to be cluster scoped, and false
otherwise.
type: boolean
kind:
type: string
labelSelector:
@ -98,8 +111,11 @@ spec:
It matches with the resource labels.
type: string
name:
description: Name of the resource.
type: string
namespace:
description: Namespace the resource belongs to, if it can
belong to a namespace.
type: string
version:
type: string

View File

@ -75,10 +75,27 @@ spec:
for the server's certificate. This will make your HTTPS
connections insecure.
type: boolean
proxy-url:
description: "ProxyURL is the URL to the proxy to be used
for all requests made by this client. URLs with \"http\",
\"https\", and \"socks5\" schemes are supported. If this
configuration is not provided or the empty string, the
client attempts to construct a proxy configuration from
http_proxy and https_proxy environment variables. If these
environment variables are not set, the client does not
attempt to proxy requests. \n socks5 proxying does not
currently support spdy streaming endpoints (exec, attach,
port forward)."
type: string
server:
description: Server is the address of the kubernetes cluster
(https://hostname:port).
type: string
tls-server-name:
description: TLSServerName is used to check server certificate.
If TLSServerName is empty, the hostname used to contact
the server is used.
type: string
required:
- server
type: object
@ -288,8 +305,24 @@ spec:
- value
type: object
type: array
installHint:
description: This text is shown to the user when the
executable doesn't seem to be present. For example,
`brew install foo-cli` might be a good InstallHint
for foo-cli on Mac OS systems.
type: string
provideClusterInfo:
description: ProvideClusterInfo determines whether or
not to provide cluster information, which could potentially
contain very large CA data, to this exec plugin as
a part of the KUBERNETES_EXEC_INFO environment variable.
By default, it is set to false. Package k8s.io/client-go/tools/auth/exec
provides helper methods for reading this environment
variable.
type: boolean
required:
- command
- provideClusterInfo
type: object
extensions:
description: Extensions holds additional information. This

View File

@ -31,6 +31,16 @@ spec:
description: ApplyConfig provides instructions on how to apply resources
to kubernetes cluster
properties:
context:
type: string
debug:
type: boolean
druRun:
type: boolean
kubeconfig:
type: string
phaseName:
type: string
pruneOptions:
description: ApplyPruneOptions provides instructions how to prune
for kubernetes resources
@ -42,6 +52,35 @@ spec:
description: ApplyWaitOptions provides instructions how to wait for
kubernetes resources
properties:
conditions:
items:
description: Condition is a jsonpath for particular TypeMeta
which indicates what state to wait
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of
this representation of an object. Servers should convert
recognized schemas to the latest internal value, and may
reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
jsonPath:
type: string
kind:
description: 'Kind is a string value representing the REST
resource this object represents. Servers may infer this
from the endpoint the client submits requests to. Cannot
be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
value:
description: Value is desired state to wait for, if no value
specified - just existence of provided jsonPath will be
checked
type: string
type: object
type: array
pollInterval:
description: PollInterval in seconds
type: integer
timeout:
description: Timeout in seconds
type: integer

View File

@ -34,8 +34,30 @@ spec:
documentEntryPoint:
type: string
executorRef:
description: ObjectReference contains enough information to let you
inspect or modify the referred object.
description: 'ObjectReference contains enough information to let you
inspect or modify the referred object. --- New uses of this type
are discouraged because of difficulty describing its usage when
embedded in APIs. 1. Ignored fields. It includes many fields which
are not generally honored. For instance, ResourceVersion and FieldPath
are both very rarely valid in actual usage. 2. Invalid usage help. It
is impossible to add specific help for individual usage. In most
embedded usages, there are particular restrictions like, "must
refer only to types A and B" or "UID not honored" or "name must
be restricted". Those cannot be well described when embedded. 3.
Inconsistent validation. Because the usages are different, the
validation rules are different by usage, which makes it hard for
users to predict what will happen. 4. The fields are both imprecise
and overly precise. Kind is not a precise mapping to a URL. This
can produce ambiguity during interpretation and require a REST
mapping. In most cases, the dependency is on the group,resource
tuple and the version of the actual struct is irrelevant. 5.
We cannot easily change it. Because this type is embedded in many
locations, updates to this type will affect numerous schemas. Don''t
make new APIs embed an underspecified API type they do not control.
Instead of using this type, create a locally provided and used type
that is well-focused on your reference. For example, ServiceReferences
for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
.'
properties:
apiVersion:
description: API version of the referent.

View File

@ -98,8 +98,11 @@ spec:
It matches with the resource labels.
type: string
name:
description: Name of the resource.
type: string
namespace:
description: Namespace the resource belongs to, if it can
belong to a namespace.
type: string
version:
type: string
@ -133,8 +136,11 @@ spec:
It matches with the resource labels.
type: string
name:
description: Name of the resource.
type: string
namespace:
description: Namespace the resource belongs to, if it
can belong to a namespace.
type: string
version:
type: string

View File

@ -83,9 +83,9 @@ spec:
description: capi_images defines collections of images used by cluster
API. The name of each key in this section should correspond to the
airshipctl function in which the images will be used, such as "capm3".
Each capi_image object must have a "manager" object, which must have
"repository" and "tag" properties defined. capi_images may also include
an optional "ipam-manager" or "auth_proxy" object,
Each capi_image object must have a "manager" object, which must
have "repository" and "tag" properties defined. capi_images may
also include an optional "ipam-manager" or "auth_proxy" object,
which must also have "repository" and "tag" properties defined.
type: object
charts:

View File

@ -0,0 +1,37 @@
/*
Licensed 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
https://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 v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
// Host object to represent baremetal host
type Host struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
NodeName string `json:"nodename"`
NodeID string `json:"nodeid"`
}
// DefaultHost can be used to safely unmarshal phase object without nil pointers
func DefaultHost() *Host {
return &Host{
NodeName: "defaultName",
NodeID: "defaultID",
}
}

View File

@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
@ -708,6 +709,31 @@ func (in *Gvk) DeepCopy() *Gvk {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Host) DeepCopyInto(out *Host) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Host.
func (in *Host) DeepCopy() *Host {
if in == nil {
return nil
}
out := new(Host)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Host) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostNetworkingSpec) DeepCopyInto(out *HostNetworkingSpec) {
*out = *in

View File

@ -149,7 +149,9 @@ func (i Inventory) newHost(doc document.Document) (Host, error) {
}
}
client, err := clientFactory(
nodeName := doc.GetName()
client, err := clientFactory(nodeName,
address,
i.mgmtCfg.Insecure,
i.mgmtCfg.UseProxy,

View File

@ -20,8 +20,18 @@ import (
"io"
"time"
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
"opendev.org/airship/airshipctl/pkg/inventory/ifc"
remoteifc "opendev.org/airship/airshipctl/pkg/remote/ifc"
"opendev.org/airship/airshipctl/pkg/util"
"opendev.org/airship/airshipctl/pkg/util/yaml"
)
const (
// TableOutputFormat table
TableOutputFormat = "table"
// YamlOutputFormat yaml
YamlOutputFormat = "yaml"
)
// CommandOptions is used to store common variables from cmd flags for baremetal command group
@ -37,6 +47,18 @@ type CommandOptions struct {
Inventory ifc.Inventory
}
// ListHostsCommand is used to store common variables from cmd flags for list-hots command
type ListHostsCommand struct {
Writer io.Writer
Options *CommandOptions
OutputFormat string
}
//NewListHostsCommand ListHostsCommand constructor
func NewListHostsCommand(options *CommandOptions) *ListHostsCommand {
return &ListHostsCommand{Options: options}
}
// NewOptions options constructor
func NewOptions(i ifc.Inventory) *CommandOptions {
return &CommandOptions{
@ -61,6 +83,22 @@ func (o *CommandOptions) validateSingleHostAction() error {
return nil
}
//RunE method returns list of hots from BaremetalInventory
func (l *ListHostsCommand) RunE() error {
if l.OutputFormat != TableOutputFormat && l.OutputFormat != YamlOutputFormat {
return ErrInvalidOptions{Message: "output formats. Supported options are 'table' and 'yaml'"}
}
hostClients, err := l.Options.getAllHost()
if err != nil {
return err
}
if len(hostClients) == 0 {
return fmt.Errorf("No hosts present in the hostInventory")
}
return l.Write(hostClients)
}
// BMHAction performs an action against BaremetalHost objects
func (o *CommandOptions) BMHAction(op ifc.BaremetalOperation) error {
if err := o.validateBMHAction(); err != nil {
@ -124,9 +162,32 @@ func (o *CommandOptions) getHost() (remoteifc.Client, error) {
return bmhInventory.SelectOne(o.selector())
}
func (o *CommandOptions) getAllHost() ([]remoteifc.Client, error) {
bmhInventory, err := o.Inventory.BaremetalInventory()
if err != nil {
return nil, err
}
return bmhInventory.Select(o.selector())
}
func (o *CommandOptions) selector() ifc.BaremetalHostSelector {
return (ifc.BaremetalHostSelector{}).
ByLabel(o.Labels).
ByName(o.Name).
ByNamespace(o.Namespace)
}
func (l *ListHostsCommand) Write(clients []remoteifc.Client) error {
hostList := []*v1alpha1.Host{}
for _, client := range clients {
host := v1alpha1.DefaultHost()
host.NodeID = client.NodeID()
host.NodeName = client.NodeName()
hostList = append(hostList, host)
}
if l.OutputFormat == YamlOutputFormat {
return yaml.WriteOut(l.Writer, hostList)
}
return util.PrintObjects(hostList, util.HostListFormat, l.Writer, false)
}

View File

@ -24,13 +24,68 @@ import (
"opendev.org/airship/airshipctl/pkg/inventory"
"opendev.org/airship/airshipctl/pkg/inventory/ifc"
remoteifc "opendev.org/airship/airshipctl/pkg/remote/ifc"
"opendev.org/airship/airshipctl/pkg/remote/power"
"opendev.org/airship/airshipctl/pkg/remote/redfish"
mockinventory "opendev.org/airship/airshipctl/testutil/inventory"
"opendev.org/airship/airshipctl/testutil/redfishutils"
)
const testNode = "node-0"
func TestListHostsCommand(t *testing.T) {
t.Run("success ListHosts", func(t *testing.T) {
var hosts []remoteifc.Client
c1, err := redfish.ClientFactory("node-0", "redfish+http://nolocalhost:32201/redfish/v1/Systems/node00",
true, true, "username", "password", 6, 300)
if err != nil {
assert.Equal(t, nil, err)
}
c2, err := redfish.ClientFactory("node-1", "redfish+http://nolocalhost:32201/redfish/v1/Systems/node01",
true, true, "username", "password", 6, 300)
if err != nil {
assert.Equal(t, nil, err)
}
hosts = append(hosts, c1, c2)
bmhInv := &mockinventory.MockBMHInventory{}
bmhInv.On("Select").Return(hosts, nil)
inv := &mockinventory.MockInventory{}
inv.On("BaremetalInventory").Once().Return(bmhInv, nil)
co := inventory.NewOptions(inv)
l := inventory.NewListHostsCommand(co)
buf := bytes.NewBuffer([]byte{})
l.Writer = buf
l.OutputFormat = "yaml"
actualErr := l.RunE()
assert.Equal(t, nil, actualErr)
assert.Contains(t, buf.String(), "node-0")
assert.Contains(t, buf.String(), "node-1")
})
t.Run("error ListHosts", func(t *testing.T) {
expectedErr := fmt.Errorf("No hosts present in the hostInventory")
var hosts []remoteifc.Client
bmhInv := &mockinventory.MockBMHInventory{}
bmhInv.On("Select").Return(hosts, nil)
inv := &mockinventory.MockInventory{}
inv.On("BaremetalInventory").Once().Return(bmhInv, nil)
co := inventory.NewOptions(inv)
l := inventory.NewListHostsCommand(co)
buf := bytes.NewBuffer([]byte{})
l.Writer = buf
l.OutputFormat = "yaml"
actualErr := l.RunE()
assert.Equal(t, expectedErr, actualErr)
})
}
func TestCommandOptions(t *testing.T) {
t.Run("error BMHAction bmh inventory", func(t *testing.T) {
inv := &mockinventory.MockInventory{}

View File

@ -44,6 +44,8 @@ const (
BaremetalOperationPowerOn BaremetalOperation = "power-on"
// BaremetalOperationEjectVirtualMedia eject virtual media
BaremetalOperationEjectVirtualMedia BaremetalOperation = "eject-virtual-media"
// BaremetalOperationListHosts list hosts
BaremetalOperationListHosts BaremetalOperation = "list-hosts"
)
// BaremetalBatchRunOptions are options to be passed to RunOperation, this is to be

View File

@ -11,3 +11,4 @@ spec:
bmc:
address: redfish+http://nolocalhost:32201/redfish/v1/Systems/ephemeral
credentialsName: node-0-bmc-secret
---

View File

@ -25,6 +25,7 @@ import (
type Client interface {
EjectVirtualMedia(context.Context) error
NodeID() string
NodeName() string
RebootSystem(context.Context) error
SetBootSourceByType(context.Context) error
SystemPowerOff(context.Context) error
@ -38,7 +39,7 @@ type Client interface {
}
// ClientFactory is a function to be used
type ClientFactory func(
type ClientFactory func(name string,
redfishURL string,
insecure bool, useProxy bool,
username string, password string,

View File

@ -36,6 +36,7 @@ const (
// Client holds details about a Redfish out-of-band system required for out-of-band management.
type Client struct {
nodeID string
nodeName string
username string
password string
redfishURL string
@ -53,6 +54,11 @@ func (c *Client) NodeID() string {
return c.nodeID
}
// NodeName retrieves the ephemeral node ID.
func (c *Client) NodeName() string {
return c.nodeName
}
// SystemActionRetries returns number of attempts to reach host during reboot process and ejecting virtual media
func (c *Client) SystemActionRetries() int {
return c.systemActionRetries
@ -355,7 +361,7 @@ func RemoteDirect(ctx context.Context, isoURL, redfishURL string, c ifc.Client)
}
// NewClient returns a client with the capability to make Redfish requests.
func NewClient(redfishURL string,
func NewClient(nodeName string, redfishURL string,
insecure bool,
useProxy bool,
username string,
@ -410,6 +416,7 @@ func NewClient(redfishURL string,
c := &Client{
nodeID: systemID,
nodeName: nodeName,
RedfishAPI: redfishClient.NewAPIClient(cfg).DefaultApi,
RedfishCFG: cfg,
systemActionRetries: systemActionRetries,
@ -427,13 +434,13 @@ func NewClient(redfishURL string,
}
// ClientFactory is a constructor for redfish ifc.Client implementation
var ClientFactory ifc.ClientFactory = func(redfishURL string,
var ClientFactory ifc.ClientFactory = func(nodeName string, redfishURL string,
insecure bool,
useProxy bool,
username string,
password string,
systemActionRetries int,
systemRebootDelay int) (ifc.Client, error) {
return NewClient(redfishURL, insecure, useProxy,
return NewClient(nodeName, redfishURL, insecure, useProxy,
username, password, systemActionRetries, systemRebootDelay)
}

View File

@ -31,6 +31,7 @@ import (
)
const (
nodeName = "node-0"
nodeID = "System.Embedded.1"
isoPath = "http://localhost:8099/ubuntu-focal.iso"
redfishURL = "redfish+https://localhost:2224/Systems/System.Embedded.1"
@ -39,13 +40,13 @@ const (
)
func TestNewClient(t *testing.T) {
c, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
c, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
assert.NotNil(t, c)
}
func TestNewClientInterface(t *testing.T) {
c, err := ClientFactory(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
c, err := ClientFactory(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
assert.NotNil(t, c)
}
@ -53,7 +54,7 @@ func TestNewClientInterface(t *testing.T) {
func TestNewClientDefaultValues(t *testing.T) {
sysActRetr := 111
sysRebDel := 999
c, err := NewClient(redfishURL, false, false, "", "", sysActRetr, sysRebDel)
c, err := NewClient(nodeName, redfishURL, false, false, "", "", sysActRetr, sysRebDel)
assert.Equal(t, c.systemActionRetries, sysActRetr)
assert.Equal(t, c.systemRebootDelay, sysRebDel)
assert.NoError(t, err)
@ -62,7 +63,7 @@ func TestNewClientDefaultValues(t *testing.T) {
func TestNewClientMissingSystemID(t *testing.T) {
badURL := "redfish+https://localhost:2224"
_, err := NewClient(badURL, false, false, "", "", systemActionRetries, systemRebootDelay)
_, err := NewClient(nodeName, badURL, false, false, "", "", systemActionRetries, systemRebootDelay)
_, ok := err.(ErrRedfishMissingConfig)
assert.True(t, ok)
}
@ -70,20 +71,20 @@ func TestNewClientMissingSystemID(t *testing.T) {
func TestNewClientNoRedfishMarking(t *testing.T) {
url := "https://localhost:2224/Systems/System.Embedded.1"
_, err := NewClient(url, false, false, "", "", systemActionRetries, systemRebootDelay)
_, err := NewClient(nodeName, url, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
}
func TestNewClientEmptyRedfishURL(t *testing.T) {
// Redfish URL cannot be empty when creating a client.
_, err := NewClient("", false, false, "", "", systemActionRetries, systemRebootDelay)
_, err := NewClient(nodeName, "", false, false, "", "", systemActionRetries, systemRebootDelay)
assert.Error(t, err)
}
func TestEjectVirtualMedia(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries+1, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries+1, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -140,7 +141,7 @@ func TestEjectVirtualMediaRetriesExceeded(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -179,7 +180,7 @@ func TestRebootSystem(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -215,7 +216,7 @@ func TestRebootSystemShutdownError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -242,7 +243,7 @@ func TestRebootSystemStartupError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -282,7 +283,7 @@ func TestRebootSystemTimeout(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -310,7 +311,7 @@ func TestSetBootSourceByTypeGetSystemError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -333,7 +334,7 @@ func TestSetBootSourceByTypeSetSystemError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -366,7 +367,7 @@ func TestSetBootSourceByTypeBootSourceUnavailable(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
ctx := SetAuth(context.Background(), "", "")
@ -404,7 +405,7 @@ func TestSetVirtualMediaEjectExistingMedia(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -450,7 +451,7 @@ func TestSetVirtualMediaEjectExistingMediaFailure(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
client.nodeID = nodeID
@ -486,7 +487,7 @@ func TestSetVirtualMediaGetSystemError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
ctx := SetAuth(context.Background(), "", "")
@ -509,7 +510,7 @@ func TestSetVirtualMediaInsertVirtualMediaError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
ctx := SetAuth(context.Background(), "", "")
@ -545,7 +546,7 @@ func TestSystemPowerOff(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.nodeID = nodeID
@ -577,7 +578,7 @@ func TestSystemPowerOffResetSystemError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.nodeID = nodeID
@ -600,7 +601,7 @@ func TestSystemPowerOn(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.nodeID = nodeID
@ -633,7 +634,7 @@ func TestSystemPowerOnResetSystemError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.nodeID = nodeID
@ -656,7 +657,7 @@ func TestSystemPowerStatusUnknown(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.nodeID = nodeID
@ -678,7 +679,7 @@ func TestSystemPowerStatusOn(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
ctx := SetAuth(context.Background(), "", "")
@ -701,7 +702,7 @@ func TestSystemPowerStatusOff(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.nodeID = nodeID
@ -724,7 +725,7 @@ func TestSystemPowerStatusPoweringOn(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.nodeID = nodeID
@ -747,7 +748,7 @@ func TestSystemPowerStatusPoweringOff(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.nodeID = nodeID
@ -770,7 +771,7 @@ func TestSystemPowerStatusGetSystemError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.nodeID = nodeID
@ -788,7 +789,7 @@ func TestWaitForPowerStateGetSystemFailed(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
ctx := SetAuth(context.Background(), "", "")
@ -811,7 +812,7 @@ func TestWaitForPowerStateNoRetries(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
ctx := SetAuth(context.Background(), "", "")
@ -837,7 +838,7 @@ func TestWaitForPowerStateWithRetries(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
ctx := SetAuth(context.Background(), "", "")
@ -867,7 +868,7 @@ func TestWaitForPowerStateRetriesExceeded(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
ctx := SetAuth(context.Background(), "", "")
@ -898,7 +899,7 @@ func TestWaitForPowerStateDifferentPowerState(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
ctx := SetAuth(context.Background(), "", "")
@ -923,7 +924,7 @@ func TestWaitForPowerStateDifferentPowerState(t *testing.T) {
func TestRemoteDirect(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
client, err := NewClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := NewClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
require.NoError(t, err)
client.RedfishAPI = m

View File

@ -135,14 +135,14 @@ func (c *Client) RemoteDirect(ctx context.Context, isoURL string) error {
}
// newClient returns a client with the capability to make Redfish requests.
func newClient(redfishURL string,
func newClient(nodeName string, redfishURL string,
insecure bool,
useProxy bool,
username string,
password string,
systemActionRetries int,
systemRebootDelay int) (*Client, error) {
genericClient, err := redfish.NewClient(redfishURL, insecure, useProxy, username, password,
genericClient, err := redfish.NewClient(nodeName, redfishURL, insecure, useProxy, username, password,
systemActionRetries, systemRebootDelay)
if err != nil {
return nil, err
@ -154,13 +154,13 @@ func newClient(redfishURL string,
}
// ClientFactory is a constructor for redfish ifc.Client implementation
var ClientFactory ifc.ClientFactory = func(redfishURL string,
var ClientFactory ifc.ClientFactory = func(nodeName, redfishURL string,
insecure bool,
useProxy bool,
username string,
password string,
systemActionRetries int,
systemRebootDelay int) (ifc.Client, error) {
return newClient(redfishURL, insecure, useProxy,
return newClient(nodeName, redfishURL, insecure, useProxy,
username, password, systemActionRetries, systemRebootDelay)
}

View File

@ -28,18 +28,19 @@ import (
)
const (
nodeName = "node-0"
redfishURL = "redfish+https://localhost/Systems/System.Embedded.1"
systemActionRetries = 0
systemRebootDelay = 0
)
func TestNewClient(t *testing.T) {
_, err := newClient(redfishURL, false, false, "username", "password", systemActionRetries, systemRebootDelay)
_, err := newClient(nodeName, redfishURL, false, false, "username", "password", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
}
func TestNewClientInterface(t *testing.T) {
c, err := ClientFactory(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
c, err := ClientFactory(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
assert.NotNil(t, c)
}
@ -47,7 +48,7 @@ func TestNewClientInterface(t *testing.T) {
func TestNewClientDefaultValues(t *testing.T) {
sysActRetr := 222
sysRebDel := 555
c, err := newClient(redfishURL, false, false, "", "", sysActRetr, sysRebDel)
c, err := newClient(nodeName, redfishURL, false, false, "", "", sysActRetr, sysRebDel)
assert.Equal(t, c.SystemActionRetries(), sysActRetr)
assert.Equal(t, c.SystemRebootDelay(), sysRebDel)
assert.NoError(t, err)
@ -56,7 +57,7 @@ func TestSetBootSourceByTypeGetSystemError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
client, err := newClient(redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
client, err := newClient(nodeName, redfishURL, false, false, "", "", systemActionRetries, systemRebootDelay)
assert.NoError(t, err)
ctx := redfish.SetAuth(context.Background(), "", "")

View File

@ -31,6 +31,8 @@ const (
"EXECUTOR:config.executorRef.kind,DOC ENTRYPOINT:config.documentEntryPoint"
// PlanListFormat is used to print tables with plan list
PlanListFormat = "NAMESPACE:metadata.namespace,NAME:metadata.name,DESCRIPTION:description"
// HostListFormat is used to print tables with host list
HostListFormat = "NodeName:nodename,NodeID:nodeid"
)
// PrintObjects prints suitable

View File

@ -24,7 +24,8 @@ import (
// MockClient is a fake Redfish client for unit testing.
type MockClient struct {
mock.Mock
nodeID string
nodeID string
nodeName string
}
// NodeID provides a stubbed method that can be mocked to test functions that use the Redfish client without
@ -40,6 +41,19 @@ func (m *MockClient) NodeID() string {
return args.String(0)
}
// NodeName provides a stubbed method that can be mocked to test functions that use the Redfish client without
// making any Redfish API calls or requiring the appropriate Redfish client settings.
//
// Example usage:
// client := redfishutils.NewClient()
// client.On("NodeName").Return(<return values>)
//
// err := client.NodeName()
func (m *MockClient) NodeName() string {
args := m.Called()
return args.String(0)
}
// EjectVirtualMedia provides a stubbed method that can be mocked to test functions that use the
// Redfish client without making any Redfish API calls or requiring the appropriate Redfish client
// settings.
@ -147,7 +161,7 @@ func (m *MockClient) RemoteDirect(ctx context.Context, isoURL string) error {
// NewClient returns a mocked Redfish client in order to test functions that use the Redfish client without making any
// Redfish API calls.
func NewClient(redfishURL string, insecure bool, useProxy bool, username string,
func NewClient(nodeName string, redfishURL string, insecure bool, useProxy bool, username string,
password string) (*MockClient, error) {
if redfishURL == "" {
return nil, redfish.ErrRedfishMissingConfig{What: "Redfish URL"}
@ -159,6 +173,6 @@ func NewClient(redfishURL string, insecure bool, useProxy bool, username string,
return nil, redfish.ErrRedfishMissingConfig{What: "management URL system ID"}
}
m := &MockClient{nodeID: systemID}
m := &MockClient{nodeID: systemID, nodeName: nodeName}
return m, nil
}