gns3-server 3.0.3__py3-none-any.whl → 3.0.5__py3-none-any.whl

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.

Potentially problematic release.


This version of gns3-server might be problematic. Click here for more details.

Files changed (116) hide show
  1. gns3_server-3.0.5.dist-info/METADATA +202 -0
  2. {gns3_server-3.0.3.dist-info → gns3_server-3.0.5.dist-info}/RECORD +114 -59
  3. {gns3_server-3.0.3.dist-info → gns3_server-3.0.5.dist-info}/WHEEL +1 -1
  4. gns3server/api/routes/compute/cloud_nodes.py +1 -1
  5. gns3server/api/routes/controller/computes.py +1 -1
  6. gns3server/api/routes/controller/images.py +0 -5
  7. gns3server/api/routes/controller/links.py +52 -1
  8. gns3server/appliances/almalinux.gns3a +30 -0
  9. gns3server/appliances/arista-veos.gns3a +15 -15
  10. gns3server/appliances/aruba-arubaoscx.gns3a +39 -0
  11. gns3server/appliances/asterfusion-vAsterNOS-campus.gns3a +50 -0
  12. gns3server/appliances/asterfusion-vAsterNOS.gns3a +1 -1
  13. gns3server/appliances/centos-cloud.gns3a +24 -54
  14. gns3server/appliances/cisco-asav.gns3a +2 -2
  15. gns3server/appliances/cisco-c8000v.gns3a +15 -2
  16. gns3server/appliances/cisco-cat9k.gns3a +2 -2
  17. gns3server/appliances/cisco-csr1000v.gns3a +3 -3
  18. gns3server/appliances/cisco-iosv.gns3a +22 -7
  19. gns3server/appliances/cisco-iosvl2.gns3a +2 -1
  20. gns3server/appliances/cisco-iosxrv.gns3a +2 -2
  21. gns3server/appliances/cisco-iosxrv9k.gns3a +16 -133
  22. gns3server/appliances/cisco-iou-l2.gns3a +2 -0
  23. gns3server/appliances/cisco-iou-l3.gns3a +2 -0
  24. gns3server/appliances/cisco-nxosv.gns3a +2 -1
  25. gns3server/appliances/cisco-nxosv9k.gns3a +36 -343
  26. gns3server/appliances/exos.gns3a +13 -0
  27. gns3server/appliances/fedora-cloud.gns3a +30 -0
  28. gns3server/appliances/fortianalyzer.gns3a +42 -0
  29. gns3server/appliances/fortigate.gns3a +42 -0
  30. gns3server/appliances/fortimanager.gns3a +43 -1
  31. gns3server/appliances/infix.gns3a +118 -0
  32. gns3server/appliances/juniper-vJunos-router.gns3a +75 -0
  33. gns3server/appliances/nethsecurity.gns3a +44 -0
  34. gns3server/appliances/oracle-linux-cloud.gns3a +31 -1
  35. gns3server/appliances/rhel.gns3a +57 -1
  36. gns3server/appliances/rockylinux.gns3a +15 -0
  37. gns3server/appliances/vyos.gns3a +13 -0
  38. gns3server/compute/builtin/nodes/nat.py +1 -1
  39. gns3server/compute/docker/__init__.py +8 -9
  40. gns3server/compute/docker/docker_vm.py +2 -1
  41. gns3server/compute/qemu/qemu_vm.py +69 -28
  42. gns3server/compute/vmware/vmware_vm.py +1 -2
  43. gns3server/controller/__init__.py +14 -0
  44. gns3server/controller/compute.py +4 -3
  45. gns3server/controller/gns3vm/virtualbox_gns3_vm.py +55 -28
  46. gns3server/crash_report.py +1 -1
  47. gns3server/custom_symbols/6wind.svg +1 -0
  48. gns3server/custom_symbols/alpine-virt-qemu.svg +46 -0
  49. gns3server/custom_symbols/arista_bybaro.svg +24 -0
  50. gns3server/custom_symbols/asterfusion-vAsterNOS.svg +48 -0
  51. gns3server/custom_symbols/cisco-fmcv.svg +498 -0
  52. gns3server/custom_symbols/cisco-ise.svg +244 -0
  53. gns3server/custom_symbols/csr1000v_bybaro.svg +38 -0
  54. gns3server/custom_symbols/cumulus_bybaro.svg +27 -0
  55. gns3server/custom_symbols/firefox.svg +1 -0
  56. gns3server/custom_symbols/fortigate_bybaro.svg +42 -0
  57. gns3server/custom_symbols/fortinet.svg +72 -0
  58. gns3server/custom_symbols/innovaphone-ap-icon.jpg +0 -0
  59. gns3server/custom_symbols/innovaphone-pbx-green.png +0 -0
  60. gns3server/custom_symbols/juniper-vmx.svg +284 -0
  61. gns3server/custom_symbols/juniper-vqfx.svg +324 -0
  62. gns3server/custom_symbols/junos_olive_bybaro.svg +18 -0
  63. gns3server/custom_symbols/linux_guest.svg +1917 -0
  64. gns3server/custom_symbols/loadbalancer.svg +380 -0
  65. gns3server/custom_symbols/loadbalancer_docker.svg +387 -0
  66. gns3server/custom_symbols/mgmt_station.svg +1967 -0
  67. gns3server/custom_symbols/mgmt_station_docker.svg +1996 -0
  68. gns3server/custom_symbols/microsoft.svg +85 -0
  69. gns3server/custom_symbols/multilayer_switch_docker.svg +332 -0
  70. gns3server/custom_symbols/multilayer_switch_green_docker.svg +344 -0
  71. gns3server/custom_symbols/multilayer_switch_red_docker.svg +344 -0
  72. gns3server/custom_symbols/opennac.png +0 -0
  73. gns3server/custom_symbols/opensuse.svg +126 -0
  74. gns3server/custom_symbols/openvswitch_bybaro.svg +26 -0
  75. gns3server/custom_symbols/ostinato-3d-icon.svg +95 -0
  76. gns3server/custom_symbols/packetfence2.png +0 -0
  77. gns3server/custom_symbols/pan-vm-fw.svg +364 -0
  78. gns3server/custom_symbols/parrotlogo.png +0 -0
  79. gns3server/custom_symbols/qemu_guest_win10_bybaro.svg +56 -0
  80. gns3server/custom_symbols/qemu_guest_win7_bybaro.svg +59 -0
  81. gns3server/custom_symbols/qemu_guest_win8_bybaro.svg +56 -0
  82. gns3server/custom_symbols/router_docker.svg +213 -0
  83. gns3server/custom_symbols/router_green.svg +206 -0
  84. gns3server/custom_symbols/router_green_docker.svg +665 -0
  85. gns3server/custom_symbols/router_red.svg +206 -0
  86. gns3server/custom_symbols/router_red_docker.svg +213 -0
  87. gns3server/custom_symbols/rpi.png +0 -0
  88. gns3server/custom_symbols/securityonion-logo.png +0 -0
  89. gns3server/custom_symbols/shieldedtux.png +0 -0
  90. gns3server/custom_symbols/steelhead-vcx.png +0 -0
  91. gns3server/custom_symbols/stormshield.png +0 -0
  92. gns3server/custom_symbols/ubuntu.svg +1750 -0
  93. gns3server/custom_symbols/vRIN.svg +316 -0
  94. gns3server/custom_symbols/vsrx_bybaro.svg +44 -0
  95. gns3server/custom_symbols/vyos.svg +35 -0
  96. gns3server/disks/OVMF_CODE_4M.fd +0 -0
  97. gns3server/disks/OVMF_VARS_4M.fd +0 -0
  98. gns3server/schemas/__init__.py +1 -1
  99. gns3server/schemas/compute/docker_nodes.py +2 -2
  100. gns3server/schemas/compute/dynamips_nodes.py +5 -5
  101. gns3server/schemas/compute/iou_nodes.py +2 -2
  102. gns3server/schemas/controller/links.py +21 -0
  103. gns3server/schemas/controller/templates/docker_templates.py +2 -2
  104. gns3server/schemas/controller/templates/dynamips_templates.py +19 -19
  105. gns3server/schemas/controller/templates/iou_templates.py +4 -4
  106. gns3server/schemas/controller/templates/qemu_templates.py +1 -1
  107. gns3server/schemas/controller/users.py +3 -3
  108. gns3server/static/web-ui/index.html +1 -1
  109. gns3server/static/web-ui/main.fd9d76d279fa7d5e.js +1 -0
  110. gns3server/utils/images.py +10 -0
  111. gns3server/version.py +2 -2
  112. gns3_server-3.0.3.dist-info/METADATA +0 -869
  113. gns3server/static/web-ui/main.2e807eb4bc32f838.js +0 -1
  114. {gns3_server-3.0.3.dist-info → gns3_server-3.0.5.dist-info}/entry_points.txt +0 -0
  115. {gns3_server-3.0.3.dist-info → gns3_server-3.0.5.dist-info/licenses}/LICENSE +0 -0
  116. {gns3_server-3.0.3.dist-info → gns3_server-3.0.5.dist-info}/top_level.txt +0 -0
