clonebox 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {clonebox-0.1.2 → clonebox-0.1.3}/PKG-INFO +81 -3
- {clonebox-0.1.2 → clonebox-0.1.3}/README.md +80 -2
- {clonebox-0.1.2 → clonebox-0.1.3}/pyproject.toml +1 -1
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox/cli.py +7 -2
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox/cloner.py +40 -5
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox.egg-info/PKG-INFO +81 -3
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox.egg-info/SOURCES.txt +2 -1
- {clonebox-0.1.2 → clonebox-0.1.3}/tests/test_cloner.py +10 -3
- clonebox-0.1.3/tests/test_network.py +164 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/LICENSE +0 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/setup.cfg +0 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox/__init__.py +0 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox/detector.py +0 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox.egg-info/dependency_links.txt +0 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox.egg-info/entry_points.txt +0 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox.egg-info/requires.txt +0 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/src/clonebox.egg-info/top_level.txt +0 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/tests/test_cli.py +0 -0
- {clonebox-0.1.2 → clonebox-0.1.3}/tests/test_detector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clonebox
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Clone your workstation environment to an isolated VM with selective apps, paths and services
|
|
5
5
|
Author: CloneBox Team
|
|
6
6
|
License: Apache-2.0
|
|
@@ -65,7 +65,28 @@ CloneBox lets you create isolated virtual machines with only the applications, d
|
|
|
65
65
|
|
|
66
66
|
## Installation
|
|
67
67
|
|
|
68
|
-
###
|
|
68
|
+
### Quick Setup (Recommended)
|
|
69
|
+
|
|
70
|
+
Run the setup script to automatically install dependencies and configure the environment:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Clone the repository
|
|
74
|
+
git clone https://github.com/wronai/clonebox.git
|
|
75
|
+
cd clonebox
|
|
76
|
+
|
|
77
|
+
# Run the setup script
|
|
78
|
+
./setup.sh
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The setup script will:
|
|
82
|
+
- Install all required packages (QEMU, libvirt, Python, etc.)
|
|
83
|
+
- Add your user to the necessary groups
|
|
84
|
+
- Configure libvirt networks
|
|
85
|
+
- Install clonebox in development mode
|
|
86
|
+
|
|
87
|
+
### Manual Installation
|
|
88
|
+
|
|
89
|
+
#### Prerequisites
|
|
69
90
|
|
|
70
91
|
```bash
|
|
71
92
|
# Install libvirt and QEMU/KVM
|
|
@@ -82,7 +103,7 @@ newgrp libvirt
|
|
|
82
103
|
sudo apt install genisoimage
|
|
83
104
|
```
|
|
84
105
|
|
|
85
|
-
|
|
106
|
+
#### Install CloneBox
|
|
86
107
|
|
|
87
108
|
```bash
|
|
88
109
|
# From source
|
|
@@ -296,6 +317,63 @@ clonebox detect --yaml --dedupe -o my-config.yaml
|
|
|
296
317
|
- Python 3.8+
|
|
297
318
|
- User in `libvirt` group
|
|
298
319
|
|
|
320
|
+
## Troubleshooting
|
|
321
|
+
|
|
322
|
+
### Network Issues
|
|
323
|
+
|
|
324
|
+
If you encounter "Network not found" or "network 'default' is not active" errors:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Run the network fix script
|
|
328
|
+
./fix-network.sh
|
|
329
|
+
|
|
330
|
+
# Or manually fix:
|
|
331
|
+
virsh --connect qemu:///session net-destroy default 2>/dev/null
|
|
332
|
+
virsh --connect qemu:///session net-undefine default 2>/dev/null
|
|
333
|
+
virsh --connect qemu:///session net-define /tmp/default-network.xml
|
|
334
|
+
virsh --connect qemu:///session net-start default
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Permission Issues
|
|
338
|
+
|
|
339
|
+
If you get permission errors:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
# Ensure user is in libvirt and kvm groups
|
|
343
|
+
sudo usermod -aG libvirt $USER
|
|
344
|
+
sudo usermod -aG kvm $USER
|
|
345
|
+
|
|
346
|
+
# Log out and log back in for groups to take effect
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### VM Already Exists
|
|
350
|
+
|
|
351
|
+
If you get "domain already exists" error:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
# List VMs
|
|
355
|
+
clonebox list
|
|
356
|
+
|
|
357
|
+
# Stop and delete the existing VM
|
|
358
|
+
clonebox delete <vm-name>
|
|
359
|
+
|
|
360
|
+
# Or use virsh directly
|
|
361
|
+
virsh --connect qemu:///session destroy <vm-name>
|
|
362
|
+
virsh --connect qemu:///session undefine <vm-name>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### virt-viewer not found
|
|
366
|
+
|
|
367
|
+
If GUI doesn't open:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Install virt-viewer
|
|
371
|
+
sudo apt install virt-viewer
|
|
372
|
+
|
|
373
|
+
# Then connect manually
|
|
374
|
+
virt-viewer --connect qemu:///session <vm-name>
|
|
375
|
+
```
|
|
376
|
+
|
|
299
377
|
## License
|
|
300
378
|
|
|
301
379
|
MIT License - see [LICENSE](LICENSE) file.
|
|
@@ -26,7 +26,28 @@ CloneBox lets you create isolated virtual machines with only the applications, d
|
|
|
26
26
|
|
|
27
27
|
## Installation
|
|
28
28
|
|
|
29
|
-
###
|
|
29
|
+
### Quick Setup (Recommended)
|
|
30
|
+
|
|
31
|
+
Run the setup script to automatically install dependencies and configure the environment:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Clone the repository
|
|
35
|
+
git clone https://github.com/wronai/clonebox.git
|
|
36
|
+
cd clonebox
|
|
37
|
+
|
|
38
|
+
# Run the setup script
|
|
39
|
+
./setup.sh
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The setup script will:
|
|
43
|
+
- Install all required packages (QEMU, libvirt, Python, etc.)
|
|
44
|
+
- Add your user to the necessary groups
|
|
45
|
+
- Configure libvirt networks
|
|
46
|
+
- Install clonebox in development mode
|
|
47
|
+
|
|
48
|
+
### Manual Installation
|
|
49
|
+
|
|
50
|
+
#### Prerequisites
|
|
30
51
|
|
|
31
52
|
```bash
|
|
32
53
|
# Install libvirt and QEMU/KVM
|
|
@@ -43,7 +64,7 @@ newgrp libvirt
|
|
|
43
64
|
sudo apt install genisoimage
|
|
44
65
|
```
|
|
45
66
|
|
|
46
|
-
|
|
67
|
+
#### Install CloneBox
|
|
47
68
|
|
|
48
69
|
```bash
|
|
49
70
|
# From source
|
|
@@ -257,6 +278,63 @@ clonebox detect --yaml --dedupe -o my-config.yaml
|
|
|
257
278
|
- Python 3.8+
|
|
258
279
|
- User in `libvirt` group
|
|
259
280
|
|
|
281
|
+
## Troubleshooting
|
|
282
|
+
|
|
283
|
+
### Network Issues
|
|
284
|
+
|
|
285
|
+
If you encounter "Network not found" or "network 'default' is not active" errors:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# Run the network fix script
|
|
289
|
+
./fix-network.sh
|
|
290
|
+
|
|
291
|
+
# Or manually fix:
|
|
292
|
+
virsh --connect qemu:///session net-destroy default 2>/dev/null
|
|
293
|
+
virsh --connect qemu:///session net-undefine default 2>/dev/null
|
|
294
|
+
virsh --connect qemu:///session net-define /tmp/default-network.xml
|
|
295
|
+
virsh --connect qemu:///session net-start default
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Permission Issues
|
|
299
|
+
|
|
300
|
+
If you get permission errors:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Ensure user is in libvirt and kvm groups
|
|
304
|
+
sudo usermod -aG libvirt $USER
|
|
305
|
+
sudo usermod -aG kvm $USER
|
|
306
|
+
|
|
307
|
+
# Log out and log back in for groups to take effect
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### VM Already Exists
|
|
311
|
+
|
|
312
|
+
If you get "domain already exists" error:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
# List VMs
|
|
316
|
+
clonebox list
|
|
317
|
+
|
|
318
|
+
# Stop and delete the existing VM
|
|
319
|
+
clonebox delete <vm-name>
|
|
320
|
+
|
|
321
|
+
# Or use virsh directly
|
|
322
|
+
virsh --connect qemu:///session destroy <vm-name>
|
|
323
|
+
virsh --connect qemu:///session undefine <vm-name>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### virt-viewer not found
|
|
327
|
+
|
|
328
|
+
If GUI doesn't open:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Install virt-viewer
|
|
332
|
+
sudo apt install virt-viewer
|
|
333
|
+
|
|
334
|
+
# Then connect manually
|
|
335
|
+
virt-viewer --connect qemu:///session <vm-name>
|
|
336
|
+
```
|
|
337
|
+
|
|
260
338
|
## License
|
|
261
339
|
|
|
262
340
|
MIT License - see [LICENSE](LICENSE) file.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "clonebox"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "Clone your workstation environment to an isolated VM with selective apps, paths and services"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -492,7 +492,7 @@ def deduplicate_list(items: list, key=None) -> list:
|
|
|
492
492
|
|
|
493
493
|
|
|
494
494
|
def generate_clonebox_yaml(snapshot, detector, deduplicate: bool = True,
|
|
495
|
-
target_path: str = None, vm_name: str = None) -> str:
|
|
495
|
+
target_path: str = None, vm_name: str = None, network_mode: str = "auto") -> str:
|
|
496
496
|
"""Generate YAML config from system snapshot."""
|
|
497
497
|
sys_info = detector.get_system_info()
|
|
498
498
|
|
|
@@ -559,6 +559,7 @@ def generate_clonebox_yaml(snapshot, detector, deduplicate: bool = True,
|
|
|
559
559
|
"vcpus": vcpus,
|
|
560
560
|
"gui": True,
|
|
561
561
|
"base_image": None,
|
|
562
|
+
"network_mode": network_mode,
|
|
562
563
|
},
|
|
563
564
|
"services": services,
|
|
564
565
|
"packages": [
|
|
@@ -605,6 +606,7 @@ def create_vm_from_config(config: dict, start: bool = False, user_session: bool
|
|
|
605
606
|
packages=config.get("packages", []),
|
|
606
607
|
services=config.get("services", []),
|
|
607
608
|
user_session=user_session,
|
|
609
|
+
network_mode=config["vm"].get("network_mode", "auto"),
|
|
608
610
|
)
|
|
609
611
|
|
|
610
612
|
cloner = SelectiveVMCloner(user_session=user_session)
|
|
@@ -655,7 +657,8 @@ def cmd_clone(args):
|
|
|
655
657
|
snapshot, detector,
|
|
656
658
|
deduplicate=args.dedupe,
|
|
657
659
|
target_path=str(target_path),
|
|
658
|
-
vm_name=vm_name
|
|
660
|
+
vm_name=vm_name,
|
|
661
|
+
network_mode=args.network
|
|
659
662
|
)
|
|
660
663
|
|
|
661
664
|
# Save config file
|
|
@@ -863,6 +866,8 @@ def main():
|
|
|
863
866
|
clone_parser.add_argument("--dedupe", action="store_true", default=True, help="Remove duplicate entries")
|
|
864
867
|
clone_parser.add_argument("--user", "-u", action="store_true",
|
|
865
868
|
help="Use user session (qemu:///session) - no root required, stores in ~/.local/share/libvirt/")
|
|
869
|
+
clone_parser.add_argument("--network", choices=["auto", "default", "user"], default="auto",
|
|
870
|
+
help="Network mode: auto (default), default (libvirt network), user (slirp)")
|
|
866
871
|
clone_parser.set_defaults(func=cmd_clone)
|
|
867
872
|
|
|
868
873
|
args = parser.parse_args()
|
|
@@ -32,6 +32,7 @@ class VMConfig:
|
|
|
32
32
|
packages: list = field(default_factory=list)
|
|
33
33
|
services: list = field(default_factory=list)
|
|
34
34
|
user_session: bool = False # Use qemu:///session instead of qemu:///system
|
|
35
|
+
network_mode: str = "auto" # auto|default|user
|
|
35
36
|
|
|
36
37
|
def to_dict(self) -> dict:
|
|
37
38
|
return {
|
|
@@ -91,6 +92,25 @@ class SelectiveVMCloner:
|
|
|
91
92
|
return self.USER_IMAGES_DIR
|
|
92
93
|
return self.SYSTEM_IMAGES_DIR
|
|
93
94
|
|
|
95
|
+
def _default_network_active(self) -> bool:
|
|
96
|
+
"""Check if libvirt default network is active."""
|
|
97
|
+
try:
|
|
98
|
+
net = self.conn.networkLookupByName("default")
|
|
99
|
+
return net.isActive() == 1
|
|
100
|
+
except libvirt.libvirtError:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def resolve_network_mode(self, config: VMConfig) -> str:
|
|
104
|
+
"""Resolve network mode based on config and session type."""
|
|
105
|
+
mode = (config.network_mode or "auto").lower()
|
|
106
|
+
if mode == "auto":
|
|
107
|
+
if self.user_session and not self._default_network_active():
|
|
108
|
+
return "user"
|
|
109
|
+
return "default"
|
|
110
|
+
if mode in {"default", "user"}:
|
|
111
|
+
return mode
|
|
112
|
+
return "default"
|
|
113
|
+
|
|
94
114
|
def check_prerequisites(self) -> dict:
|
|
95
115
|
"""Check system prerequisites for VM creation."""
|
|
96
116
|
images_dir = self.get_images_dir()
|
|
@@ -123,8 +143,11 @@ class SelectiveVMCloner:
|
|
|
123
143
|
except libvirt.libvirtError:
|
|
124
144
|
checks["network_error"] = (
|
|
125
145
|
"Default network not found or inactive.\n"
|
|
126
|
-
"
|
|
127
|
-
" Or create
|
|
146
|
+
" For user session, CloneBox can use user-mode networking (slirp) automatically.\n"
|
|
147
|
+
" Or create a user network:\n"
|
|
148
|
+
" virsh --connect qemu:///session net-define /tmp/default-network.xml\n"
|
|
149
|
+
" virsh --connect qemu:///session net-start default\n"
|
|
150
|
+
" Or use system session: clonebox clone . (without --user)\n"
|
|
128
151
|
)
|
|
129
152
|
|
|
130
153
|
# Check images directory
|
|
@@ -215,6 +238,13 @@ class SelectiveVMCloner:
|
|
|
215
238
|
cloudinit_iso = self._create_cloudinit_iso(vm_dir, config)
|
|
216
239
|
log(f"[cyan]☁️ Created cloud-init ISO with {len(config.packages)} packages[/]")
|
|
217
240
|
|
|
241
|
+
# Resolve network mode
|
|
242
|
+
network_mode = self.resolve_network_mode(config)
|
|
243
|
+
if network_mode == "user":
|
|
244
|
+
log("[yellow]⚠️ Using user-mode networking (slirp) because default libvirt network is unavailable[/]")
|
|
245
|
+
else:
|
|
246
|
+
log(f"[dim]Network mode: {network_mode}[/]")
|
|
247
|
+
|
|
218
248
|
# Generate VM XML
|
|
219
249
|
vm_xml = self._generate_vm_xml(config, root_disk, cloudinit_iso)
|
|
220
250
|
|
|
@@ -288,9 +318,14 @@ class SelectiveVMCloner:
|
|
|
288
318
|
ET.SubElement(fs, "target", dir=tag)
|
|
289
319
|
|
|
290
320
|
# Network interface
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
321
|
+
network_mode = self.resolve_network_mode(config)
|
|
322
|
+
if network_mode == "user":
|
|
323
|
+
iface = ET.SubElement(devices, "interface", type="user")
|
|
324
|
+
ET.SubElement(iface, "model", type="virtio")
|
|
325
|
+
else:
|
|
326
|
+
iface = ET.SubElement(devices, "interface", type="network")
|
|
327
|
+
ET.SubElement(iface, "source", network="default")
|
|
328
|
+
ET.SubElement(iface, "model", type="virtio")
|
|
294
329
|
|
|
295
330
|
# Serial console
|
|
296
331
|
serial = ET.SubElement(devices, "serial", type="pty")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clonebox
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Clone your workstation environment to an isolated VM with selective apps, paths and services
|
|
5
5
|
Author: CloneBox Team
|
|
6
6
|
License: Apache-2.0
|
|
@@ -65,7 +65,28 @@ CloneBox lets you create isolated virtual machines with only the applications, d
|
|
|
65
65
|
|
|
66
66
|
## Installation
|
|
67
67
|
|
|
68
|
-
###
|
|
68
|
+
### Quick Setup (Recommended)
|
|
69
|
+
|
|
70
|
+
Run the setup script to automatically install dependencies and configure the environment:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Clone the repository
|
|
74
|
+
git clone https://github.com/wronai/clonebox.git
|
|
75
|
+
cd clonebox
|
|
76
|
+
|
|
77
|
+
# Run the setup script
|
|
78
|
+
./setup.sh
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The setup script will:
|
|
82
|
+
- Install all required packages (QEMU, libvirt, Python, etc.)
|
|
83
|
+
- Add your user to the necessary groups
|
|
84
|
+
- Configure libvirt networks
|
|
85
|
+
- Install clonebox in development mode
|
|
86
|
+
|
|
87
|
+
### Manual Installation
|
|
88
|
+
|
|
89
|
+
#### Prerequisites
|
|
69
90
|
|
|
70
91
|
```bash
|
|
71
92
|
# Install libvirt and QEMU/KVM
|
|
@@ -82,7 +103,7 @@ newgrp libvirt
|
|
|
82
103
|
sudo apt install genisoimage
|
|
83
104
|
```
|
|
84
105
|
|
|
85
|
-
|
|
106
|
+
#### Install CloneBox
|
|
86
107
|
|
|
87
108
|
```bash
|
|
88
109
|
# From source
|
|
@@ -296,6 +317,63 @@ clonebox detect --yaml --dedupe -o my-config.yaml
|
|
|
296
317
|
- Python 3.8+
|
|
297
318
|
- User in `libvirt` group
|
|
298
319
|
|
|
320
|
+
## Troubleshooting
|
|
321
|
+
|
|
322
|
+
### Network Issues
|
|
323
|
+
|
|
324
|
+
If you encounter "Network not found" or "network 'default' is not active" errors:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Run the network fix script
|
|
328
|
+
./fix-network.sh
|
|
329
|
+
|
|
330
|
+
# Or manually fix:
|
|
331
|
+
virsh --connect qemu:///session net-destroy default 2>/dev/null
|
|
332
|
+
virsh --connect qemu:///session net-undefine default 2>/dev/null
|
|
333
|
+
virsh --connect qemu:///session net-define /tmp/default-network.xml
|
|
334
|
+
virsh --connect qemu:///session net-start default
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Permission Issues
|
|
338
|
+
|
|
339
|
+
If you get permission errors:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
# Ensure user is in libvirt and kvm groups
|
|
343
|
+
sudo usermod -aG libvirt $USER
|
|
344
|
+
sudo usermod -aG kvm $USER
|
|
345
|
+
|
|
346
|
+
# Log out and log back in for groups to take effect
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### VM Already Exists
|
|
350
|
+
|
|
351
|
+
If you get "domain already exists" error:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
# List VMs
|
|
355
|
+
clonebox list
|
|
356
|
+
|
|
357
|
+
# Stop and delete the existing VM
|
|
358
|
+
clonebox delete <vm-name>
|
|
359
|
+
|
|
360
|
+
# Or use virsh directly
|
|
361
|
+
virsh --connect qemu:///session destroy <vm-name>
|
|
362
|
+
virsh --connect qemu:///session undefine <vm-name>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### virt-viewer not found
|
|
366
|
+
|
|
367
|
+
If GUI doesn't open:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Install virt-viewer
|
|
371
|
+
sudo apt install virt-viewer
|
|
372
|
+
|
|
373
|
+
# Then connect manually
|
|
374
|
+
virt-viewer --connect qemu:///session <vm-name>
|
|
375
|
+
```
|
|
376
|
+
|
|
299
377
|
## License
|
|
300
378
|
|
|
301
379
|
MIT License - see [LICENSE](LICENSE) file.
|
|
@@ -104,9 +104,16 @@ class TestSelectiveVMClonerInit:
|
|
|
104
104
|
|
|
105
105
|
@patch('clonebox.cloner.libvirt')
|
|
106
106
|
def test_init_connection_failed(self, mock_libvirt):
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
try:
|
|
108
|
+
import libvirt as real_libvirt
|
|
109
|
+
mock_libvirt.libvirtError = real_libvirt.libvirtError
|
|
110
|
+
mock_libvirt.open.side_effect = real_libvirt.libvirtError("Connection refused")
|
|
111
|
+
except ImportError:
|
|
112
|
+
# If libvirt is not installed, create a mock exception
|
|
113
|
+
class MockLibvirtError(Exception):
|
|
114
|
+
pass
|
|
115
|
+
mock_libvirt.libvirtError = MockLibvirtError
|
|
116
|
+
mock_libvirt.open.side_effect = MockLibvirtError("Connection refused")
|
|
110
117
|
|
|
111
118
|
with pytest.raises(ConnectionError) as exc_info:
|
|
112
119
|
SelectiveVMCloner()
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Tests for network mode functionality."""
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest.mock import patch, MagicMock
|
|
7
|
+
|
|
8
|
+
from clonebox.cloner import VMConfig, SelectiveVMCloner
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestNetworkMode:
|
|
12
|
+
"""Test network mode resolution and fallback."""
|
|
13
|
+
|
|
14
|
+
@patch('clonebox.cloner.libvirt')
|
|
15
|
+
def test_vm_config_network_mode_default(self, mock_libvirt):
|
|
16
|
+
config = VMConfig()
|
|
17
|
+
assert config.network_mode == "auto"
|
|
18
|
+
|
|
19
|
+
@patch('clonebox.cloner.libvirt')
|
|
20
|
+
def test_vm_config_network_mode_custom(self, mock_libvirt):
|
|
21
|
+
config = VMConfig(network_mode="user")
|
|
22
|
+
assert config.network_mode == "user"
|
|
23
|
+
|
|
24
|
+
@patch('clonebox.cloner.libvirt')
|
|
25
|
+
def test_resolve_network_mode_auto_system(self, mock_libvirt):
|
|
26
|
+
"""Test auto mode with system session uses default network."""
|
|
27
|
+
mock_conn = MagicMock()
|
|
28
|
+
mock_libvirt.open.return_value = mock_conn
|
|
29
|
+
|
|
30
|
+
cloner = SelectiveVMCloner(user_session=False)
|
|
31
|
+
config = VMConfig(network_mode="auto")
|
|
32
|
+
|
|
33
|
+
mode = cloner.resolve_network_mode(config)
|
|
34
|
+
assert mode == "default"
|
|
35
|
+
|
|
36
|
+
@patch('clonebox.cloner.libvirt')
|
|
37
|
+
def test_resolve_network_mode_auto_user_with_default(self, mock_libvirt):
|
|
38
|
+
"""Test auto mode with user session and default network available."""
|
|
39
|
+
mock_conn = MagicMock()
|
|
40
|
+
mock_net = MagicMock()
|
|
41
|
+
mock_net.isActive.return_value = 1
|
|
42
|
+
mock_conn.networkLookupByName.return_value = mock_net
|
|
43
|
+
mock_libvirt.open.return_value = mock_conn
|
|
44
|
+
|
|
45
|
+
cloner = SelectiveVMCloner(user_session=True)
|
|
46
|
+
config = VMConfig(network_mode="auto")
|
|
47
|
+
|
|
48
|
+
mode = cloner.resolve_network_mode(config)
|
|
49
|
+
assert mode == "default"
|
|
50
|
+
|
|
51
|
+
@patch('clonebox.cloner.libvirt')
|
|
52
|
+
def test_resolve_network_mode_auto_user_no_default(self, mock_libvirt):
|
|
53
|
+
"""Test auto mode with user session and no default network falls back to user."""
|
|
54
|
+
import libvirt as real_libvirt
|
|
55
|
+
mock_conn = MagicMock()
|
|
56
|
+
mock_conn.networkLookupByName.side_effect = real_libvirt.libvirtError("No network")
|
|
57
|
+
mock_libvirt.open.return_value = mock_conn
|
|
58
|
+
|
|
59
|
+
cloner = SelectiveVMCloner(user_session=True)
|
|
60
|
+
config = VMConfig(network_mode="auto")
|
|
61
|
+
|
|
62
|
+
mode = cloner.resolve_network_mode(config)
|
|
63
|
+
assert mode == "user"
|
|
64
|
+
|
|
65
|
+
@patch('clonebox.cloner.libvirt')
|
|
66
|
+
def test_resolve_network_mode_explicit_default(self, mock_libvirt):
|
|
67
|
+
"""Test explicit default mode."""
|
|
68
|
+
mock_conn = MagicMock()
|
|
69
|
+
mock_libvirt.open.return_value = mock_conn
|
|
70
|
+
|
|
71
|
+
cloner = SelectiveVMCloner(user_session=True)
|
|
72
|
+
config = VMConfig(network_mode="default")
|
|
73
|
+
|
|
74
|
+
mode = cloner.resolve_network_mode(config)
|
|
75
|
+
assert mode == "default"
|
|
76
|
+
|
|
77
|
+
@patch('clonebox.cloner.libvirt')
|
|
78
|
+
def test_resolve_network_mode_explicit_user(self, mock_libvirt):
|
|
79
|
+
"""Test explicit user mode."""
|
|
80
|
+
mock_conn = MagicMock()
|
|
81
|
+
mock_libvirt.open.return_value = mock_conn
|
|
82
|
+
|
|
83
|
+
cloner = SelectiveVMCloner(user_session=False)
|
|
84
|
+
config = VMConfig(network_mode="user")
|
|
85
|
+
|
|
86
|
+
mode = cloner.resolve_network_mode(config)
|
|
87
|
+
assert mode == "user"
|
|
88
|
+
|
|
89
|
+
@patch('clonebox.cloner.libvirt')
|
|
90
|
+
def test_resolve_network_mode_invalid(self, mock_libvirt):
|
|
91
|
+
"""Test invalid network mode falls back to default."""
|
|
92
|
+
mock_conn = MagicMock()
|
|
93
|
+
mock_libvirt.open.return_value = mock_conn
|
|
94
|
+
|
|
95
|
+
cloner = SelectiveVMCloner()
|
|
96
|
+
config = VMConfig(network_mode="invalid")
|
|
97
|
+
|
|
98
|
+
mode = cloner.resolve_network_mode(config)
|
|
99
|
+
assert mode == "default"
|
|
100
|
+
|
|
101
|
+
@patch('clonebox.cloner.libvirt')
|
|
102
|
+
def test_default_network_active_true(self, mock_libvirt):
|
|
103
|
+
"""Test _default_network_active returns True when network is active."""
|
|
104
|
+
mock_conn = MagicMock()
|
|
105
|
+
mock_net = MagicMock()
|
|
106
|
+
mock_net.isActive.return_value = 1
|
|
107
|
+
mock_conn.networkLookupByName.return_value = mock_net
|
|
108
|
+
mock_libvirt.open.return_value = mock_conn
|
|
109
|
+
|
|
110
|
+
cloner = SelectiveVMCloner()
|
|
111
|
+
assert cloner._default_network_active() is True
|
|
112
|
+
|
|
113
|
+
@patch('clonebox.cloner.libvirt')
|
|
114
|
+
def test_default_network_active_false(self, mock_libvirt):
|
|
115
|
+
"""Test _default_network_active returns False when network is inactive."""
|
|
116
|
+
import libvirt as real_libvirt
|
|
117
|
+
mock_conn = MagicMock()
|
|
118
|
+
mock_net = MagicMock()
|
|
119
|
+
mock_net.isActive.return_value = 0
|
|
120
|
+
mock_conn.networkLookupByName.return_value = mock_net
|
|
121
|
+
mock_libvirt.open.return_value = mock_conn
|
|
122
|
+
|
|
123
|
+
cloner = SelectiveVMCloner()
|
|
124
|
+
assert cloner._default_network_active() is False
|
|
125
|
+
|
|
126
|
+
@patch('clonebox.cloner.libvirt')
|
|
127
|
+
def test_default_network_active_not_found(self, mock_libvirt):
|
|
128
|
+
"""Test _default_network_active returns False when network not found."""
|
|
129
|
+
import libvirt as real_libvirt
|
|
130
|
+
mock_conn = MagicMock()
|
|
131
|
+
mock_conn.networkLookupByName.side_effect = real_libvirt.libvirtError("Not found")
|
|
132
|
+
mock_libvirt.open.return_value = mock_conn
|
|
133
|
+
|
|
134
|
+
cloner = SelectiveVMCloner()
|
|
135
|
+
assert cloner._default_network_active() is False
|
|
136
|
+
|
|
137
|
+
@patch('clonebox.cloner.libvirt')
|
|
138
|
+
def test_generate_vm_xml_user_network(self, mock_libvirt):
|
|
139
|
+
"""Test VM XML generation with user network."""
|
|
140
|
+
mock_conn = MagicMock()
|
|
141
|
+
mock_libvirt.open.return_value = mock_conn
|
|
142
|
+
|
|
143
|
+
cloner = SelectiveVMCloner()
|
|
144
|
+
config = VMConfig(name="test-vm", network_mode="user")
|
|
145
|
+
|
|
146
|
+
xml = cloner._generate_vm_xml(config, Path("/tmp/root.qcow2"), None)
|
|
147
|
+
|
|
148
|
+
assert '<interface type="user">' in xml
|
|
149
|
+
assert '<interface type="network">' not in xml
|
|
150
|
+
|
|
151
|
+
@patch('clonebox.cloner.libvirt')
|
|
152
|
+
def test_generate_vm_xml_default_network(self, mock_libvirt):
|
|
153
|
+
"""Test VM XML generation with default network."""
|
|
154
|
+
mock_conn = MagicMock()
|
|
155
|
+
mock_libvirt.open.return_value = mock_conn
|
|
156
|
+
|
|
157
|
+
cloner = SelectiveVMCloner()
|
|
158
|
+
config = VMConfig(name="test-vm", network_mode="default")
|
|
159
|
+
|
|
160
|
+
xml = cloner._generate_vm_xml(config, Path("/tmp/root.qcow2"), None)
|
|
161
|
+
|
|
162
|
+
assert '<interface type="network">' in xml
|
|
163
|
+
assert '<source network="default"/>' in xml
|
|
164
|
+
assert '<interface type="user">' not in xml
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|