Add baremetal inventory implementation.
The implementation is inspired by pkg/remote module, and evolves it to be built on top of document bundles as well as makes it more command line oriented by adding RunOperation method. Change-Id: If95a007986eb61c977c1ddbe5a94cfbeefeac225 Relates-To: #397 Relates-To: #362 Relates-To: #359
This commit is contained in:
parent
c18db07043
commit
4698abad49
147
pkg/inventory/baremetal/baremetal.go
Normal file
147
pkg/inventory/baremetal/baremetal.go
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/errors"
|
||||
"opendev.org/airship/airshipctl/pkg/inventory/ifc"
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
remoteifc "opendev.org/airship/airshipctl/pkg/remote/ifc"
|
||||
"opendev.org/airship/airshipctl/pkg/remote/redfish"
|
||||
redfishdell "opendev.org/airship/airshipctl/pkg/remote/redfish/vendors/dell"
|
||||
)
|
||||
|
||||
// Inventory implements baremetal invenotry interface
|
||||
type Inventory struct {
|
||||
mgmtCfg config.ManagementConfiguration
|
||||
inventoryBundle document.Bundle
|
||||
}
|
||||
|
||||
var _ ifc.BaremetalInventory = Inventory{}
|
||||
|
||||
// NewInventory returns inventory implementation based on BaremetalHost objects
|
||||
func NewInventory(
|
||||
mgmtCfg config.ManagementConfiguration,
|
||||
inventoryBundle document.Bundle) ifc.BaremetalInventory {
|
||||
return Inventory{
|
||||
mgmtCfg: mgmtCfg,
|
||||
inventoryBundle: inventoryBundle,
|
||||
}
|
||||
}
|
||||
|
||||
// Select selects hosts based on given selector
|
||||
func (i Inventory) Select(selector ifc.BaremetalHostSelector) ([]remoteifc.Client, error) {
|
||||
log.Debugf("Using selector %v to filter baremetal hosts", selector)
|
||||
bmhSelector := toDocumentSelector(selector)
|
||||
docs, err := i.inventoryBundle.Select(bmhSelector)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to find BaremetalHosts")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("Baremetal hosts count that matched the selector '%v' is '%d'", selector, len(docs))
|
||||
hostList := []remoteifc.Client{}
|
||||
for _, doc := range docs {
|
||||
host, err := i.newHost(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hostList = append(hostList, host)
|
||||
}
|
||||
|
||||
return hostList, nil
|
||||
}
|
||||
|
||||
// SelectOne selects single host based on given selector, if more than or less than one host is found
|
||||
// error is returned
|
||||
func (i Inventory) SelectOne(selector ifc.BaremetalHostSelector) (remoteifc.Client, error) {
|
||||
log.Debugf("Using selector %v to filter one baremetal host", selector)
|
||||
bmhSelector := toDocumentSelector(selector)
|
||||
|
||||
doc, err := i.inventoryBundle.SelectOne(bmhSelector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return i.newHost(doc)
|
||||
}
|
||||
|
||||
// RunOperation runs specified operation against the hosts that would be filtered by selector.
|
||||
// Options are ignored for now, when we implement concurency, they will be used.
|
||||
func (i Inventory) RunOperation(
|
||||
ctx context.Context,
|
||||
op ifc.BaremetalOperation,
|
||||
selector ifc.BaremetalHostSelector,
|
||||
_ ifc.BaremetalBatchRunOptions) error {
|
||||
return errors.ErrNotImplemented{What: "RunOperation of the baremetal inventory interface"}
|
||||
}
|
||||
|
||||
// Host implements baremetal host interface
|
||||
type Host struct {
|
||||
remoteifc.Client
|
||||
}
|
||||
|
||||
var _ remoteifc.Client = Host{}
|
||||
|
||||
func (i Inventory) newHost(doc document.Document) (Host, error) {
|
||||
address, err := document.GetBMHBMCAddress(doc)
|
||||
if err != nil {
|
||||
return Host{}, err
|
||||
}
|
||||
|
||||
username, password, err := document.GetBMHBMCCredentials(doc, i.inventoryBundle)
|
||||
if err != nil {
|
||||
return Host{}, err
|
||||
}
|
||||
|
||||
var clientFactory remoteifc.ClientFactory
|
||||
switch i.mgmtCfg.Type {
|
||||
case redfish.ClientType:
|
||||
clientFactory = redfish.ClientFactory
|
||||
case redfishdell.ClientType:
|
||||
clientFactory = redfishdell.ClientFactory
|
||||
default:
|
||||
return Host{}, ErrRemoteDriverNotSupported{
|
||||
BMHName: doc.GetName(),
|
||||
BMHNamespace: doc.GetNamespace(),
|
||||
RemoteType: i.mgmtCfg.Type,
|
||||
}
|
||||
}
|
||||
|
||||
client, err := clientFactory(
|
||||
address,
|
||||
i.mgmtCfg.Insecure,
|
||||
i.mgmtCfg.UseProxy,
|
||||
username,
|
||||
password,
|
||||
i.mgmtCfg.SystemActionRetries,
|
||||
i.mgmtCfg.SystemRebootDelay)
|
||||
if err != nil {
|
||||
return Host{}, err
|
||||
}
|
||||
return Host{Client: client}, nil
|
||||
}
|
||||
|
||||
func toDocumentSelector(selector ifc.BaremetalHostSelector) document.Selector {
|
||||
return document.NewSelector().
|
||||
ByKind(document.BareMetalHostKind).
|
||||
ByLabel(selector.LabelSelector).
|
||||
ByName(selector.Name).
|
||||
ByNamespace(selector.Namespace)
|
||||
}
|
127
pkg/inventory/baremetal/baremetal_test.go
Normal file
127
pkg/inventory/baremetal/baremetal_test.go
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/inventory/ifc"
|
||||
)
|
||||
|
||||
func TestSelect(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, remoteDriver, expectedErr string
|
||||
expectedHosts int
|
||||
|
||||
selector ifc.BaremetalHostSelector
|
||||
}{
|
||||
{
|
||||
name: "success return one host",
|
||||
remoteDriver: "redfish-dell",
|
||||
expectedHosts: 1,
|
||||
selector: (ifc.BaremetalHostSelector{}).ByName("master-0"),
|
||||
},
|
||||
{
|
||||
name: "success return multiple host",
|
||||
remoteDriver: "redfish",
|
||||
expectedHosts: 2,
|
||||
selector: (ifc.BaremetalHostSelector{}).ByLabel("host-group=control-plane"),
|
||||
},
|
||||
{
|
||||
name: "error remote driver not supported",
|
||||
remoteDriver: "should return error",
|
||||
expectedErr: "not supported",
|
||||
selector: (ifc.BaremetalHostSelector{}).ByLabel("host-group=control-plane"),
|
||||
},
|
||||
{
|
||||
name: "error no credentials",
|
||||
remoteDriver: "redfish",
|
||||
expectedErr: "no field named",
|
||||
selector: (ifc.BaremetalHostSelector{}).ByName("no-creds"),
|
||||
},
|
||||
{
|
||||
name: "error no hosts found",
|
||||
remoteDriver: "redfish",
|
||||
expectedHosts: 0,
|
||||
selector: (ifc.BaremetalHostSelector{}).ByName("no such host"),
|
||||
},
|
||||
}
|
||||
|
||||
bundle := testBundle(t)
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mgmCfg := config.ManagementConfiguration{Type: tt.remoteDriver}
|
||||
inventory := NewInventory(mgmCfg, bundle)
|
||||
hosts, err := inventory.Select(tt.selector)
|
||||
if tt.expectedErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, hosts, tt.expectedHosts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectOne(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, remoteDriver, expectedErr string
|
||||
|
||||
selector ifc.BaremetalHostSelector
|
||||
}{
|
||||
{
|
||||
name: "success return one host",
|
||||
remoteDriver: "redfish-dell",
|
||||
selector: (ifc.BaremetalHostSelector{}).ByName("master-0"),
|
||||
},
|
||||
{
|
||||
name: "error return multiple host",
|
||||
remoteDriver: "redfish",
|
||||
expectedErr: "found more than one document",
|
||||
selector: (ifc.BaremetalHostSelector{}).ByLabel("host-group=control-plane"),
|
||||
},
|
||||
}
|
||||
|
||||
bundle := testBundle(t)
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mgmCfg := config.ManagementConfiguration{Type: tt.remoteDriver}
|
||||
inventory := NewInventory(mgmCfg, bundle)
|
||||
host, err := inventory.SelectOne(tt.selector)
|
||||
if tt.expectedErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, host)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testBundle(t *testing.T) document.Bundle {
|
||||
t.Helper()
|
||||
bundle, err := document.NewBundleByPath("testdata")
|
||||
require.NoError(t, err)
|
||||
return bundle
|
||||
}
|
42
pkg/inventory/baremetal/errors.go
Normal file
42
pkg/inventory/baremetal/errors.go
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/inventory/ifc"
|
||||
)
|
||||
|
||||
// ErrRemoteDriverNotSupported is returned when remote driver is not supported for baremetal host
|
||||
type ErrRemoteDriverNotSupported struct {
|
||||
BMHName string
|
||||
BMHNamespace string
|
||||
RemoteType string
|
||||
}
|
||||
|
||||
func (e ErrRemoteDriverNotSupported) Error() string {
|
||||
return fmt.Sprintf("Baremetal host named '%s' in namespace '%s' remote driver '%s' not supported",
|
||||
e.BMHName, e.BMHNamespace, e.RemoteType)
|
||||
}
|
||||
|
||||
// ErrNoBaremetalHostsFound is returned when no baremetal hosts matched the selector
|
||||
type ErrNoBaremetalHostsFound struct {
|
||||
Selector ifc.BaremetalHostSelector
|
||||
}
|
||||
|
||||
func (e ErrNoBaremetalHostsFound) Error() string {
|
||||
return fmt.Sprintf("No baremetal hosts matched selector %v", e.Selector)
|
||||
}
|
71
pkg/inventory/baremetal/testdata/hosts.yaml
vendored
Normal file
71
pkg/inventory/baremetal/testdata/hosts.yaml
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
---
|
||||
apiVersion: metal3.io/v1alpha1
|
||||
kind: BareMetalHost
|
||||
metadata:
|
||||
labels:
|
||||
airshipit.org/ephemeral-node: "true"
|
||||
name: master-0
|
||||
spec:
|
||||
online: true
|
||||
bootMACAddress: 00:3b:8b:0c:ec:8b
|
||||
bmc:
|
||||
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/ephemeral
|
||||
credentialsName: master-0-bmc-secret
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
name: master-0-bmc-secret
|
||||
type: Opaque
|
||||
data:
|
||||
username: YWRtaW4=
|
||||
password: cGFzc3dvcmQ=
|
||||
---
|
||||
apiVersion: metal3.io/v1alpha1
|
||||
kind: BareMetalHost
|
||||
metadata:
|
||||
labels:
|
||||
host-group: "control-plane"
|
||||
name: master-1
|
||||
spec:
|
||||
online: true
|
||||
bootMACAddress: 00:3b:8b:0c:ec:8b
|
||||
bmc:
|
||||
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/node-master-1
|
||||
credentialsName: master-1-bmc-secret
|
||||
---
|
||||
apiVersion: metal3.io/v1alpha1
|
||||
kind: BareMetalHost
|
||||
metadata:
|
||||
labels:
|
||||
host-group: "control-plane"
|
||||
name: master-2
|
||||
spec:
|
||||
online: true
|
||||
bootMACAddress: 00:3b:8b:0c:ec:8b
|
||||
bmc:
|
||||
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/node-master-2
|
||||
credentialsName: master-1-bmc-secret
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
airshipit.org/ephemeral-node: "true"
|
||||
name: master-1-bmc-secret
|
||||
type: Opaque
|
||||
data:
|
||||
username: YWRtaW4=
|
||||
password: cGFzc3dvcmQ=
|
||||
---
|
||||
apiVersion: metal3.io/v1alpha1
|
||||
kind: BareMetalHost
|
||||
metadata:
|
||||
name: no-creds
|
||||
spec:
|
||||
online: true
|
||||
bootMACAddress: 00:3b:8b:0c:ec:8b
|
||||
bmc:
|
||||
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/test-node
|
||||
...
|
2
pkg/inventory/baremetal/testdata/kustomization.yaml
vendored
Normal file
2
pkg/inventory/baremetal/testdata/kustomization.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
resources:
|
||||
- hosts.yaml
|
Loading…
Reference in New Issue
Block a user