@@ -30,6 +30,13 @@
30
30
  "on_close": "shutdown_signal"
31
31
  },
32
32
  "images": [
33
+ {
34
+ "filename": "vyos-1.4.1-kvm-amd64.qcow2",
35
+ "version": "1.4.1",
36
+ "md5sum": "5b8ebcd9905719d24405ed093afdd8ba",
37
+ "filesize": 513736704,
38
+ "download_url": "https://support.vyos.io/"
39
+ },
33
40
  {
34
41
  "filename": "vyos-1.4.0-kvm-amd64.qcow2",
35
42
  "version": "1.4.0",
@@ -109,6 +116,12 @@
109
116
  }
110
117
  ],
111
118
  "versions": [
119
+ {
120
+ "name": "1.4.1",
121
+ "images": {
122
+ "hda_disk_image": "vyos-1.4.1-kvm-amd64.qcow2"
123
+ }
124
+ },
112
125
  {
113
126
  "name": "1.4.0",
114
127
  "images": {
@@ -37,7 +37,7 @@ class Nat(Cloud):
37
37
  def __init__(self, name, node_id, project, manager, ports=None):
38
38
 
39
39
  allowed_interfaces = Config.instance().settings.Server.allowed_interfaces
40
- if allowed_interfaces:
40
+ if allowed_interfaces and isinstance(allowed_interfaces, str):
41
41
  allowed_interfaces = allowed_interfaces.split(',')
42
42
  if sys.platform.startswith("linux"):
43
43
  nat_interface = Config.instance().settings.Server.default_nat_interface
@@ -175,11 +175,10 @@ class Docker(BaseManager):
175
175
  response = await self.http_query(method, path, data=data, params=params)
176
176
  body = await response.read()
177
177
  response.close()
178
- if body and len(body):
179
- if response.headers.get('CONTENT-TYPE') == 'application/json':
180
- body = json.loads(body.decode("utf-8"))
181
- else:
182
- body = body.decode("utf-8")
178
+ if response.headers.get('CONTENT-TYPE') == 'application/json':
179
+ body = json.loads(body.decode("utf-8", errors="ignore"))
180
+ else:
181
+ body = body.decode("utf-8", errors="ignore")
183
182
  log.debug("Query Docker %s %s params=%s data=%s Response: %s", method, path, params, data, body)
184
183
  return body
185
184
 
@@ -267,12 +266,12 @@ class Docker(BaseManager):
267
266
  pass
268
267
 
269
268
  if progress_callback:
270
- progress_callback(f"Pulling '{image}' from docker hub")
269
+ progress_callback(f"Pulling '{image}' from Docker repository")
271
270
  try:
272
271
  response = await self.http_query("POST", "images/create", params={"fromImage": image}, timeout=None)
273
272
  except DockerError as e:
274
273
  raise DockerError(
275
- f"Could not pull the '{image}' image from Docker Hub, "
274
+ f"Could not pull the '{image}' image from Docker repository, "
276
275
  f"please check your Internet connection (original error: {e})"
277
276
  )
278
277
  # The pull api will stream status via an HTTP JSON stream
@@ -281,10 +280,10 @@ class Docker(BaseManager):
281
280
  try:
282
281
  chunk = await response.content.read(CHUNK_SIZE)
283
282
  except aiohttp.ServerDisconnectedError:
284
- log.error(f"Disconnected from server while pulling Docker image '{image}' from docker hub")
283
+ log.error(f"Disconnected from server while pulling Docker image '{image}' from Docker repository")
285
284
  break
286
285
  except asyncio.TimeoutError:
287
- log.error(f"Timeout while pulling Docker image '{image}' from docker hub")
286
+ log.error("Timeout while pulling Docker image '{}' from Docker repository".format(image))
288
287
  break
289
288
  if not chunk:
290
289
  break
@@ -437,7 +437,7 @@ class DockerVM(BaseNode):
437
437
  try:
438
438
  image_infos = await self._get_image_information()
439
439
  except DockerHttp404Error:
440
- log.info(f"Image '{self._image}' is missing, pulling it from Docker hub...")
440
+ log.info("Image '{}' is missing, pulling it from Docker repository...".format(self._image))
441
441
  await self.pull_image(self._image)
442
442
  image_infos = await self._get_image_information()
443
443
 
@@ -617,6 +617,7 @@ class DockerVM(BaseNode):
617
617
  await self._clean_servers()
618
618
 
619
619
  await self.manager.query("POST", f"containers/{self._cid}/start")
620
+ await asyncio.sleep(0.5) # give the Docker container some time to start
620
621
  self._namespace = await self._get_namespace()
621
622
 
622
623
  await self._start_ubridge(require_privileged_access=True)
@@ -32,6 +32,7 @@ import subprocess
32
32
  import time
33
33
  import json
34
34
  import shlex
35
+ import psutil
35
36
 
36
37
  from gns3server.utils import parse_version
37
38
  from gns3server.utils.asyncio import subprocess_check_output, cancellable_wait_run_in_executor
@@ -265,17 +266,10 @@ class QemuVM(BaseNode):
265
266
  if qemu_bin == "qemu":
266
267
  self._platform = "i386"
267
268
  else:
268
- self._platform = re.sub(r'^qemu-system-(\w+).*$', r'\1', qemu_bin, re.IGNORECASE)
269
-
270
- try:
271
- QemuPlatform(self._platform.split(".")[0])
272
- except ValueError:
269
+ self._platform = re.sub(r'^qemu-system-(\w+).*$', r'\1', qemu_bin, flags=re.IGNORECASE)
270
+ if self._platform.split(".")[0] not in list(QemuPlatform):
273
271
  raise QemuError(f"Platform {self._platform} is unknown")
274
- log.info(
275
- 'QEMU VM "{name}" [{id}] has set the QEMU path to {qemu_path}'.format(
276
- name=self._name, id=self._id, qemu_path=qemu_path
277
- )
278
- )
272
+ log.info(f'QEMU VM "{self._name}" [{self._name}] has set the QEMU path to {qemu_path}')
279
273
 
280
274
  def _check_qemu_path(self, qemu_path):
281
275
 
@@ -1225,6 +1219,21 @@ class QemuVM(BaseNode):
1225
1219
  except OSError as e:
1226
1220
  raise QemuError(f"Could not start Telnet QEMU console {e}\n")
1227
1221
 
1222
+ def _find_partition_for_path(self, path):
1223
+ """
1224
+ Finds the disk partition for a given path.
1225
+ """
1226
+
1227
+ path = os.path.abspath(path)
1228
+ partitions = psutil.disk_partitions()
1229
+ # find the partition with the longest matching mount point
1230
+ matching_partition = None
1231
+ for partition in partitions:
1232
+ if path.startswith(partition.mountpoint):
1233
+ if matching_partition is None or len(partition.mountpoint) > len(matching_partition.mountpoint):
1234
+ matching_partition = partition
1235
+ return matching_partition
1236
+
1228
1237
  async def _termination_callback(self, returncode):
1229
1238
  """
1230
1239
  Called when the process has stopped.
@@ -1236,9 +1245,19 @@ class QemuVM(BaseNode):
1236
1245
  log.info("QEMU process has stopped, return code: %d", returncode)
1237
1246
  await self.stop()
1238
1247
  if returncode != 0:
1248
+ qemu_stdout = self.read_stdout()
1249
+ # additional permissions need to be configured for swtpm in AppArmor if the working dir
1250
+ # is located on a different partition than the partition for the root directory
1251
+ if "TPM result for CMD_INIT" in qemu_stdout:
1252
+ partition = self._find_partition_for_path(self.project.path)
1253
+ if partition and partition.mountpoint != "/":
1254
+ qemu_stdout += "\nTPM error: the project directory is not on the same partition as the root directory which can be a problem when using AppArmor.\n" \
1255
+ "Please try to execute the following commands on the server:\n\n" \
1256
+ "echo 'owner {}/** rwk,' | sudo tee /etc/apparmor.d/local/usr.bin.swtpm > /dev/null\n" \
1257
+ "sudo service apparmor restart".format(os.path.dirname(self.project.path))
1239
1258
  self.project.emit(
1240
1259
  "log.error",
1241
- {"message": f"QEMU process has stopped, return code: {returncode}\n{self.read_stdout()}"},
1260
+ {"message": f"QEMU process has stopped, return code: {returncode}\n{qemu_stdout}"},
1242
1261
  )
1243
1262
 
1244
1263
  async def stop(self):
@@ -2287,19 +2306,42 @@ class QemuVM(BaseNode):
2287
2306
  else:
2288
2307
  raise QemuError(f"bios image '{self._bios_image}' is not accessible")
2289
2308
  options.extend(["-bios", self._bios_image.replace(",", ",,")])
2309
+
2290
2310
  elif self._uefi:
2291
- # get the OVMF firmware from the images directory
2292
- ovmf_firmware_path = self.manager.get_abs_image_path("OVMF_CODE.fd")
2311
+
2312
+ old_ovmf_vars_path = os.path.join(self.working_dir, "OVMF_VARS.fd")
2313
+ if os.path.exists(old_ovmf_vars_path):
2314
+ # the node has its own UEFI variables store already, we must also use the old UEFI firmware
2315
+ ovmf_firmware_path = self.manager.get_abs_image_path("OVMF_CODE.fd")
2316
+ else:
2317
+ system_ovmf_firmware_path = "/usr/share/OVMF/OVMF_CODE_4M.fd"
2318
+ if os.path.exists(system_ovmf_firmware_path):
2319
+ ovmf_firmware_path = system_ovmf_firmware_path
2320
+ else:
2321
+ # otherwise, get the UEFI firmware from the images directory
2322
+ ovmf_firmware_path = self.manager.get_abs_image_path("OVMF_CODE_4M.fd")
2323
+
2293
2324
  log.info("Configuring UEFI boot mode using OVMF file: '{}'".format(ovmf_firmware_path))
2294
2325
  options.extend(["-drive", "if=pflash,format=raw,readonly,file={}".format(ovmf_firmware_path)])
2295
2326
 
2327
+ # try to use the UEFI variables store from the system first
2328
+ system_ovmf_vars_path = "/usr/share/OVMF/OVMF_VARS_4M.fd"
2329
+ if os.path.exists(system_ovmf_vars_path):
2330
+ ovmf_vars_path = system_ovmf_vars_path
2331
+ else:
2332
+ # otherwise, get the UEFI variables store from the images directory
2333
+ ovmf_vars_path = self.manager.get_abs_image_path("OVMF_VARS_4M.fd")
2334
+
2296
2335
  # the node should have its own copy of OVMF_VARS.fd (the UEFI variables store)
2297
- ovmf_vars_node_path = os.path.join(self.working_dir, "OVMF_VARS.fd")
2298
- if not os.path.exists(ovmf_vars_node_path):
2299
- try:
2300
- shutil.copyfile(self.manager.get_abs_image_path("OVMF_VARS.fd"), ovmf_vars_node_path)
2301
- except OSError as e:
2302
- raise QemuError("Cannot copy OVMF_VARS.fd file to the node working directory: {}".format(e))
2336
+ if os.path.exists(old_ovmf_vars_path):
2337
+ ovmf_vars_node_path = old_ovmf_vars_path
2338
+ else:
2339
+ ovmf_vars_node_path = os.path.join(self.working_dir, "OVMF_VARS_4M.fd")
2340
+ if not os.path.exists(ovmf_vars_node_path):
2341
+ try:
2342
+ shutil.copyfile(ovmf_vars_path, ovmf_vars_node_path)
2343
+ except OSError as e:
2344
+ raise QemuError("Cannot copy OVMF_VARS_4M.fd file to the node working directory: {}".format(e))
2303
2345
  options.extend(["-drive", "if=pflash,format=raw,file={}".format(ovmf_vars_node_path)])
2304
2346
  return options
2305
2347
 
@@ -2396,16 +2438,13 @@ class QemuVM(BaseNode):
2396
2438
  ) # we do not want any user networking back-end if no adapter is connected.
2397
2439
 
2398
2440
  # Each 32 PCI device we need to add a PCI bridge with max 9 bridges
2399
- pci_devices = 4 + len(self._ethernet_adapters) # 4 PCI devices are use by default by qemu
2400
- pci_bridges = math.floor(pci_devices / 32)
2441
+ # Reserve 32 devices on root pci_bridge,
2442
+ # since the number of devices used by templates may differ significantly
2443
+ # and pci_bridges also consume IDs.
2444
+ # Move network devices to their own bridge
2445
+ pci_devices_reserved = 32
2401
2446
  pci_bridges_created = 0
2402
- if pci_bridges >= 1:
2403
- if self._qemu_version and parse_version(self._qemu_version) < parse_version("2.4.0"):
2404
- raise QemuError(
2405
- "Qemu version 2.4 or later is required to run this VM with a large number of network adapters"
2406
- )
2407
-
2408
- pci_device_id = 4 + pci_bridges # Bridge consume PCI ports
2447
+ pci_device_id = pci_devices_reserved
2409
2448
  for adapter_number, adapter in enumerate(self._ethernet_adapters):
2410
2449
  mac = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number)
2411
2450
 
@@ -2596,6 +2635,8 @@ class QemuVM(BaseNode):
2596
2635
  """
2597
2636
 
2598
2637
  self._qemu_version = await self.manager.get_qemu_version(self.qemu_path)
2638
+ if self._qemu_version and parse_version(self._qemu_version) < parse_version("2.4.0"):
2639
+ raise QemuError("Qemu version 2.4 or later is required to run Qemu VMs")
2599
2640
  vm_name = self._name.replace(",", ",,")
2600
2641
  project_path = self.project.path.replace(",", ",,")
2601
2642
  additional_options = self._options.strip()
@@ -352,6 +352,7 @@ class VMwareVM(BaseNode):
352
352
  vnet = self._get_vnet(adapter_number)
353
353
  await self._ubridge_send(f"bridge create {vnet}")
354
354
  vmnet_interface = os.path.basename(self._vmx_pairs[vnet])
355
+ block_host_traffic = self.manager.config.settings.VMware.block_host_traffic
355
356
 
356
357
  if sys.platform.startswith("darwin"):
357
358
  if parse_version(platform.mac_ver()[0]) >= parse_version("11.0.0"):
@@ -360,13 +361,11 @@ class VMwareVM(BaseNode):
360
361
  vmnet_interface = self.manager.find_bridge_interface(vmnet_interface)
361
362
  if not vmnet_interface:
362
363
  raise VMwareError(f"Could not find bridge interface linked with {vmnet_interface}")
363
- block_host_traffic = self.manager.config.get_section_config("VMware").getboolean("block_host_traffic", False)
364
364
  await self._add_ubridge_ethernet_connection(vnet, vmnet_interface, block_host_traffic)
365
365
  else:
366
366
  # special case on macOS, we cannot bind VMnet interfaces using the libpcap
367
367
  await self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(name=vnet, interface=vmnet_interface))
368
368
  else:
369
- block_host_traffic = self.manager.config.VMware.block_host_traffic
370
369
  await self._add_ubridge_ethernet_connection(vnet, vmnet_interface, block_host_traffic)
371
370
 
372
371
  if isinstance(nio, NIOUDP):
@@ -74,6 +74,7 @@ class Controller:
74
74
 
75
75
  log.info("Controller is starting")
76
76
  await self._install_base_configs()
77
+ await self._install_custom_symbols()
77
78
  installed_disks = await self._install_builtin_disks()
78
79
  if installed_disks:
79
80
  await update_disk_checksums(installed_disks)
@@ -371,6 +372,19 @@ class Controller:
371
372
  except OSError as e:
372
373
  log.error(f"Could not install disk files to {dst_path}: {e}")
373
374
 
375
+ async def _install_custom_symbols(self):
376
+ """
377
+ At startup we copy custom symbols to the user location to allow
378
+ them to use with appliances
379
+ """
380
+
381
+ dst_path = self.symbols.symbols_path()
382
+ log.info(f"Installing custom symbols in '{dst_path}'")
383
+ try:
384
+ return await Controller.install_resource_files(dst_path, "custom_symbols", upgrade_resources=False)
385
+ except OSError as e:
386
+ log.error(f"Could not install custom symbols to {dst_path}: {e}")
387
+
374
388
  def images_path(self):
375
389
  """
376
390
  Get the image storage directory
@@ -458,10 +458,11 @@ class Compute:
458
458
  # FIXME: slow down number of compute events
459
459
  self._controller.notification.controller_emit("compute.updated", self.asdict())
460
460
  else:
461
- if action == "log.error":
462
- log.error(event.pop("message"))
463
461
  await self._controller.notification.dispatch(
464
- action, event, project_id=project_id, compute_id=self.id
462
+ action,
463
+ event,
464
+ project_id=project_id,
465
+ compute_id=self.id
465
466
  )
466
467
  else:
467
468
  if response.type == aiohttp.WSMsgType.CLOSE:
@@ -249,6 +249,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
249
249
  return True
250
250
  return False
251
251
 
252
+
252
253
  async def list(self):
253
254
  """
254
255
  List all VirtualBox VMs
@@ -269,8 +270,8 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
269
270
 
270
271
  # get a NAT interface number
271
272
  nat_interface_number = await self._look_for_interface("nat")
272
- if nat_interface_number < 0:
273
- raise GNS3VMError(f'VM "{self.vmname}" must have a NAT interface configured in order to start')
273
+ if nat_interface_number < 0 and await self._look_for_interface("natnetwork") < 0:
274
+ raise GNS3VMError(f'VM "{self.vmname}" must have a NAT interface or NAT Network configured in order to start')
274
275
 
275
276
  if sys.platform.startswith("darwin") and parse_version(self._system_properties["API version"]) >= parse_version("7_0"):
276
277
  # VirtualBox 7.0+ on macOS requires a host-only network interface
@@ -339,42 +340,68 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
339
340
  elif vm_state == "paused":
340
341
  args = [self._vmname, "resume"]
341
342
  await self._execute("controlvm", args)
342
- ip_address = "127.0.0.1"
343
- try:
344
- # get a random port on localhost
345
- with socket.socket() as s:
346
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
347
- s.bind((ip_address, 0))
348
- api_port = s.getsockname()[1]
349
- except OSError as e:
350
- raise GNS3VMError(f"Error while getting random port: {e}")
351
-
352
- if await self._check_vbox_port_forwarding():
353
- # delete the GNS3VM NAT port forwarding rule if it exists
354
- log.info(f"Removing GNS3VM NAT port forwarding rule from interface {nat_interface_number}")
355
- await self._execute("controlvm", [self._vmname, f"natpf{nat_interface_number}", "delete", "GNS3VM"])
356
-
357
- # add a GNS3VM NAT port forwarding rule to redirect 127.0.0.1 with random port to the port in the VM
358
- log.info(f"Adding GNS3VM NAT port forwarding rule with port {api_port} to interface {nat_interface_number}")
359
- await self._execute(
360
- "controlvm",
361
- [self._vmname, f"natpf{nat_interface_number}", f"GNS3VM,tcp,{ip_address},{api_port},,{self.port}"],
362
- )
363
343
 
364
- self.ip_address = await self._get_ip(interface_number, api_port)
365
- log.info("GNS3 VM has been started with IP {}".format(self.ip_address))
344
+ log.info("Retrieving IP address from GNS3 VM...")
345
+ ip = await self._get_ip_from_guest_property()
346
+ if ip:
347
+ self.ip_address = ip
348
+ else:
349
+ # if we can't get the IP address from the guest property, we try to get it from the GNS3 server (a NAT interface is required)
350
+ if nat_interface_number < 0:
351
+ raise GNS3VMError("Could not find guest IP address for {}".format(self.vmname))
352
+ log.warning("Could not find IP address from guest property, trying to get it from GNS3 server")
353
+ ip_address = "127.0.0.1"
354
+ try:
355
+ # get a random port on localhost
356
+ with socket.socket() as s:
357
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
358
+ s.bind((ip_address, 0))
359
+ api_port = s.getsockname()[1]
360
+ except OSError as e:
361
+ raise GNS3VMError("Error while getting random port: {}".format(e))
362
+
363
+ if await self._check_vbox_port_forwarding():
364
+ # delete the GNS3VM NAT port forwarding rule if it exists
365
+ log.info("Removing GNS3VM NAT port forwarding rule from interface {}".format(nat_interface_number))
366
+ await self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number), "delete", "GNS3VM"])
367
+
368
+ # add a GNS3VM NAT port forwarding rule to redirect 127.0.0.1 with random port to the port in the VM
369
+ log.info("Adding GNS3VM NAT port forwarding rule with port {} to interface {}".format(api_port, nat_interface_number))
370
+ await self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number),
371
+ "GNS3VM,tcp,{},{},,{}".format(ip_address, api_port, self.port)])
372
+
373
+ self.ip_address = await self._get_ip_from_server(interface_number, api_port)
374
+
375
+ log.info("GNS3 VM has been started with IP '{}'".format(self.ip_address))
366
376
  self.running = True
