1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 11:18:11 +00:00

Merge pull request #2286 from GNS3/release-v2.2.43

Release v2.2.43
This commit is contained in:
Jeremy Grossmann 2023-09-19 21:04:52 +07:00 committed by GitHub
commit 6f345bb1ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 152 additions and 76 deletions

View File

@ -1,5 +1,15 @@
# Change Log # Change Log
## 2.2.43 19/09/2023
* Force English output for VBoxManage. Fixes #2266
* Automatically add vboxnet and DHCP server if not present for VirtualBox GNS3 VM. Ref #2266
* Fix issue with controller config saved before checking current version with previous one
* Prevent X11 socket file to be modified by Docker container
* Use the user data dir to store built-in appliances
* Catch ConnectionResetError exception when client disconnects
* Upgrade to PyQt 5.15.9 and pywin32
## 2.2.42 09/08/2023 ## 2.2.42 09/08/2023
* Bundle web-ui v2.2.42 * Bundle web-ui v2.2.42

View File

@ -26,6 +26,14 @@
"kvm": "require" "kvm": "require"
}, },
"images": [ "images": [
{
"filename": "openmediavault_6.5.0-amd64.iso",
"version": "6.5.0",
"md5sum": "aa40e5ca50748b139cba2f4ac704a72d",
"filesize": 941621248,
"download_url": "https://www.openmediavault.org/download.html",
"direct_download_url": "https://sourceforge.net/projects/openmediavault/files/6.5.0/openmediavault_6.5.0-amd64.iso"
},
{ {
"filename": "openmediavault_6.0.24-amd64.iso", "filename": "openmediavault_6.0.24-amd64.iso",
"version": "6.0.24", "version": "6.0.24",
@ -60,6 +68,14 @@
} }
], ],
"versions": [ "versions": [
{
"name": "6.5.0",
"images": {
"hda_disk_image": "empty30G.qcow2",
"hdb_disk_image": "empty30G.qcow2",
"cdrom_image": "openmediavault_6.5.0-amd64.iso"
}
},
{ {
"name": "6.0.24", "name": "6.0.24",
"images": { "images": {

View File

@ -59,32 +59,28 @@
"version": "1.2.9-S1", "version": "1.2.9-S1",
"md5sum": "3fece6363f9766f862e26d292d0ed5a3", "md5sum": "3fece6363f9766f862e26d292d0ed5a3",
"filesize": 430964736, "filesize": 430964736,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-s1-generic-iso-image", "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-s1-generic-iso-image"
"direct_download_url": "https://s3-us.vyos.io/1.2.9-S1/vyos-1.2.9-S1-amd64.iso"
}, },
{ {
"filename": "vyos-1.2.9-S1-10G-qemu.qcow2", "filename": "vyos-1.2.9-S1-10G-qemu.qcow2",
"version": "1.2.9-S1-KVM", "version": "1.2.9-S1-KVM",
"md5sum": "0a70d78b80a3716d42487c02ef44f41f", "md5sum": "0a70d78b80a3716d42487c02ef44f41f",
"filesize": 426967040, "filesize": 426967040,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-s1-for-kvm", "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-s1-for-kvm"
"direct_download_url": "https://s3-us.vyos.io/1.2.9-S1/vyos-1.2.9-S1-10G-qemu.qcow2"
}, },
{ {
"filename": "vyos-1.2.9-amd64.iso", "filename": "vyos-1.2.9-amd64.iso",
"version": "1.2.9", "version": "1.2.9",
"md5sum": "586be23b6256173e174c82d8f1f699a1", "md5sum": "586be23b6256173e174c82d8f1f699a1",
"filesize": 430964736, "filesize": 430964736,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-generic-iso-image", "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-generic-iso-image"
"direct_download_url": "https://s3-us.vyos.io/1.2.9/vyos-1.2.9-amd64.iso"
}, },
{ {
"filename": "vyos-1.2.9-10G-qemu.qcow2", "filename": "vyos-1.2.9-10G-qemu.qcow2",
"version": "1.2.9-KVM", "version": "1.2.9-KVM",
"md5sum": "76871c7b248c32f75177c419128257ac", "md5sum": "76871c7b248c32f75177c419128257ac",
"filesize": 427360256, "filesize": 427360256,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-10g-qemu-qcow2", "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-10g-qemu-qcow2"
"direct_download_url": "https://s3-us.vyos.io/1.2.9/vyos-1.2.9-10G-qemu.qcow2"
}, },
{ {
"filename": "vyos-1.2.8-amd64.iso", "filename": "vyos-1.2.8-amd64.iso",
@ -93,13 +89,6 @@
"filesize": 429916160, "filesize": 429916160,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-8-generic-iso-image" "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-8-generic-iso-image"
}, },
{
"filename": "vyos-1.1.8-amd64.iso",
"version": "1.1.8",
"md5sum": "95a141d4b592b81c803cdf7e9b11d8ea",
"filesize": 241172480,
"direct_download_url": "https://s3-us.vyos.io/vyos-1.1.8-amd64.iso"
},
{ {
"filename": "empty8G.qcow2", "filename": "empty8G.qcow2",
"version": "1.0", "version": "1.0",
@ -170,13 +159,6 @@
"hda_disk_image": "empty8G.qcow2", "hda_disk_image": "empty8G.qcow2",
"cdrom_image": "vyos-1.2.8-amd64.iso" "cdrom_image": "vyos-1.2.8-amd64.iso"
} }
},
{
"name": "1.1.8",
"images": {
"hda_disk_image": "empty8G.qcow2",
"cdrom_image": "vyos-1.1.8-amd64.iso"
}
} }
] ]
} }

