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(NewPowerStatusCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewRebootCommand(cfgFactory, options)) baremetalRootCmd.AddCommand(NewRebootCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewRemoteDirectCommand(cfgFactory, options)) baremetalRootCmd.AddCommand(NewRemoteDirectCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewListHostsCommand(cfgFactory, options))
return baremetalRootCmd return baremetalRootCmd
} }

View File

@ -59,6 +59,11 @@ func TestBaremetal(t *testing.T) {
CmdLine: "-h", CmdLine: "-h",
Cmd: baremetal.NewRemoteDirectCommand(nil, &inventory.CommandOptions{}), Cmd: baremetal.NewRemoteDirectCommand(nil, &inventory.CommandOptions{}),
}, },
{
Name: "baremetal-list-hosts-with-help",
CmdLine: "-h",
Cmd: baremetal.NewListHostsCommand(nil, &inventory.CommandOptions{}),
},
} }
for _, tt := range tests { 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: Available Commands:
ejectmedia Airshipctl command to eject virtual media attached to a bare metal host ejectmedia Airshipctl command to eject virtual media attached to a bare metal host
help Help about any command help Help about any command
list-hosts Airshipctl command to list bare metal host(s)
poweroff Airshipctl command to shutdown bare metal host(s) poweroff Airshipctl command to shutdown bare metal host(s)
poweron Airshipctl command to power on host(s) poweron Airshipctl command to power on host(s)
powerstatus Airshipctl command to retrieve the power status of a bare metal host 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 <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 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 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 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 * :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
airshipctl_baremetal_ejectmedia airshipctl_baremetal_ejectmedia
airshipctl_baremetal_list-hosts
airshipctl_baremetal_poweroff airshipctl_baremetal_poweroff
airshipctl_baremetal_poweron airshipctl_baremetal_poweron
airshipctl_baremetal_powerstatus airshipctl_baremetal_powerstatus

View File

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

View File

@ -35,10 +35,6 @@ spec:
of an object. Servers should convert recognized schemas to the latest 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' internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string type: string
env-vars:
description: EnvVars if set to true, allows to source variables for cluster-api
components for environment variables.
type: boolean
images: images:
additionalProperties: additionalProperties:
description: ImageMeta is part of clusterctl config description: ImageMeta is part of clusterctl config
@ -53,66 +49,24 @@ spec:
description: InitOptions container with exposed clusterctl InitOptions description: InitOptions container with exposed clusterctl InitOptions
properties: properties:
bootstrap-providers: bootstrap-providers:
description: BootstrapProviders and versions (e.g. kubeadm:v0.3.0) description: BootstrapProviders and versions (comma separated, e.g.
to add to the management cluster. If unspecified, the kubeadm bootstrap kubeadm:v0.3.0) to add to the management cluster. If unspecified,
provider's latest release is used. the kubeadm bootstrap provider's latest release is used.
items:
type: string type: string
type: array
control-plane-providers: control-plane-providers:
description: ControlPlaneProviders and versions (e.g. kubeadm:v0.3.0) description: ControlPlaneProviders and versions (comma separated,
to add to the management cluster. If unspecified, the kubeadm control e.g. kubeadm:v0.3.0) to add to the management cluster. If unspecified,
plane provider latest release is used. the kubeadm control plane provider latest release is used.
items:
type: string type: string
type: array
core-provider: core-provider:
description: CoreProvider version (e.g. cluster-api:v0.3.0) to add description: CoreProvider version (e.g. cluster-api:v0.3.0) to add
to the management cluster. If unspecified, the cluster-api core to the management cluster. If unspecified, the cluster-api core
provider's latest release is used. provider's latest release is used.
type: string type: string
infrastructure-providers: infrastructure-providers:
description: InfrastructureProviders and versions (e.g. aws:v0.5.0) description: InfrastructureProviders and versions (comma separated,
to add to the management cluster. e.g. aws:v0.5.0,metal3:v0.4.0) to add to the management cluster.
items:
type: string 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
type: object type: object
kind: kind:
description: 'Kind is a string value representing the REST resource this description: 'Kind is a string value representing the REST resource this
@ -125,39 +79,22 @@ spec:
description: MoveOptions carries the options supported by move. description: MoveOptions carries the options supported by move.
properties: properties:
namespace: namespace:
description: The namespace where the workload cluster is hosted. If description: Namespace where the objects describing the workload cluster
unspecified, the target context's namespace is used. exists. If unspecified, the current namespace will be used.
type: string type: string
type: object type: object
providers: providers:
items: items:
description: Provider is part of clusterctl config description: Provider is part of clusterctl config
properties: 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: name:
type: string type: string
type: type:
type: string type: string
url: url:
description: URL can contain remote URL of upstream Provider or
relative to target path of the manifest
type: string 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: required:
- name - name
- type - 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 type: string
group: group:
type: string 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: kind:
type: string type: string
labelSelector: labelSelector:
@ -59,8 +64,11 @@ spec:
It matches with the resource labels. It matches with the resource labels.
type: string type: string
name: name:
description: Name of the resource.
type: string type: string
namespace: namespace:
description: Namespace the resource belongs to, if it can
belong to a namespace.
type: string type: string
version: version:
type: string type: string
@ -90,6 +98,11 @@ spec:
type: string type: string
group: group:
type: string 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: kind:
type: string type: string
labelSelector: labelSelector:
@ -98,8 +111,11 @@ spec:
It matches with the resource labels. It matches with the resource labels.
type: string type: string
name: name:
description: Name of the resource.
type: string type: string
namespace: namespace:
description: Namespace the resource belongs to, if it can
belong to a namespace.
type: string type: string
version: version:
type: string type: string

View File

@ -75,10 +75,27 @@ spec:
for the server's certificate. This will make your HTTPS for the server's certificate. This will make your HTTPS
connections insecure. connections insecure.
type: boolean 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: server:
description: Server is the address of the kubernetes cluster description: Server is the address of the kubernetes cluster
(https://hostname:port). (https://hostname:port).
type: string 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: required:
- server - server
type: object type: object
@ -288,8 +305,24 @@ spec:
- value - value
type: object type: object
type: array 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: required:
- command - command
- provideClusterInfo
type: object type: object
extensions: extensions:
description: Extensions holds additional information. This description: Extensions holds additional information. This

View File

@ -31,6 +31,16 @@ spec:
description: ApplyConfig provides instructions on how to apply resources description: ApplyConfig provides instructions on how to apply resources
to kubernetes cluster to kubernetes cluster
properties: properties:
context:
type: string
debug:
type: boolean
druRun:
type: boolean
kubeconfig:
type: string
phaseName:
type: string
pruneOptions: pruneOptions:
description: ApplyPruneOptions provides instructions how to prune description: ApplyPruneOptions provides instructions how to prune
for kubernetes resources for kubernetes resources
@ -42,6 +52,35 @@ spec:
description: ApplyWaitOptions provides instructions how to wait for description: ApplyWaitOptions provides instructions how to wait for
kubernetes resources kubernetes resources
properties: 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: timeout:
description: Timeout in seconds description: Timeout in seconds
type: integer type: integer

View File

@ -34,8 +34,30 @@ spec:
documentEntryPoint: documentEntryPoint:
type: string type: string
executorRef: executorRef:
description: ObjectReference contains enough information to let you description: 'ObjectReference contains enough information to let you
inspect or modify the referred object. 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: properties:
apiVersion: apiVersion:
description: API version of the referent. description: API version of the referent.

View File

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

View File

@ -83,9 +83,9 @@ spec:
description: capi_images defines collections of images used by cluster description: capi_images defines collections of images used by cluster
API. The name of each key in this section should correspond to the 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". airshipctl function in which the images will be used, such as "capm3".
Each capi_image object must have a "manager" object, which must have Each capi_image object must have a "manager" object, which must
"repository" and "tag" properties defined. capi_images may also include have "repository" and "tag" properties defined. capi_images may
an optional "ipam-manager" or "auth_proxy" object, also include an optional "ipam-manager" or "auth_proxy" object,
which must also have "repository" and "tag" properties defined. which must also have "repository" and "tag" properties defined.
type: object type: object
charts: 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 // +build !ignore_autogenerated
/* /*
@ -708,6 +709,31 @@ func (in *Gvk) DeepCopy() *Gvk {
return out 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostNetworkingSpec) DeepCopyInto(out *HostNetworkingSpec) { func (in *HostNetworkingSpec) DeepCopyInto(out *HostNetworkingSpec) {
*out = *in *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, address,
i.mgmtCfg.Insecure, i.mgmtCfg.Insecure,
i.mgmtCfg.UseProxy, i.mgmtCfg.UseProxy,

View File

@ -20,8 +20,18 @@ import (
"io" "io"
"time" "time"
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
"opendev.org/airship/airshipctl/pkg/inventory/ifc" "opendev.org/airship/airshipctl/pkg/inventory/ifc"
remoteifc "opendev.org/airship/airshipctl/pkg/remote/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 // CommandOptions is used to store common variables from cmd flags for baremetal command group
@ -37,6 +47,18 @@ type CommandOptions struct {
Inventory ifc.Inventory 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 // NewOptions options constructor
func NewOptions(i ifc.Inventory) *CommandOptions { func NewOptions(i ifc.Inventory) *CommandOptions {
return &CommandOptions{ return &CommandOptions{
@ -61,6 +83,22 @@ func (o *CommandOptions) validateSingleHostAction() error {
return nil 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 // BMHAction performs an action against BaremetalHost objects
func (o *CommandOptions) BMHAction(op ifc.BaremetalOperation) error { func (o *CommandOptions) BMHAction(op ifc.BaremetalOperation) error {
if err := o.validateBMHAction(); err != nil { if err := o.validateBMHAction(); err != nil {
@ -124,9 +162,32 @@ func (o *CommandOptions) getHost() (remoteifc.Client, error) {
return bmhInventory.SelectOne(o.selector()) 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 { func (o *CommandOptions) selector() ifc.BaremetalHostSelector {
return (ifc.BaremetalHostSelector{}). return (ifc.BaremetalHostSelector{}).
ByLabel(o.Labels). ByLabel(o.Labels).
ByName(o.Name). ByName(o.Name).
ByNamespace(o.Namespace) 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"
"opendev.org/airship/airshipctl/pkg/inventory/ifc" "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/power"
"opendev.org/airship/airshipctl/pkg/remote/redfish"
mockinventory "opendev.org/airship/airshipctl/testutil/inventory" mockinventory "opendev.org/airship/airshipctl/testutil/inventory"
"opendev.org/airship/airshipctl/testutil/redfishutils" "opendev.org/airship/airshipctl/testutil/redfishutils"
) )
const testNode = "node-0" 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) { func TestCommandOptions(t *testing.T) {
t.Run("error BMHAction bmh inventory", func(t *testing.T) { t.Run("error BMHAction bmh inventory", func(t *testing.T) {
inv := &mockinventory.MockInventory{} inv := &mockinventory.MockInventory{}

View File

@ -44,6 +44,8 @@ const (
BaremetalOperationPowerOn BaremetalOperation = "power-on" BaremetalOperationPowerOn BaremetalOperation = "power-on"
// BaremetalOperationEjectVirtualMedia eject virtual media // BaremetalOperationEjectVirtualMedia eject virtual media
BaremetalOperationEjectVirtualMedia BaremetalOperation = "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 // BaremetalBatchRunOptions are options to be passed to RunOperation, this is to be

View File

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

View File

@ -25,6 +25,7 @@ import (
type Client interface { type Client interface {
EjectVirtualMedia(context.Context) error EjectVirtualMedia(context.Context) error
NodeID() string NodeID() string
NodeName() string
RebootSystem(context.Context) error RebootSystem(context.Context) error
SetBootSourceByType(context.Context) error SetBootSourceByType(context.Context) error
SystemPowerOff(context.Context) error SystemPowerOff(context.Context) error
@ -38,7 +39,7 @@ type Client interface {
} }
// ClientFactory is a function to be used // ClientFactory is a function to be used
type ClientFactory func( type ClientFactory func(name string,
redfishURL string, redfishURL string,
insecure bool, useProxy bool, insecure bool, useProxy bool,
username string, password string, 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. // Client holds details about a Redfish out-of-band system required for out-of-band management.
type Client struct { type Client struct {
nodeID string nodeID string
nodeName string
username string username string
password string password string
redfishURL string redfishURL string
@ -53,6 +54,11 @@ func (c *Client) NodeID() string {
return c.nodeID 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 // SystemActionRetries returns number of attempts to reach host during reboot process and ejecting virtual media
func (c *Client) SystemActionRetries() int { func (c *Client) SystemActionRetries() int {
return c.systemActionRetries 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. // NewClient returns a client with the capability to make Redfish requests.
func NewClient(redfishURL string, func NewClient(nodeName string, redfishURL string,
insecure bool, insecure bool,
useProxy bool, useProxy bool,
username string, username string,
@ -410,6 +416,7 @@ func NewClient(redfishURL string,
c := &Client{ c := &Client{
nodeID: systemID, nodeID: systemID,
nodeName: nodeName,
RedfishAPI: redfishClient.NewAPIClient(cfg).DefaultApi, RedfishAPI: redfishClient.NewAPIClient(cfg).DefaultApi,
RedfishCFG: cfg, RedfishCFG: cfg,
systemActionRetries: systemActionRetries, systemActionRetries: systemActionRetries,
@ -427,13 +434,13 @@ func NewClient(redfishURL string,
} }
// ClientFactory is a constructor for redfish ifc.Client implementation // 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, insecure bool,
useProxy bool, useProxy bool,
username string, username string,
password string, password string,
systemActionRetries int, systemActionRetries int,
systemRebootDelay int) (ifc.Client, error) { systemRebootDelay int) (ifc.Client, error) {
return NewClient(redfishURL, insecure, useProxy, return NewClient(nodeName, redfishURL, insecure, useProxy,
username, password, systemActionRetries, systemRebootDelay) username, password, systemActionRetries, systemRebootDelay)
} }

View File

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

View File

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

View File

@ -31,6 +31,8 @@ const (
"EXECUTOR:config.executorRef.kind,DOC ENTRYPOINT:config.documentEntryPoint" "EXECUTOR:config.executorRef.kind,DOC ENTRYPOINT:config.documentEntryPoint"
// PlanListFormat is used to print tables with plan list // PlanListFormat is used to print tables with plan list
PlanListFormat = "NAMESPACE:metadata.namespace,NAME:metadata.name,DESCRIPTION:description" 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 // PrintObjects prints suitable

View File

@ -25,6 +25,7 @@ import (
type MockClient struct { type MockClient struct {
mock.Mock 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 // 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) 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 // 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 // Redfish client without making any Redfish API calls or requiring the appropriate Redfish client
// settings. // 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 // NewClient returns a mocked Redfish client in order to test functions that use the Redfish client without making any
// Redfish API calls. // 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) { password string) (*MockClient, error) {
if redfishURL == "" { if redfishURL == "" {
return nil, redfish.ErrRedfishMissingConfig{What: "Redfish URL"} 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"} return nil, redfish.ErrRedfishMissingConfig{What: "management URL system ID"}
} }
m := &MockClient{nodeID: systemID} m := &MockClient{nodeID: systemID, nodeName: nodeName}
return m, nil return m, nil
} }