367
377
 
368
- async def _get_ip(self, hostonly_interface_number, api_port):
378
+ async def _get_ip_from_guest_property(self):
379
+ """
380
+ Get the IP from VirtualBox by retrieving the guest property (Guest Additions must be installed).
381
+ """
382
+
383
+ remaining_try = 180 # try for 3 minutes
384
+ while remaining_try > 0:
385
+ result = await self._execute("guestproperty", ["get", self._vmname, "/VirtualBox/GuestInfo/Net/0/V4/IP"])
386
+ for info in result.splitlines():
387
+ if ':' in info:
388
+ name, value = info.split(':', 1)
389
+ if name == "Value":
390
+ return value.strip()
391
+ remaining_try -= 1
392
+ await asyncio.sleep(1)
393
+ return None
394
+
395
+ async def _get_ip_from_server(self, hostonly_interface_number, api_port):
369
396
  """
370
- Get the IP from VirtualBox.
397
+ Get the IP from VirtualBox by sending a request to the GNS3 server.
371
398
 
372
399
  Due to VirtualBox limitation the only way is to send request each
373
400
  second to a GNS3 endpoint in order to get the list of the interfaces and
374
401
  their IP and after that match it with VirtualBox host only.
375
402
  """
376
403
 
377
- remaining_try = 300
404
+ remaining_try = 180 # try for 3 minutes
378
405
  while remaining_try > 0:
