From 0d2a18d1f32b43d150e68a47e68c6e9e63d990df Mon Sep 17 00:00:00 2001 From: ge Date: Thu, 9 Nov 2023 22:35:19 +0300 Subject: [PATCH] add pause/resume/memory hotplug --- README.md | 14 ++++++------ compute/cli/control.py | 22 ++++++++++++++++++ compute/instance/instance.py | 44 ++++++++++++++++++++---------------- pyproject.toml | 2 +- 4 files changed, 55 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 096853c..075c4e1 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Run `make serve-docs`. See [Development](#development) below. - [ ] CDROM - [ ] cloud-init for provisioning instances - [x] Instance power management -- [ ] Instance pause and resume +- [x] Instance pause and resume - [x] vCPU hotplug -- [ ] Memory hotplug +- [x] Memory hotplug - [x] Hot disk resize [not tested] - [ ] CPU topology customization - [x] CPU customization (emulation mode, model, vendor, features) @@ -27,18 +27,18 @@ Run `make serve-docs`. See [Development](#development) below. - [ ] Instance resources usage stats - [ ] SSH-keys management - [x] Setting user passwords in guest [not tested] -- [ ] LXC - [x] QCOW2 disks support - [ ] ZVOL support - [ ] Network disks support - [ ] Images service integration (Images service is not implemented yet) - [ ] Manage storage pools +- [ ] Idempotency +- [ ] CLI [in progress] +- [ ] HTTP API +- [ ] Instance migrations - [ ] Instance snapshots - [ ] Instance backups -- [ ] Instance migrations -- [ ] Idempotency -- [ ] HTTP API -- [ ] CLI [in progress] +- [ ] LXC ## Development diff --git a/compute/cli/control.py b/compute/cli/control.py index d55f235..1f91b12 100644 --- a/compute/cli/control.py +++ b/compute/cli/control.py @@ -219,12 +219,21 @@ def main(session: Session, args: argparse.Namespace) -> None: case 'reset': instance = session.get_instance(args.instance) instance.reset() + case 'pause': + instance = session.get_instance(args.instance) + instance.pause() + case 'resume': + instance = session.get_instance(args.instance) + instance.resume() case 'status': instance = session.get_instance(args.instance) print(instance.status) case 'setvcpus': instance = session.get_instance(args.instance) instance.set_vcpus(args.nvcpus, live=True) + case 'setmem': + instance = session.get_instance(args.instance) + instance.set_memory(args.memory, live=True) def cli() -> None: # noqa: PLR0915 @@ -352,6 +361,14 @@ def cli() -> None: # noqa: PLR0915 reset = subparsers.add_parser('reset', help='reset instance') reset.add_argument('instance') + # pause subcommand + pause = subparsers.add_parser('pause', help='pause instance') + pause.add_argument('instance') + + # resume subcommand + resume = subparsers.add_parser('resume', help='resume paused instance') + resume.add_argument('instance') + # status subcommand status = subparsers.add_parser('status', help='display instance status') status.add_argument('instance') @@ -361,6 +378,11 @@ def cli() -> None: # noqa: PLR0915 setvcpus.add_argument('instance') setvcpus.add_argument('nvcpus', type=int) + # setmem subcommand + setmem = subparsers.add_parser('setmem', help='set memory size') + setmem.add_argument('instance') + setmem.add_argument('memory', type=int, help='memory in MiB') + # Run parser args = root.parse_args() if args.command is None: diff --git a/compute/instance/instance.py b/compute/instance/instance.py index bcb3927..f6c7e50 100644 --- a/compute/instance/instance.py +++ b/compute/instance/instance.py @@ -218,13 +218,13 @@ class Instance: def get_info(self) -> InstanceInfo: """Return instance info.""" - _info = self.domain.info() + info = self.domain.info() return InstanceInfo( - state=self._expand_instance_state(_info[0]), - max_memory=_info[1], - memory=_info[2], - nproc=_info[3], - cputime=_info[4], + state=self._expand_instance_state(info[0]), + max_memory=info[1], + memory=info[2], + nproc=info[3], + cputime=info[4], ) def get_status(self) -> str: @@ -405,10 +405,10 @@ class Instance: :param nvcpus: Number of vCPUs :param live: Affect a running instance """ - if nvcpus == 0: - raise InstanceError( - f'Cannot set zero vCPUs for instance={self.name}' - ) + if nvcpus <= 0: + raise InstanceError('Cannot set zero vCPUs') + if nvcpus > self.get_max_vcpus(): + raise InstanceError('vCPUs count is greather than max_vcpus') if nvcpus == self.get_info().nproc: log.warning( 'Instance instance=%s already have %s vCPUs, nothing to do', @@ -461,11 +461,18 @@ class Instance: :param memory: Memory value in mebibytes :param live: Affect a running instance """ - if memory == 0: - raise InstanceError( - f'Cannot set zero memory for instance={self.name}' + if memory <= 0: + raise InstanceError('Cannot set zero memory') + if (memory * 1024) > self.get_max_memory(): + raise InstanceError('Memory is greather than max_memory') + if (memory * 1024) == self.get_info().memory: + log.warning( + "Instance '%s' already have %s memory, nothing to do", + self.name, + memory, ) - if live and self.info()['state'] == libvirt.VIR_DOMAIN_RUNNING: + return + if live and self.is_running(): flags = ( libvirt.VIR_DOMAIN_AFFECT_LIVE | libvirt.VIR_DOMAIN_AFFECT_CONFIG @@ -473,9 +480,6 @@ class Instance: else: flags = libvirt.VIR_DOMAIN_AFFECT_CONFIG try: - self.domain.setMemoryFlags( - memory * 1024, flags=libvirt.VIR_DOMAIN_MEM_MAXIMUM - ) self.domain.setMemoryFlags(memory * 1024, flags=flags) except libvirt.libvirtError as e: msg = f'Cannot set memory for instance={self.name} {memory=}: {e}' @@ -535,11 +539,13 @@ class Instance: def pause(self) -> None: """Pause instance.""" - raise NotImplementedError + if not self.is_running(): + raise InstanceError('Cannot pause inactive instance') + self.domain.suspend() def resume(self) -> None: """Resume paused instance.""" - raise NotImplementedError + self.domain.resume() def list_ssh_keys(self, user: str) -> list[str]: """ diff --git a/pyproject.toml b/pyproject.toml index 2eb0de7..aa5e8e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ select = ['ALL'] ignore = [ 'Q000', 'Q003', 'D211', 'D212', 'ANN101', 'ISC001', 'COM812', 'D203', 'ANN204', 'T201', - 'EM102', 'TRY003', # maybe not ignore? + 'EM102', 'TRY003', 'EM101', # maybe not ignore? 'TD003', 'TD006', 'FIX002', # todo strings linting ] exclude = ['__init__.py']