Skip to content

Commit

Permalink
ksushy: enable bootonce for kvm
Browse files Browse the repository at this point in the history
  • Loading branch information
karmab committed Feb 19, 2025
1 parent 2f6aee2 commit 537eaff
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 65 deletions.
7 changes: 0 additions & 7 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2208,7 +2208,6 @@ ksushy can be launched manually for testing purposes but the following command c
- KSUSHY_DEBUG: enable debug
- KSUSHY_USER: username for authentication
- KSUSHY_PASSWORD: password for authentication
- KSUSHY_BOOTONCE: enable bootonce hack

```
kcli create sushy-service
Expand Down Expand Up @@ -2236,12 +2235,6 @@ For plugging an iso, only virtualization providers can be used.

When deploying the service, an username and password can be specified for securing access through basic authentication

### Bootonce hack

As virtualization providers don't provide a way to restart in a given iso only one time (and because in kcli design, we don't mess with boot orders), the `bootonce` flags allows to overcome this by emptying the primary disk of the corresponding vm when receiving a one time iso reboot via redfish.

Note that this is destructive and as such considered a hack, but can be useful for testing purposes which involve reinstalling several times a given node.

# Ansible support

klist.py is provided as a dynamic inventory for ansible.
Expand Down
8 changes: 0 additions & 8 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2379,7 +2379,6 @@ ksushy can be launched manually for testing purposes but the following command c
- KSUSHY_DEBUG: enable debug
- KSUSHY_USER: username for authentication
- KSUSHY_PASSWORD: password for authentication
- KSUSHY_BOOTONCE: enable bootonce hack

::

Expand Down Expand Up @@ -2409,13 +2408,6 @@ Restricting access

When deploying the service, an username and password can be specified for securing access through basic authentication

Bootonce hack
~~~~~~~~~~~~~

As virtualization providers don’t provide a way to restart in a given iso only one time (and because in kcli design, we don’t mess with boot orders), the ``bootonce`` flags allows to overcome this by emptying the primary disk of the corresponding vm when receiving a one time iso reboot via redfish.

Note that this is destructive and as such considered a hack, but can be useful for testing purposes which involve reinstalling several times a given node.

Ansible support
===============

Expand Down
28 changes: 1 addition & 27 deletions kvirt/ksushy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,6 @@ def system_resource_get(client, name):
@app.route('/redfish/v1/Systems/<client>/<name>', method='PATCH')
@auth_basic(credentials)
def system_resource(client, name):
if not self.bootonce:
return
boot = request.json.get('Boot', {})
if not boot:
response.status = 400
Expand All @@ -148,29 +146,6 @@ def system_resource(client, name):
error(msg)
return msg
else:
baseconfig = Kbaseconfig()
if client not in baseconfig.clients:
response.status = 404
msg = f'Client {client} not found'
error(msg)
return msg
config = Kconfig(client)
k = config.k
info = k.info(name)
pprint('Forcing to boot from ISO by deleting primary disk')
try:
pool = config.pool
diskname = os.path.basename(info['disks'][0]['path'])
size = info['disks'][0]['size']
interface = info['disks'][0]['format']
k.stop(name)
k.delete_disk(name=name, diskname=diskname, pool=pool)
k.add_disk(name=name, size=size, pool=pool, interface=interface, diskname=diskname)
except Exception as e:
msg = f'Failed to set boot from virtualcd once. Hit {e}'
error(msg)
response.status = 400
return msg
response.status = 204
return ''

Expand Down Expand Up @@ -225,7 +200,7 @@ def system_reset_action(client, name):
if reset_type in ['On', 'ForceRestart']:
try:
pprint(f"Starting vm {name}")
k.start(name)
k.start_from_cd(name) if config.type == 'kvm' else k.start(name)
except subprocess.CalledProcessError as e:
error(e)
response.status = 400
Expand Down Expand Up @@ -354,7 +329,6 @@ def bios_resource(client, name):
self.debug = 'KSUSHY_DEBUG' in os.environ
self.ipv6 = 'KSUSHY_IPV6' in os.environ
self.host = '::' if self.ipv6 else '0.0.0.0'
self.bootonce = 'KSUSHY_BOOTONCE' in os.environ

def run(self):
data = {'host': self.host, 'port': self.port, 'debug': self.debug}
Expand Down
71 changes: 48 additions & 23 deletions kvirt/providers/kvm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1409,8 +1409,8 @@ def create(self, name, virttype=None, profile='kvirt', flavor=None, plan='kvirt'
os_tag.remove(initrd)
if cmdline is not None:
os_tag.remove(cmdline)
newxml = ET.tostring(root)
conn.defineXML(newxml.decode("utf-8"))
newxml = ET.tostring(root).decode("utf-8")
conn.defineXML(newxml)
return {'result': 'success'}

def start(self, name):
Expand Down Expand Up @@ -2321,8 +2321,8 @@ def clone(self, old, new, full=False, start=False):
for serial in list(tree.iter('serial')):
source = serial.find('source')
source.set('service', str(common.get_free_port()))
newxml = ET.tostring(tree)
conn.defineXML(newxml.decode("utf-8"))
newxml = ET.tostring(tree).decode("utf-8")
conn.defineXML(newxml)
vm = conn.lookupByName(new)
vm.setAutostart(oldautostart)
if start:
Expand Down Expand Up @@ -2444,8 +2444,8 @@ def reserve_dns(self, name, nets=[], domain=None, ip=None, alias=[], force=False
base = list(root.iter('network'))[0]
dns = ET.Element("dns")
base.append(dns)
newxml = ET.tostring(root)
conn.networkDefineXML(newxml.decode("utf-8"))
newxml = ET.tostring(root).decode("utf-8")
conn.networkDefineXML(newxml)
elif dns[0].get('enable', 'yes') == 'no':
warning("Ignoring reservedns as network was created with dns explicitely disabled")
continue
Expand Down Expand Up @@ -2604,8 +2604,8 @@ def update_cpus(self, name, numcpus):
return {'result': 'success'}
warning("Note it will only be effective upon next start")
cpunode.text = str(numcpus)
newxml = ET.tostring(root)
conn.defineXML(newxml.decode("utf-8"))
newxml = ET.tostring(root).decode("utf-8")
conn.defineXML(newxml)
return {'result': 'success'}

def update_cpuflags(self, name, cpuflags, disable=False):
Expand Down Expand Up @@ -2654,8 +2654,8 @@ def update_cpuflags(self, name, cpuflags, disable=False):
pprint(f"Adding flag {feature}")
new_entry = f"<feature policy='require' name='{feature}'/>"
cpu.append((ET.fromstring(new_entry)))
newxml = ET.tostring(root)
conn.defineXML(newxml.decode("utf-8"))
newxml = ET.tostring(root).decode("utf-8")
conn.defineXML(newxml)
return {'result': 'success'}

def update_memory(self, name, memory):
Expand All @@ -2680,8 +2680,8 @@ def update_memory(self, name, memory):
elif vm.isActive() != 0:
warning("Note it will only be effective upon next start")
currentmemory.text = memory
newxml = ET.tostring(root)
conn.defineXML(newxml.decode("utf-8"))
newxml = ET.tostring(root).decode("utf-8")
conn.defineXML(newxml)
return {'result': 'success'}

def update_iso(self, name, iso):
Expand Down Expand Up @@ -2721,7 +2721,8 @@ def update_iso(self, name, iso):
if source is None:
source = element.find('source')
if iso is None:
element.remove(source)
if source is not None:
element.remove(source)
elif source is not None:
source.set('file', iso)
else:
Expand All @@ -2738,12 +2739,36 @@ def update_iso(self, name, iso):
</disk>""" % iso
base = list(root.iter('devices'))[-1]
base.append((ET.fromstring(isoxml)))
newxml = ET.tostring(root).decode("utf-8")
if vm.isActive() != 0:
warning("Note it will only be effective upon next start")
newxml = ET.tostring(root)
conn.defineXML(newxml.decode("utf-8"))
conn.defineXML(newxml)
return {'result': 'success'}

def start_from_cd(self, name):
self.stop(name)
conn = self.conn
vm = conn.lookupByName(name)
xml = vm.XMLDesc(0)
root = ET.fromstring(xml)
newxml = ET.tostring(root).decode("utf-8")
newxml = newxml.replace('dev="hd"', 'dev="cdrom"')
for element in list(root.iter('disk')):
if element.get('device') == 'cdrom':
iso_file = element.find('source').get('file') if element.find('source') is not None else None
if iso_file is None or iso_file.endswith(f'{name}.ISO'):
msg = f"No iso found in VM {name}"
error(msg)
return {'result': 'failure', 'reason': msg}
boot = element.find('boot') or {}
cd_order = boot.get('order')
if cd_order is not None:
newxml = newxml.replace("<boot order='1'/>", "<boot order='XX'/>")
newxml = newxml.replace(f"<boot order='{cd_order}'/>", "<boot order='1'/>")
newxml = newxml.replace("<boot order='XX'/>", f"<boot order='{cd_order}'/>")
break
conn.createXML(newxml)

def update_flavor(self, name, flavor):
pprint("Not implemented")
return {'result': 'success'}
Expand All @@ -2767,8 +2792,8 @@ def remove_cloudinit(self, name):
volume = conn.storageVolLookupByPath(path)
volume.delete(0)
element.remove(source)
newxml = ET.tostring(root)
conn.defineXML(newxml.decode("utf-8"))
newxml = ET.tostring(root).decode("utf-8")
conn.defineXML(newxml)

def update_start(self, name, start=True):
conn = self.conn
Expand Down Expand Up @@ -3980,8 +4005,8 @@ def update_nic(self, name, index, network):
break
if vm.isActive() != 0:
warning("Note it will only be effective upon next start")
newxml = ET.tostring(root)
conn.defineXML(newxml.decode("utf-8"))
newxml = ET.tostring(root).decode("utf-8")
conn.defineXML(newxml)
return {'result': 'success'}

def update_network(self, name, dhcp=None, nat=None, domain=None, plan=None, overrides={}):
Expand Down Expand Up @@ -4059,8 +4084,8 @@ def update_network(self, name, dhcp=None, nat=None, domain=None, plan=None, over
if modified:
warning("Network will be restarted")
network.destroy()
newxml = ET.tostring(root)
conn.networkDefineXML(newxml.decode("utf-8"))
newxml = ET.tostring(root).decode("utf-8")
conn.networkDefineXML(newxml)
network.create()
else:
return {'result': 'failure', 'reason': 'No changes needed'}
Expand Down Expand Up @@ -4113,8 +4138,8 @@ def update_pool(self, name, pool):
if element.find('source').get(_type) is not None:
element.find('source').set(_type, new_path)
break
newxml = ET.tostring(root)
conn.defineXML(newxml.decode("utf-8"))
newxml = ET.tostring(root).decode("utf-8")
conn.defineXML(newxml)
return {'result': 'success'}

def list_security_groups(self, network=None):
Expand Down

0 comments on commit 537eaff

Please sign in to comment.