View File

@ -29,6 +29,14 @@
"kvm": "require" "kvm": "require"
}, },
"images": [ "images": [
{
"filename": "WinDev2308Eval-disk1.vmdk",
"version": "2308",
"md5sum": "6a9b4ed6d7481f7bbf8a054c797b1eee",
"filesize": 24945341952,
"download_url": "https://download.microsoft.com/download/7/1/3/7135f2ab-8528-49fc-9252-8d5d94c697ef/WinDev2308Eval.VMWare.zip",
"compression": "zip"
},
{ {
"filename": "WinDev2212Eval-disk1.vmdk", "filename": "WinDev2212Eval-disk1.vmdk",
"version": "2212", "version": "2212",
@ -48,6 +56,13 @@
} }
], ],
"versions": [ "versions": [
{
"name": "2308",
"images": {
"bios_image": "OVMF-edk2-stable202305.fd",
"hda_disk_image": "WinDev2308Eval-disk1.vmdk"
}
},
{ {
"name": "2212", "name": "2212",
"images": { "images": {

View File

@ -406,7 +406,7 @@ class DockerVM(BaseNode):
await self._start_vnc() await self._start_vnc()
params["Env"].append("QT_GRAPHICSSYSTEM=native") # To fix a Qt issue: https://github.com/GNS3/gns3-server/issues/556 params["Env"].append("QT_GRAPHICSSYSTEM=native") # To fix a Qt issue: https://github.com/GNS3/gns3-server/issues/556
params["Env"].append("DISPLAY=:{}".format(self._display)) params["Env"].append("DISPLAY=:{}".format(self._display))
params["HostConfig"]["Binds"].append("/tmp/.X11-unix/:/tmp/.X11-unix/") params["HostConfig"]["Binds"].append("/tmp/.X11-unix/X{0}:/tmp/.X11-unix/X{0}:ro".format(self._display))
if self._extra_hosts: if self._extra_hosts:
extra_hosts = self._format_extra_hosts(self._extra_hosts) extra_hosts = self._format_extra_hosts(self._extra_hosts)

View File

@ -109,9 +109,16 @@ class VirtualBox(BaseManager):
command = [vboxmanage_path, "--nologo", subcommand] command = [vboxmanage_path, "--nologo", subcommand]
command.extend(args) command.extend(args)
command_string = " ".join(command) command_string = " ".join(command)
env = os.environ.copy()
env["LANG"] = "en" # force english output because we rely on it to parse the output
log.info("Executing VBoxManage with command: {}".format(command_string)) log.info("Executing VBoxManage with command: {}".format(command_string))
try: try:
process = await asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) process = await asyncio.create_subprocess_exec(
*command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
env=env
)
except (OSError, subprocess.SubprocessError) as e: except (OSError, subprocess.SubprocessError) as e:
raise VirtualBoxError("Could not execute VBoxManage: {}".format(e)) raise VirtualBoxError("Could not execute VBoxManage: {}".format(e))

View File

@ -191,9 +191,8 @@ class Controller:
Save the controller configuration on disk Save the controller configuration on disk
""" """
if self._config_loaded is False: controller_settings = dict()
return if self._config_loaded:
controller_settings = {"computes": [], controller_settings = {"computes": [],
"templates": [], "templates": [],
"gns3vm": self.gns3vm.__json__(), "gns3vm": self.gns3vm.__json__(),
@ -229,8 +228,7 @@ class Controller:
try: try:
if not os.path.exists(self._config_file): if not os.path.exists(self._config_file):
self._config_loaded = True self.save() # this will create the config file
self.save()
with open(self._config_file) as f: with open(self._config_file) as f:
controller_settings = json.load(f) controller_settings = json.load(f)
except (OSError, ValueError) as e: except (OSError, ValueError) as e:
@ -255,6 +253,8 @@ class Controller:
if not previous_version or \ if not previous_version or \
parse_version(__version__.split("+")[0]) > parse_version(previous_version.split("+")[0]): parse_version(__version__.split("+")[0]) > parse_version(previous_version.split("+")[0]):
self._appliance_manager.install_builtin_appliances() self._appliance_manager.install_builtin_appliances()
elif not os.listdir(self._appliance_manager.builtin_appliances_path()):
self._appliance_manager.install_builtin_appliances()
self._appliance_manager.appliances_etag = controller_settings.get("appliances_etag") self._appliance_manager.appliances_etag = controller_settings.get("appliances_etag")
self._appliance_manager.load_appliances() self._appliance_manager.load_appliances()

View File

@ -21,6 +21,7 @@ import uuid
import asyncio import asyncio
import aiohttp import aiohttp
import shutil import shutil
import platformdirs
try: try:
@ -81,13 +82,13 @@ class ApplianceManager:
os.makedirs(appliances_path, exist_ok=True) os.makedirs(appliances_path, exist_ok=True)
return appliances_path return appliances_path
def _builtin_appliances_path(self, delete_first=False): def builtin_appliances_path(self, delete_first=False):
""" """
Get the built-in appliance storage directory Get the built-in appliance storage directory
""" """
config = Config.instance() appname = vendor = "GNS3"
appliances_dir = os.path.join(config.config_dir, "appliances") appliances_dir = os.path.join(platformdirs.user_data_dir(appname, vendor, roaming=True), "appliances")
if delete_first: if delete_first:
shutil.rmtree(appliances_dir, ignore_errors=True) shutil.rmtree(appliances_dir, ignore_errors=True)
os.makedirs(appliances_dir, exist_ok=True) os.makedirs(appliances_dir, exist_ok=True)
@ -98,7 +99,7 @@ class ApplianceManager:
At startup we copy the built-in appliances files. At startup we copy the built-in appliances files.
""" """
dst_path = self._builtin_appliances_path(delete_first=True) dst_path = self.builtin_appliances_path(delete_first=True)
log.info(f"Installing built-in appliances in '{dst_path}'") log.info(f"Installing built-in appliances in '{dst_path}'")
from . import Controller from . import Controller
try: try:
@ -112,7 +113,7 @@ class ApplianceManager:
""" """
self._appliances = {} self._appliances = {}
for directory, builtin in ((self._builtin_appliances_path(), True,), (self._custom_appliances_path(), False,)): for directory, builtin in ((self.builtin_appliances_path(), True,), (self._custom_appliances_path(), False,)):
if directory and os.path.isdir(directory): if directory and os.path.isdir(directory):
for file in os.listdir(directory): for file in os.listdir(directory):
if not file.endswith('.gns3a') and not file.endswith('.gns3appliance'): if not file.endswith('.gns3a') and not file.endswith('.gns3appliance'):
@ -215,7 +216,7 @@ class ApplianceManager:
from . import Controller from . import Controller
Controller.instance().save() Controller.instance().save()
json_data = await response.json() json_data = await response.json()
appliances_dir = self._builtin_appliances_path() appliances_dir = self.builtin_appliances_path()
downloaded_appliance_files = [] downloaded_appliance_files = []
for appliance in json_data: for appliance in json_data:
if appliance["type"] == "file": if appliance["type"] == "file":

View File

@ -15,11 +15,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import sys import sys
import aiohttp import aiohttp
import logging import logging
import asyncio import asyncio
import socket import socket
import ipaddress
from .base_gns3_vm import BaseGNS3VM from .base_gns3_vm import BaseGNS3VM
from .gns3_vm_error import GNS3VMError from .gns3_vm_error import GNS3VMError
@ -80,9 +82,6 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
except ValueError: except ValueError:
continue continue
self._system_properties[name.strip()] = value.strip() self._system_properties[name.strip()] = value.strip()
if "API Version" in self._system_properties:
# API version is not consistent between VirtualBox versions, the key is named "API Version" in VirtualBox 7
self._system_properties["API version"] = self._system_properties.pop("API Version")
async def _check_requirements(self): async def _check_requirements(self):
""" """
@ -92,16 +91,16 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
if not self._system_properties: if not self._system_properties:
await self._get_system_properties() await self._get_system_properties()
if "API version" not in self._system_properties: if "API version" not in self._system_properties:
raise VirtualBoxError("Can't access to VirtualBox API version:\n{}".format(self._system_properties)) raise GNS3VMError("Can't access to VirtualBox API version:\n{}".format(self._system_properties))
from cpuinfo import get_cpu_info from cpuinfo import get_cpu_info
cpu_info = await wait_run_in_executor(get_cpu_info) cpu_info = await wait_run_in_executor(get_cpu_info)
vendor_id = cpu_info.get('vendor_id_raw') vendor_id = cpu_info.get('vendor_id_raw')
if vendor_id == "GenuineIntel": if vendor_id == "GenuineIntel":
if parse_version(self._system_properties["API version"]) < parse_version("6_1"): if parse_version(self._system_properties["API version"]) < parse_version("6_1"):
raise VirtualBoxError("VirtualBox version 6.1 or above is required to run the GNS3 VM with nested virtualization enabled on Intel processors") raise GNS3VMError("VirtualBox version 6.1 or above is required to run the GNS3 VM with nested virtualization enabled on Intel processors")
elif vendor_id == "AuthenticAMD": elif vendor_id == "AuthenticAMD":
if parse_version(self._system_properties["API version"]) < parse_version("6_0"): if parse_version(self._system_properties["API version"]) < parse_version("6_0"):
raise VirtualBoxError("VirtualBox version 6.0 or above is required to run the GNS3 VM with nested virtualization enabled on AMD processors") raise GNS3VMError("VirtualBox version 6.0 or above is required to run the GNS3 VM with nested virtualization enabled on AMD processors")
else: else:
log.warning("Could not determine CPU vendor: {}".format(vendor_id)) log.warning("Could not determine CPU vendor: {}".format(vendor_id))
@ -162,6 +161,44 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
return True return True
return False return False
async def _add_dhcp_server(self, vboxnet):
"""
Add a DHCP server for vboxnet.
:param vboxnet: vboxnet name
"""
hostonlyifs = await self._execute("list", ["hostonlyifs"])
pattern = r"IPAddress:\s+(\d+\.\d+\.\d+\.\d+)\nNetworkMask:\s+(\d+\.\d+\.\d+\.\d+)"
match = re.search(pattern, hostonlyifs)
if match:
ip_address = match.group(1)
netmask = match.group(2)
else:
raise GNS3VMError("Could not find IP address and netmask for vboxnet {}".format(vboxnet))
try:
interface = ipaddress.IPv4Interface(f"{ip_address}/{netmask}")
subnet = ipaddress.IPv4Network(str(interface.network))
dhcp_server_ip = str(interface.ip + 1)
netmask = str(subnet.netmask)
lower_ip = str(interface.ip + 2)
upper_ip = str(subnet.network_address + subnet.num_addresses - 2)
except ValueError:
raise GNS3VMError("Invalid IP address and netmask for vboxnet {}: {}/{}".format(vboxnet, ip_address, netmask))
dhcp_server_args = [
"add",
"--network=HostInterfaceNetworking-{}".format(vboxnet),
"--server-ip={}".format(dhcp_server_ip),
"--netmask={}".format(netmask),
"--lower-ip={}".format(lower_ip),
"--upper-ip={}".format(upper_ip),
"--enable"
]
await self._execute("dhcpserver", dhcp_server_args)
async def _check_vboxnet_exists(self, vboxnet, vboxnet_type): async def _check_vboxnet_exists(self, vboxnet, vboxnet_type):
""" """
Check if the vboxnet interface exists Check if the vboxnet interface exists
@ -264,12 +301,20 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
await self.set_hostonly_network(interface_number, first_available_vboxnet) await self.set_hostonly_network(interface_number, first_available_vboxnet)
vboxnet = first_available_vboxnet vboxnet = first_available_vboxnet
else: else:
raise GNS3VMError('VirtualBox host-only network "{}" does not exist, please make the sure the network adapter {} configuration is valid for "{}"'.format(vboxnet, try:
await self._execute("hostonlyif", ["create"])
except GNS3VMError:
raise GNS3VMError('VirtualBox host-only network "{}" does not exist and could not be automatically created, please make the sure the network adapter {} configuration is valid for "{}"'.format(
vboxnet,
interface_number, interface_number,
self._vmname)) self._vmname
))
if backend_type == "hostonlyadapter" and not (await self._check_dhcp_server(vboxnet)): if backend_type == "hostonlyadapter" and not (await self._check_dhcp_server(vboxnet)):
raise GNS3VMError('DHCP must be enabled on VirtualBox host-only network "{}"'.format(vboxnet)) try:
await self._add_dhcp_server(vboxnet)
except GNS3VMError as e:
raise GNS3VMError("Could not add DHCP server for vboxnet {}: {}, please configure manually".format(vboxnet, e))
vm_state = await self._get_state() vm_state = await self._get_state()
log.info('"{}" state is {}'.format(self._vmname, vm_state)) log.info('"{}" state is {}'.format(self._vmname, vm_state))
@ -302,7 +347,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
except OSError as e: except OSError as e:
raise GNS3VMError("Error while getting random port: {}".format(e)) raise GNS3VMError("Error while getting random port: {}".format(e))
if (await self._check_vbox_port_forwarding()): if await self._check_vbox_port_forwarding():
# delete the GNS3VM NAT port forwarding rule if it exists # delete the GNS3VM NAT port forwarding rule if it exists
log.info("Removing GNS3VM NAT port forwarding rule from interface {}".format(nat_interface_number)) log.info("Removing GNS3VM NAT port forwarding rule from interface {}".format(nat_interface_number))
await self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number), "delete", "GNS3VM"]) await self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number), "delete", "GNS3VM"])

View File

@ -57,7 +57,7 @@ class CrashReport:
Report crash to a third party service Report crash to a third party service
""" """
DSN = "https://226eee142b22cc399d1566b3dd4cbc86@o19455.ingest.sentry.io/38482" DSN = "https://8dcaf668c2f31af6028fb4130bf2f58e@o19455.ingest.sentry.io/38482"
_instance = None _instance = None
def __init__(self): def __init__(self):

View File

@ -23,8 +23,8 @@
# or negative for a release candidate or beta (after the base version # or negative for a release candidate or beta (after the base version
# number has been incremented) # number has been incremented)
__version__ = "2.2.42" __version__ = "2.2.43"
__version_info__ = (2, 2, 42, 0) __version_info__ = (2, 2, 43, 0)
if "dev" in __version__: if "dev" in __version__:
try: try:

View File

@ -232,8 +232,7 @@ class Route(object):
response.set_status(408) response.set_status(408)
response.json({"message": "Request canceled", "status": 408}) response.json({"message": "Request canceled", "status": 408})
raise # must raise to let aiohttp know the connection has been closed raise # must raise to let aiohttp know the connection has been closed
except aiohttp.ClientError: except (ConnectionResetError, aiohttp.ClientError):
log.warning("Client error")
response = Response(request=request, route=route) response = Response(request=request, route=route)
response.set_status(408) response.set_status(408)
response.json({"message": "Client error", "status": 408}) response.json({"message": "Client error", "status": 408})

View File

@ -1,17 +1,18 @@
jsonschema>=4.17.3,<4.18; python_version >= '3.7' jsonschema>=4.17.3,<4.18; python_version >= '3.7' # v4.17.3 is the last version to support Python 3.7
jsonschema==3.2.0; python_version < '3.7' # v3.2.0 is the last version to support Python 3.6 jsonschema==3.2.0; python_version < '3.7' # v3.2.0 is the last version to support Python 3.6
aiohttp>=3.8.4,<3.9 aiohttp>=3.8.5,<3.9
aiohttp-cors>=0.7.0,<0.8 aiohttp-cors>=0.7.0,<0.8
aiofiles>=23.1.0,<23.2; python_version >= '3.7' aiofiles>=23.2.1,<23.3; python_version >= '3.7'
aiofiles==0.8.0; python_version < '3.7' # v0.8.0 is the last version to support Python 3.6 aiofiles==0.8.0; python_version < '3.7' # v0.8.0 is the last version to support Python 3.6
Jinja2>=3.1.2,<3.2; python_version >= '3.7' Jinja2>=3.1.2,<3.2; python_version >= '3.7'
Jinja2==3.0.3; python_version < '3.7' # v3.0.3 is the last version to support Python 3.6 Jinja2==3.0.3; python_version < '3.7' # v3.0.3 is the last version to support Python 3.6
sentry-sdk==1.29.2,<1.30 sentry-sdk==1.31.0,<1.32
psutil==5.9.5 psutil==5.9.5
async-timeout>=4.0.2,<4.1 async-timeout>=4.0.2,<4.1
distro>=1.8.0 distro>=1.8.0
py-cpuinfo>=9.0.0,<10.0 py-cpuinfo>=9.0.0,<10.0
platformdirs>=2.4.0
importlib-resources>=1.3; python_version <= '3.9' importlib-resources>=1.3; python_version <= '3.9'
truststore>=0.7.0; python_version >= '3.10' truststore>=0.8.0; python_version >= '3.10'
setuptools>=60.8.1; python_version >= '3.7' setuptools>=60.8.1; python_version >= '3.7'
setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6 setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6

View File

@ -182,7 +182,7 @@ async def test_create_vnc(compute_project, manager):
"Binds": [ "Binds": [
"{}:/gns3:ro".format(get_resource("compute/docker/resources")), "{}:/gns3:ro".format(get_resource("compute/docker/resources")),
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")), "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")),
'/tmp/.X11-unix/:/tmp/.X11-unix/' "/tmp/.X11-unix/X{0}:/tmp/.X11-unix/X{0}:ro".format(vm._display)
], ],
"Privileged": True "Privileged": True
}, },

View File

@ -1,4 +1,4 @@
-rrequirements.txt -rrequirements.txt
pywin32==305 pywin32==306
wmi==1.5.1 wmi==1.5.1