mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-21 08:33:23 +00:00
Add nvos onboarding
This commit is contained in:
@@ -79,6 +79,7 @@ import confluent.discovery.handlers.xcc as xcc
|
||||
import confluent.discovery.handlers.xcc3 as xcc3
|
||||
import confluent.discovery.handlers.smm3 as smm3
|
||||
import confluent.discovery.handlers.megarac as megarac
|
||||
import confluent.discovery.handlers.nvos as nvos
|
||||
import confluent.discovery.handlers.eureka as eureka
|
||||
import confluent.exceptions as exc
|
||||
import confluent.log as log
|
||||
@@ -134,7 +135,7 @@ nodehandlers = {
|
||||
'generic-redfish': None,
|
||||
'generic-https': None,
|
||||
'generic-ssh': None,
|
||||
'nvos-switch': None,
|
||||
'nvos-switch': nvos,
|
||||
#'openbmc': None,
|
||||
'service:io-device.Lenovo:management-module': None,
|
||||
'service:thinkagile-storage': cpstorage,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import asyncssh
|
||||
import confluent.sshclient as sshclient
|
||||
import confluent.discovery.handlers.generic as generic
|
||||
import confluent.netutil as netutil
|
||||
|
||||
class NodeHandler(generic.NodeHandler):
|
||||
devname = 'NVOS Switch'
|
||||
|
||||
def get_firmware_default_account_info(self):
|
||||
return ('admin', 'admin')
|
||||
|
||||
async def config(self, nodename):
|
||||
ncreds = self.configmanager.get_node_attributes(nodename, ['secret.adminuser', 'secret.adminpassword'], decrypt=True)
|
||||
ncreds = ncreds.get(nodename, {})
|
||||
adminuser = ncreds.get('secret.adminuser', {}).get('value', 'admin')
|
||||
if isinstance(adminuser, bytes):
|
||||
adminuser = adminuser.decode('utf-8')
|
||||
if adminuser != 'admin':
|
||||
raise ValueError("nvos support does not support renaming the admin user")
|
||||
adminpass = ncreds.get('secret.adminpassword', {}).get('value')
|
||||
if isinstance(adminpass, bytes):
|
||||
adminpass = adminpass.decode('utf-8')
|
||||
if not adminpass:
|
||||
raise ValueError("secret.adminpassword must be specified for " + nodename)
|
||||
myaddress = self.info.get('addresses', [[None]])[0][0]
|
||||
if not myaddress:
|
||||
raise ValueError("No address found for request")
|
||||
hwaddr = self.info.get('hwaddr')
|
||||
myaddress = self.info.get('addresses', [[None]])[0][0]
|
||||
if myaddress:
|
||||
attrset = {'deployment.client_ip': myaddress}
|
||||
if hwaddr:
|
||||
myip = await netutil.my_ip_facing(myaddress)
|
||||
if myip:
|
||||
niccfg = await netutil.get_nic_config(self.configmanager, nodename, serverip=myip)
|
||||
cfgname = niccfg.get('config_name', None)
|
||||
if cfgname:
|
||||
attrname = f'net.{cfgname}.hwaddr'
|
||||
else:
|
||||
attrname = 'net.hwaddr'
|
||||
attrset[attrname] = hwaddr
|
||||
await self.configmanager.set_node_attributes({nodename: attrset})
|
||||
try:
|
||||
async with sshclient.connect(myaddress, username=adminuser, password=adminpass, nodename=nodename, configmanager=self.configmanager) as conn:
|
||||
pass
|
||||
except asyncssh.misc.PermissionDenied: # try admin, admin
|
||||
async with sshclient.connect(myaddress, username='admin', password='admin', nodename=nodename, configmanager=self.configmanager) as conn:
|
||||
res = await conn.run(f"nv set system aaa user admin password '{adminpass}'")
|
||||
if res.exit_status == 0:
|
||||
res = await conn.run(f'nv config apply')
|
||||
if res.exit_status != 0:
|
||||
raise RuntimeError("Failed to apply NVOS configuration")
|
||||
|
||||
@@ -140,6 +140,17 @@ def mac2lla(mac):
|
||||
lla = f'fe80::{first_half[0:4]}:{first_half[4:]}ff:fe{second_half[0:2]}:{second_half[2:6]}'
|
||||
return lla
|
||||
|
||||
async def my_ip_facing(target):
|
||||
addrinfo = await asyncio.get_running_loop().getaddrinfo(
|
||||
target, 0, type=socket.SOCK_DGRAM)
|
||||
family, socktype, proto, _, destination = addrinfo[0]
|
||||
myip = None
|
||||
with socket.socket(family, socktype, proto) as sock:
|
||||
# UDP connect selects the route and local address without
|
||||
# transmitting a packet.
|
||||
sock.connect(destination)
|
||||
myip = sock.getsockname()[0]
|
||||
return myip
|
||||
|
||||
async def mac2ip(mac):
|
||||
lla = None
|
||||
@@ -778,6 +789,7 @@ async def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
'ipv6_address': None,
|
||||
'ipv6_method': None,
|
||||
'lease_time': None,
|
||||
'config_name': None,
|
||||
}
|
||||
myaddrs = []
|
||||
if ifidx is not None:
|
||||
@@ -876,6 +888,7 @@ async def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
if ((isremote and ipn_on_same_subnet(fam, clientipn, candipn, int(candprefix)))
|
||||
or ipn_on_same_subnet(fam, bootsvrip, candipn, prefix)):
|
||||
bestsrvbyfam[fam] = svrip
|
||||
cfgdata['config_name'] = candidate
|
||||
cfgdata['ipv{}_address'.format(nver)] = candip
|
||||
cfgdata['ipv{}_method'.format(nver)] = ipmethod
|
||||
cfgdata['ipv{}_gateway'.format(nver)] = cfgbyname[candidate].get(
|
||||
|
||||
@@ -107,7 +107,7 @@ class _MyClient(asyncssh.SSHClient):
|
||||
self.confluent_custom_ctx = ctx
|
||||
|
||||
|
||||
def connect(target, context=None, disable_hostkey_validation=False, known_hosts=(), nodename=None, configmanager=None, keyattrib='pubkeys.ssh', **kwargs):
|
||||
def connect(target, context=None, disable_hostkey_validation=False, known_hosts=(), nodename=None, configmanager=None, keyattrib='pubkeys.ssh', quickquit=False, **kwargs):
|
||||
if context is None:
|
||||
context = {}
|
||||
def make_client():
|
||||
@@ -120,19 +120,24 @@ def connect(target, context=None, disable_hostkey_validation=False, known_hosts=
|
||||
|
||||
client.confluent_set_context(context)
|
||||
return client
|
||||
login_timeout=30
|
||||
connect_timeout=30
|
||||
if quickquit:
|
||||
login_timeout=2
|
||||
connect_timeout=2
|
||||
sco = asyncssh.SSHClientConnectionOptions(
|
||||
client_factory=make_client,
|
||||
x509_trusted_cert_paths=None,
|
||||
known_hosts=known_hosts,
|
||||
login_timeout=3,
|
||||
connect_timeout=2)
|
||||
login_timeout=login_timeout,
|
||||
connect_timeout=connect_timeout)
|
||||
return asyncssh.connect(target, options=sco, **kwargs)
|
||||
|
||||
|
||||
async def get_ssh_banner(target):
|
||||
mycontext = {'nologon': True}
|
||||
try:
|
||||
async with connect(target, disable_hostkey_validation=True, context=mycontext):
|
||||
async with connect(target, disable_hostkey_validation=True, quickquit=True, context=mycontext):
|
||||
pass
|
||||
except _CancelSsh:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user