379
406
  try:
380
407
  async with HTTPClient.get(f"http://127.0.0.1:{api_port}/v3/compute/network/interfaces") as resp:
@@ -58,7 +58,7 @@ class CrashReport:
58
58
  Report crash to a third party service
59
59
  """
60
60
 
61
- DSN = "https://2c96fa0280f82c48108f122b87cd902c@o19455.ingest.us.sentry.io/38482"
61
+ DSN = "https://61bb46252cabeebd49ee1e09fb8ba72e@o19455.ingest.us.sentry.io/38482"
62
62
  _instance = None
63
63
 
64
64
  def __init__(self):
@@ -0,0 +1 @@
1
+ <svg id="f6fe4215-7336-420b-bd07-06692844737a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 67.6 65.59"><title>vrouter-logo-generic</title><circle cx="34" cy="32.75" r="19.09" style="fill:#3d4859;opacity:0.8999999761581421;isolation:isolate"/><circle cx="34.04" cy="32.79" r="11.18" style="fill:#5e7ce2;opacity:0.8999999761581421;isolation:isolate"/><path d="M44.61,7.88l20.74-5.6c1.22-.33,2,.41,1.7,1.65L62.21,24.86c-.47,2-3.1,3.65-4.17,2.62l-4.92-4.76-7.84,8.13c-1.07,1.11-2.48,1.49-3.15.85l-3.62-3.49c-.66-.64-.33-2.07.73-3.18l7.84-8.13-4.93-4.76C41.07,11.1,42.62,8.42,44.61,7.88Z" transform="translate(-1.16 -2.2)" style="fill:#0aaded;opacity:0.8999999761581421;isolation:isolate"/><path d="M26.06,62.11,5.32,67.72c-1.21.33-2-.42-1.7-1.65L8.47,45.14c.44-2,3.08-3.65,4.15-2.62l4.93,4.76,7.84-8.13c1.07-1.11,2.49-1.49,3.15-.85l3.63,3.49c.67.64.33,2.07-.72,3.18L23.61,53.1l4.91,4.76C29.6,58.9,28.05,61.58,26.06,62.11Z" transform="translate(-1.16 -2.2)" style="fill:#5e7ce2;opacity:0.8999999761581421;isolation:isolate"/><path d="M25.29,8.71l5.6,20.75c.33,1.21-.41,2-1.64,1.69L8.31,26.31c-2-.47-3.65-3.1-2.61-4.17l4.75-4.91L2.32,9.38C1.21,8.31.83,6.9,1.47,6.24L5,2.6c.64-.66,2.07-.33,3.18.73l8.13,7.86L21,6.25C22.07,5.17,24.75,6.72,25.29,8.71Z" transform="translate(-1.16 -2.2)" style="fill:#5e7ce2;opacity:0.8999999761581421;isolation:isolate"/><path d="M44.64,61.15,39,40.4c-.33-1.21.42-2,1.65-1.7l20.93,4.84c2,.46,3.65,3.09,2.62,4.16l-4.76,4.94,8.13,7.84c1.11,1.06,1.49,2.48.85,3.14L65,67.24c-.64.67-2.07.34-3.18-.72l-8.13-7.84L48.9,63.61C47.85,64.69,45.17,63.14,44.64,61.15Z" transform="translate(-1.16 -2.2)" style="fill:#0aaded;opacity:0.8999999761581421;isolation:isolate"/></svg>
@@ -0,0 +1,46 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg height="59.253815" id="Andysvg" version="1.0" width="65.414536" x="0.00000000" y="0.00000000" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
3
+ <defs id="defs3">
4
+ <linearGradient id="linearGradient891" x1="92.437965" x2="27.674332" y1="-3.9104078" y2="91.076988" gradientTransform="matrix(0.8184166,0,0,0.530237,-9.8827678,-14.589608)" gradientUnits="userSpaceOnUse">
5
+ <stop id="stop889" offset="0" style="stop-color:#626262;stop-opacity:1.0000000;"/>
6
+ <stop id="stop890" offset="1" style="stop-color:#fff;stop-opacity:1;"/>
7
+ </linearGradient>
8
+ <linearGradient id="linearGradient905" x1="-77.726181" x2="95.64444" y1="208.43991" y2="11.699047" gradientTransform="matrix(0.7314635,0,0,0.5932693,-9.8827694,-14.589616)" gradientUnits="userSpaceOnUse">
9
+ <stop id="stop889" offset="0" style="stop-color:#626262;stop-opacity:1.0000000;"/>
10
+ <stop id="stop890" offset="1" style="stop-color:#fff;stop-opacity:1;"/>
11
+ </linearGradient>
12
+ <radialGradient cx="60.004654" cy="56.485935" id="radialGradient1132" r="68.589226" gradientTransform="matrix(0.8184166,0,0,0.530237,-9.8827678,-14.589608)" gradientUnits="userSpaceOnUse">
13
+ <stop id="stop1134" offset="0" style="stop-color:#8bb7df;stop-opacity:1.0000000;"/>
14
+ <stop id="stop1136" offset="0.7621" style="stop-color:#2a6092;stop-opacity:1.0000000;"/>
15
+ <stop id="stop1135" offset="1" style="stop-color:#375e82;stop-opacity:1.0000000;"/>
16
+ </radialGradient>
17
+ <linearGradient id="linearGradient1140" x1="45.685757" x2="41.96706" y1="110.4447" y2="232.24953" gradientTransform="matrix(1.2743811,0,0,0.3405213,-9.8827694,-14.589616)" gradientUnits="userSpaceOnUse">
18
+ <stop id="stop889" offset="0" style="stop-color:#626262;stop-opacity:1.0000000;"/>
19
+ <stop id="stop890" offset="1" style="stop-color:#fff;stop-opacity:1;"/>
20
+ </linearGradient>
21
+ <linearGradient id="linearGradient1144" x1="31.449743" x2="31.617281" y1="203.49899" y2="251.21892" gradientTransform="matrix(1.4044089,0,0,0.3089952,-9.8827694,-14.589616)" gradientUnits="userSpaceOnUse">
22
+ <stop id="stop893" offset="0" style="stop-color:#ffffff;stop-opacity:0.00000000;"/>
23
+ <stop id="stop894" offset="1" style="stop-color:#fff;stop-opacity:1;"/>
24
+ </linearGradient>
25
+ <linearGradient id="linearGradient1146" x1="39.810947" x2="17.87653" y1="90.197021" y2="113.71949" gradientTransform="matrix(0.8811179,0,0,0.4925045,-9.8827694,-14.589616)" gradientUnits="userSpaceOnUse">
26
+ <stop id="stop893" offset="0" style="stop-color:#ffffff;stop-opacity:0.00000000;"/>
27
+ <stop id="stop894" offset="1" style="stop-color:#fff;stop-opacity:1;"/>
28
+ </linearGradient>
29
+ <linearGradient id="linearGradient1156" x1="25.220816" x2="25.220816" y1="178.48862" y2="234.26866" gradientTransform="matrix(1.6156202,0,0,0.2685999,-9.8827678,-14.589608)" gradientUnits="userSpaceOnUse">
30
+ <stop id="stop889" offset="0" style="stop-color:#626262;stop-opacity:1.0000000;"/>
31
+ <stop id="stop890" offset="1" style="stop-color:#fff;stop-opacity:1;"/>
32
+ </linearGradient>
33
+ <linearGradient id="linearGradient1157" x1="51.46093" x2="-16.224497" y1="269.85831" y2="176.28694" gradientTransform="matrix(1.6156202,0,0,0.2685999,-9.8827678,-14.589608)" gradientUnits="userSpaceOnUse">
34
+ <stop id="stop889" offset="0" style="stop-color:#626262;stop-opacity:1.0000000;"/>
35
+ <stop id="stop890" offset="1" style="stop-color:#fff;stop-opacity:1;"/>
36
+ </linearGradient>
37
+ </defs>
38
+ <rect height="5.4088969" id="rect1155" style="fill: url(#linearGradient1156); fill-opacity: 1; fill-rule: evenodd; stroke: url(#linearGradient1157); stroke-width: 0.953444pt;" width="38.405891" x="12.350429" y="36.575089"/>
39
+ <path d="M 0.19287398,54.885213 C -0.67386281,56.46268 1.5579198,59.253939 3.3933636,59.253939 L 62.069027,59.253939 C 63.628857,59.253939 65.976201,57.281205 65.269522,55.925378 L 58.655165,43.235277 C 58.145696,42.257819 57.005954,41.779036 55.881408,41.779036 L 8.9408812,41.779036 C 8.0135624,41.779036 7.0317541,42.438291 6.5938539,43.235277 L 0.19287398,54.885213 z " id="path1139" style="fill: url(#linearGradient1140); fill-opacity: 1; fill-rule: evenodd; stroke-width: 1.44735pt;"/>
40
+ <rect height="39.110481" id="rect1137" rx="3.6272225" ry="3.536587" style="fill: url(#linearGradient905); fill-opacity: 1; fill-rule: evenodd; stroke-width: 1.62827;" width="48.220726" x="7.4732022" y="-0.010164791"/>
41
+ <rect height="24.74659" id="rect1131" style="fill: url(#radialGradient1132); fill-opacity: 1; fill-rule: evenodd; stroke: url(#linearGradient891); stroke-width: 0.965037pt;" width="38.813015" x="12.390426" y="6.027596"/>
42
+ <path d="M 8.5906358,19.782157 L 8.3772707,34.032521 C 8.3432557,36.304242 9.6712558,37.75887 12.004493,37.777143 L 38.568566,37.985175 L 41.128957,32.160207 L 11.471076,31.952177 L 11.257713,19.678141 L 8.5906358,19.782157 z " id="path1145" style="fill: url(#linearGradient1146); fill-opacity: 1; fill-rule: evenodd; stroke-width: 1pt;"/>
43
+ <path d="M 2.7205883,54.675124 C 2.1113822,55.866786 2.9089235,57.975349 4.9701318,57.975349 L 60.179981,57.975349 C 61.276346,57.975349 62.926231,56.48511 62.429524,55.460899 L 57.78047,45.874547 C 57.422369,45.136152 56.621274,44.774474 55.830865,44.774474 L 8.8693353,44.774468 C 8.2175462,44.774468 7.5274604,45.272486 7.2196723,45.874547 L 2.7205883,54.675124 z " id="path1143" style="fill: url(#linearGradient1144); fill-opacity: 1; fill-rule: evenodd; stroke-width: 1pt;"/>
44
+ <rect x="12.664" y="6.294" width="38.092" height="24.48" style="fill: rgb(255, 255, 255);"/>
45
+ <path d="M 28.718 20.949 L 28.718 18.69 L 27.09 20.318 C 27.266 20.44 27.431 20.541 27.587 20.622 C 27.741 20.704 27.887 20.768 28.023 20.815 C 28.159 20.864 28.284 20.896 28.399 20.918 C 28.516 20.939 28.622 20.948 28.718 20.949 M 37.035 20.762 C 37.038 20.764 37.054 20.778 37.082 20.796 C 37.113 20.815 37.155 20.839 37.212 20.862 C 37.269 20.886 37.339 20.91 37.426 20.927 C 37.512 20.945 37.613 20.956 37.728 20.956 C 37.825 20.956 37.931 20.948 38.048 20.927 C 38.164 20.907 38.292 20.875 38.429 20.826 C 38.566 20.779 38.713 20.716 38.871 20.633 C 39.029 20.551 39.196 20.449 39.374 20.325 L 38.14 19.116 L 34.048 15.009 L 32.253 16.805 L 29.832 14.307 L 23.753 20.317 C 23.931 20.442 24.098 20.542 24.256 20.624 C 24.413 20.707 24.561 20.771 24.697 20.818 C 24.835 20.866 24.961 20.899 25.078 20.919 C 25.195 20.939 25.301 20.948 25.398 20.948 C 25.513 20.948 25.614 20.936 25.7 20.919 C 25.785 20.901 25.856 20.878 25.914 20.855 C 25.97 20.831 26.013 20.806 26.043 20.788 C 26.072 20.769 26.088 20.755 26.091 20.753 L 28.835 18.009 L 29.816 17.068 L 32.534 19.786 L 33.483 20.762 C 33.485 20.764 33.501 20.778 33.53 20.796 C 33.56 20.815 33.602 20.839 33.66 20.862 C 33.716 20.886 33.788 20.91 33.873 20.927 C 33.959 20.945 34.06 20.956 34.176 20.956 C 34.273 20.956 34.379 20.948 34.496 20.927 C 34.613 20.907 34.74 20.875 34.877 20.826 C 35.014 20.779 35.161 20.716 35.319 20.633 C 35.476 20.551 35.644 20.449 35.823 20.325 L 33.642 18.19 L 34.047 17.785 L 35.934 19.672 L 37.035 20.762 M 37.011 9.382 L 42.371 18.668 L 37.011 27.955 L 26.288 27.955 L 20.926 18.668 L 26.288 9.382 L 37.011 9.382 Z" style="fill: rgb(13, 89, 127); fill-opacity: 1; fill-rule: nonzero; stroke: none;" id="path58" bx:origin="0 0"/>
46
+ </svg>
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg id="svg1998" width="51.358818" height="47.630692" version="1.0" xmlns="http://www.w3.org/2000/svg">
3
+ <defs id="defs2001">
4
+ <marker orient="auto" refY="0" refX="0" id="Arrow2Lstart" style="overflow:visible">
5
+ <path id="path11918" style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.97309,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " transform="matrix(1.1,0,0,1.1,1.1,0)"/>
6
+ </marker>
7
+ <marker orient="auto" refY="0" refX="0" id="Arrow1Send" style="overflow:visible">
8
+ <path id="path11921" d="M 0,0 L 5,-5 L -12.5,0 L 5,5 L 0,0 z " style="fill-rule:evenodd;stroke:black;stroke-width:1pt;marker-start:none" transform="matrix(-0.2,0,0,-0.2,-1.2,0)"/>
9
+ </marker>
10
+ <marker orient="auto" refY="0" refX="0" id="Arrow1Lstart" style="overflow:visible">
11
+ <path id="path11936" d="M 0,0 L 5,-5 L -12.5,0 L 5,5 L 0,0 z " style="fill-rule:evenodd;stroke:black;stroke-width:1pt;marker-start:none" transform="matrix(0.8,0,0,0.8,10,0)"/>
12
+ </marker>
13
+ <linearGradient id="linearGradient9199" gradientUnits="userSpaceOnUse" gradientTransform="matrix(24.666332,1.3331909e-2,-1.2883406e-2,23.708747,-8.6563136,-4.6316275)" x1="2.1178279" y1="0.30649999" x2="-0.4954865" y2="0.30649999">
14
+ <stop style="stop-opacity: 1; stop-color: rgb(239, 239, 239);" offset="0" id="stop8333"/>
15
+ <stop style="stop-opacity: 1; stop-color: rgb(255, 255, 255);" offset="1" id="stop8335"/>
16
+ </linearGradient>
17
+ </defs>
18
+ <path id="path11907" d="M 1.8402723,44.565612 L 1.8402723,47.615693 L 44.293781,47.615693 L 44.293781,44.565612 L 1.8402723,44.565612" style="fill:#000000;fill-opacity:0.39215686;stroke:none;stroke-width:0.001"/>
19
+ <path id="path11020" d="M 44.370594,6.1851672 L 51.194316,1.0980117 L 51.358819,41.344844 L 44.266438,47.63069 L 44.370594,6.1851672" style="fill:#000000;fill-opacity:0.39215686;stroke:none;stroke-width:0.001"/>
20
+ <path id="path2037" d="M 0.062500355,5.0439492 L 0.062500355,46.326915 L 44.31814,46.326915 L 44.31814,5.0439492 L 0.062500355,5.0439492" style="fill-opacity: 1; stroke: none; stroke-width: 0.001; fill: rgb(239, 239, 239);"/>
21
+ <path id="path2041" d="M -0.028418806,5.1647552 L 5.5982489,0.11782966 L 50.022311,0.14184042 L 44.346312,5.1887393 L -0.028418806,5.1647552" style="fill:url(#linearGradient9199);fill-opacity:1;stroke:none;stroke-width:0.001"/>
22
+ <path id="path2045" d="M 44.228586,5.0887264 L 49.976169,0.12831483 L 49.976169,41.348635 L 44.228586,46.309047 L 44.228586,5.0887264" style="fill-opacity: 1; stroke: none; stroke-width: 0.001; fill: rgb(223, 223, 223);"/>
23
+ <path class="st0" d="M 40.254 38.435 L 24.509 13.686 C 23.584 12.049 20.925 12.049 19.901 13.789 L 4.254 38.435 L 9.369 38.435 L 14.073 30.969 L 24.099 30.969 L 26.753 26.67 L 16.731 26.67 L 22.255 18.082 L 35.039 38.435 L 40.254 38.435 Z" style="fill: rgb(24, 61, 100);"/>
24
+ </svg>
@@ -0,0 +1,48 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 66 44.52">
3
+ <defs>
4
+ <style>
5
+ .cls-1 {
6
+ fill: none;
7
+ }
8
+
9
+ .cls-2 {
10
+ clip-path: url(#clippath);
11
+ }
12
+
13
+ .cls-3 {
14
+ fill: #fff;
15
+ }
16
+
17
+ .cls-4 {
18
+ fill: #abbbed;
19
+ }
20
+
21
+ .cls-5 {
22
+ fill: #96aae9;
23
+ }
24
+
25
+ .cls-6 {
26
+ fill: #7989bb;
27
+ }
28
+ </style>
29
+ <clipPath id="clippath">
30
+ <rect class="cls-1" x=".1" y="8.18" width="64.89" height="27.34"/>
31
+ </clipPath>
32
+ </defs>
33
+ <rect x="1.34" y="23.21" width="49.53" height="13.54"/>
34
+ <polygon points="50.88 23.21 50.88 36.75 66 23.21 66 9.64 50.88 23.21"/>
35
+ <rect class="cls-1" x=".1" y="8.18" width="64.89" height="27.34"/>
36
+ <rect class="cls-1" x=".1" y="8.18" width="64.89" height="27.34"/>
37
+ <rect class="cls-5" x=".22" y="21.87" width="49.53" height="13.54"/>
38
+ <polygon class="cls-4" points="15.34 8.3 .22 21.87 49.75 21.87 64.87 8.3 15.34 8.3"/>
39
+ <polygon class="cls-6" points="49.75 21.87 49.75 35.4 64.87 21.87 64.87 8.3 49.75 21.87"/>
40
+ <polygon class="cls-3" points="33.17 12.87 25.08 14.56 31.96 17.27 40.04 15.58 33.17 12.87"/>
41
+ <polygon class="cls-3" points="24.77 15.66 28.21 17.02 14.88 19.81 16.6 20.49 9.1 19.98 9.73 17.78 11.45 18.46 24.77 15.66"/>
42
+ <polygon class="cls-3" points="36.9 13.12 40.33 14.48 53.69 11.68 55.41 12.35 56 10.15 48.54 9.64 50.26 10.32 36.9 13.12"/>
43
+ <polygon class="cls-3" points="35.71 17.52 39.75 16.68 44.9 18.71 46.92 18.29 46.33 20.49 38.83 19.98 40.86 19.56 35.71 17.52"/>
44
+ <polygon class="cls-3" points="25.39 13.46 29.44 12.61 24.28 10.58 26.29 10.15 18.83 9.64 18.2 11.85 20.24 11.42 25.39 13.46"/>
45
+ <g class="cls-2">
46
+ <path class="cls-3" d="m25.84,29.93c0-.2-.07-.35-.21-.46-.14-.11-.39-.22-.74-.34s-.64-.23-.86-.34c-.73-.36-1.09-.85-1.09-1.47,0-.31.09-.58.27-.82.18-.24.44-.42.77-.56s.7-.2,1.12-.2.77.07,1.09.21c.32.15.57.35.74.62.18.26.26.57.26.91h-1.35c0-.23-.07-.41-.21-.53-.14-.13-.33-.19-.57-.19s-.43.05-.57.16c-.14.1-.21.24-.21.41,0,.15.08.27.23.39.16.12.42.24.81.36.39.12.71.26.95.4.61.35.91.83.91,1.45,0,.49-.18.88-.56,1.15-.37.28-.88.42-1.52.42-.46,0-.87-.08-1.24-.25s-.65-.39-.83-.67c-.19-.28-.28-.61-.28-.98h1.35c0,.3.08.52.23.67.16.14.41.21.76.21.22,0,.4-.05.53-.15.13-.1.2-.23.2-.41"/>
47
+ </g>
48
+ </svg>