You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

500 lines
17 KiB

7 years ago
  1. #!/bin/bash
  2. # create-arista-veos-image.sh
  3. # HS-Fulda - sebastian.rieger@informatik.hs-fulda.de
  4. #
  5. # changelog:
  6. # V1.1 added injection of config defined in VM Maestro using config-drivex
  7. # V1.1.1 fixed device mapping of extracted partitions, fixed problems with stale swi directory
  8. # V1.1.2 rc.eos now supports e1000 and virtio as vnic types (virtio is supported in vEOS >=4.14.5F)
  9. # V1.2 added dynamic handling of device mapping of extacted partitions
  10. # V1.21 checking whether it safe to unmount working directories
  11. # V1.3 added support to delete existing image with the same name and generating the default nova flavor
  12. # V1.3.1 added support for newer glance releases (e.g. kilo) used in VIRL 1.0.0
  13. # V1.3.2 changed the extension of the bootloader iso to match the size of the partitions to be injected
  14. # V1.4 support for variable VEOS image sizes (as requested by @Jade_Deane to use custom VEOS images)
  15. # V1.4.1 fixed check for existing images
  16. # V1.5 fixed detection of missing loop files for Ubuntu 16.04 in VIRL >=1.3, added default subtype creation
  17. # V1.51 fixed default subtype creation to use ssh management and max 22 ports, changed ram to 2GB as recommended by Arista
  18. # V1.52 fixed default subtype creation to support VIRL version <1.3
  19. # V2.0 added support for new partition layout of vEOS-lab >= 4.21 images, using an integrated bootloader (as the earlier combined versions)
  20. # usage
  21. if [ ! $# -eq 3 ] ; then
  22. echo -e "usage: $0 <Aboot-veos-serial-version.iso> <vEOS-version.vmdk> <new glance image name>, e.g.:\n"
  23. echo "$0 Aboot-veos-serial-8.0.0.iso vEOS-lab-4.16.9M.vmdk vEOS"
  24. exit -1
  25. fi
  26. # sudo check
  27. if [ ! $UID -eq 0 ] ; then
  28. echo "Insufficient privileges. Please consider using sudo -s."
  29. exit -1
  30. fi
  31. ABOOT_SERIAL_ISO=$1
  32. ABOOT_SERIAL_ISO_BASENAME=$(basename -s .iso $1)
  33. VEOS_VMDK=$2
  34. VEOS_VMDK_BASENAME=$(basename -s .vmdk $2)
  35. GLANCE_IMAGE_NAME=$3
  36. GLANCE_IMAGE_RELEASE=$VEOS_VMDK_BASENAME-$ABOOT_SERIAL_ISO_BASENAME
  37. TMP_NAME="vEOS-$GLANCE_IMAGE_RELEASE"
  38. TIMESTAMP=$(date +%Y%m%d%H%M%S)
  39. function wait_for_loop_dev_partitions() {
  40. LOOPDEV_PARTITION_1=$1
  41. LOOPDEV_PARTITION_2=$2
  42. echo -n "waiting for loop devs from kpartx ($LOOPDEV_PARTITION_1 and $LOOPDEV_PARTITION_2)"
  43. LOOP_DEV_RETRIES=0
  44. until dd if=/dev/mapper/$LOOPDEV_PARTITION_1 of=/dev/null bs=1k count=1 &>/dev/null && dd if=/dev/mapper/$LOOPDEV_PARTITION_2 of=/dev/null bs=1k count=1 &>/dev/null ; do
  45. echo -n "."
  46. LOOP_DEV_RETRIES=$(expr $LOOP_DEV_RETRIES + 1)
  47. if [ $LOOP_DEV_RETRIES -eq 10 ]; then
  48. echo
  49. echo "ERROR: timeout waiting for loop devs from kpartx"
  50. exit -1
  51. fi
  52. sleep 1
  53. done
  54. return 0
  55. }
  56. function remove_loop_devs() {
  57. LOOP_DEV_IMAGE=$1
  58. LOOP_DEV_DEL_RETRIES=0
  59. echo "removing loop devs from kpartx ($LOOP_DEV_IMAGE)"
  60. until kpartx -vd $LOOP_DEV_IMAGE ; do
  61. echo -n "."
  62. LOOP_DEV_DEL_RETRIES=$(expr $LOOP_DEV_DEL_RETRIES + 1)
  63. if [ $LOOP_DEV_DEL_RETRIES -eq 10 ]; then
  64. echo
  65. echo "ERROR: timeout waiting for loop dev removal from kpartx"
  66. exit -1
  67. fi
  68. sleep 1
  69. done
  70. echo
  71. return 0
  72. }
  73. function safe_unmount() {
  74. MOUNTPOINT=$1
  75. echo -n "Unmounting $MOUNTPOINT..."
  76. RETRY=0
  77. until umount $MOUNTPOINT &>/dev/null
  78. do
  79. echo -n "."
  80. sleep 1
  81. RETRY=$((RETRY+1))
  82. if [ "$RETRY" -ge "5" ] ; then
  83. echo
  84. echo "ERROR: unable to unmount working directory $MOUNTPOINT"
  85. exit -1
  86. fi
  87. done
  88. echo
  89. return 0
  90. }
  91. function inject_rc_eos() {
  92. echo
  93. echo "Injecting rc.eos startup script to get switch config..."
  94. echo "==========================================================="
  95. # inject rc.eos script to get switch config defined in VM Maestro (config-drive)
  96. INJECTION_TIMESTAMP=$1
  97. VEOS_VMDK_BASENAME_RC_EOS_PART=$2
  98. mkdir swi-$INJECTION_TIMESTAMP
  99. echo "loop mounting image..."
  100. mount -o loop $VEOS_VMDK_BASENAME_RC_EOS_PART swi-$INJECTION_TIMESTAMP
  101. cd swi-$INJECTION_TIMESTAMP
  102. echo "injecting rc.eos..."
  103. cat << EOF > rc.eos
  104. #!/bin/sh
  105. #
  106. # startup script to get node configs from VM Maestro
  107. #
  108. echo "Getting switch config from config drive..."
  109. echo "=========================================="
  110. mkdir /config-drive
  111. # check if config drive is CDROM or DISK, CDROM is required for vEOS >=4.21 combined images
  112. blkid | grep /dev/sr0 | grep CDROM
  113. if [ \$? -eq 0 ]; then
  114. # config_disk_type is cdrom
  115. mount /dev/sr0 /config-drive
  116. else
  117. # config_disk_type is disk
  118. mount /dev/sdb1 /config-drive
  119. fi
  120. echo "Getting ip address for ma1 via dhcp..."
  121. echo "=========================================="
  122. MANAGEMENT_INTERFACE="ma1"
  123. ip link show \$MANAGEMENT_INTERFACE
  124. if [ \$? -ne 0 ]; then
  125. # if using virtio ma1 will not be up during Eos Init 1, hence we use eth0
  126. MANAGEMENT_INTERFACE="eth0"
  127. fi
  128. dhclient -r \$MANAGEMENT_INTERFACE
  129. dhclient -1 -v \$MANAGEMENT_INTERFACE >/mnt/flash/dhclient.log
  130. IP=\$(ip addr show \$MANAGEMENT_INTERFACE | grep inet | tr -s ' ' | cut -d ' ' -f 3 | sed s/"\/"/"\\\\\\\\\/"/g)
  131. echo \$IP
  132. sed s/"! ip of ma1 configured on launch"/"ip address \$IP"/g /config-drive/veos_config.txt >/mnt/flash/startup-config.tmp
  133. cat /mnt/flash/startup-config.tmp
  134. echo
  135. echo "Copying switch config from config drive..."
  136. echo "=========================================="
  137. cp /mnt/flash/startup-config.tmp /mnt/flash/startup-config
  138. EOF
  139. chmod 755 rc.eos
  140. cd ..
  141. echo "unmounting image..."
  142. safe_unmount swi-$INJECTION_TIMESTAMP
  143. rm -rf swi-$INJECTION_TIMESTAMP
  144. return 0
  145. }
  146. # check for an existing image with the same name and offer to delete it prior to creating a new one
  147. CHECK_FOR_EXISTING_IMAGE=$(glance --os-image-api-version 1 image-show $GLANCE_IMAGE_NAME 2>&1)
  148. if [ $? == 0 ] ; then
  149. glance --os-image-api-version 1 image-show $GLANCE_IMAGE_NAME
  150. echo
  151. echo
  152. read -r -p "There is already an image with the same name in glance. Do you want to overwrite it? [y/N] " RESPONSE
  153. if [[ $RESPONSE =~ ^([yY][eE][sS]|[yY])$ ]] ; then
  154. echo "Deleting existing image $GLANCE_IMAGE_NAME..."
  155. echo "==========================================================="
  156. glance --os-image-api-version 1 image-delete $GLANCE_IMAGE_NAME
  157. else
  158. echo "An image with the same name already exists. Either delete this image or choose another name."
  159. exit -1
  160. fi
  161. fi
  162. echo
  163. echo "Detecting partition layout..."
  164. echo "==========================================================="
  165. # convert vmdk to raw
  166. echo "Converting vmdk image to raw..."
  167. qemu-img convert -O raw $2 $VEOS_VMDK_BASENAME.raw
  168. echo
  169. echo "vEOS image partition layout:"
  170. fdisk -l $VEOS_VMDK_BASENAME.raw
  171. PARTITION_LAYOUT=$(fdisk -l $VEOS_VMDK_BASENAME.raw -l -o device,id | tail -2 | tr -s " " | cut -d " " -f 2)
  172. PARTITION_TYPE_PART1=$(echo "$PARTITION_LAYOUT" | sed '1q;d')
  173. PARTITION_TYPE_PART2=$(echo "$PARTITION_LAYOUT" | sed '2q;d')
  174. if [ "$PARTITION_TYPE_PART1" == "c" ] && [ "$PARTITION_TYPE_PART2" == "12" ]; then
  175. PARTITION_LAYOUT_TYPE="old"
  176. CONFIG_DISK_TYPE="disk"
  177. echo
  178. echo "Detected old partition layout. Bootloader partition needs to be injected in vEOS image..."
  179. # create a copy of Aboot bootloader and extend it to vEOS image size
  180. echo "Creating a copy of Aboot bootloader in $TMP_NAME.raw..."
  181. cp $1 $TMP_NAME.raw
  182. LOOPDEV=$(kpartx -av $VEOS_VMDK_BASENAME.raw)
  183. LOOPDEV_PART1=$(echo "$LOOPDEV" | sed '1q;d' | cut -d " " -f 3)
  184. LOOPDEV_PART2=$(echo "$LOOPDEV" | sed '2q;d' | cut -d " " -f 3)
  185. echo "Output from kpartx: $LOOPDEV"
  186. echo "PART1: $LOOPDEV_PART1"
  187. echo "PART2: $LOOPDEV_PART2"
  188. wait_for_loop_dev_partitions $LOOPDEV_PART1 $LOOPDEV_PART2
  189. echo
  190. echo "using dd to extract partitions..."
  191. dd if=/dev/mapper/$LOOPDEV_PART1 of=$VEOS_VMDK_BASENAME-p1.raw
  192. dd if=/dev/mapper/$LOOPDEV_PART2 of=$VEOS_VMDK_BASENAME-p2.raw
  193. remove_loop_devs $VEOS_VMDK_BASENAME.raw
  194. # inject rc.eos file to allow configuration from VM Maestro
  195. inject_rc_eos $TIMESTAMP $VEOS_VMDK_BASENAME-p1.raw
  196. echo
  197. echo "Injecting new partitions from vEOS vmdk in Aboot image..."
  198. echo "==========================================================="
  199. echo "$VEOS_VMDK_BASENAME.raw (original vEOS vmdk layout):"
  200. fdisk -l $VEOS_VMDK_BASENAME.raw
  201. echo "$TMP_NAME.raw (original ABoot bootloader layout):"
  202. fdisk -l $TMP_NAME.raw
  203. # calulate size of the two partitions
  204. PART1_START=$(fdisk -l $VEOS_VMDK_BASENAME.raw | grep "\.raw1" | tr -s " " | cut -d ' ' -f 3)
  205. PART1_END=$(fdisk -l $VEOS_VMDK_BASENAME.raw | grep "\.raw1" | tr -s " " | cut -d ' ' -f 4)
  206. PART1_LENGTH=$(expr $PART1_END - $PART1_START)
  207. echo "raw1 start=$PART1_START,end=$PART1_END,length=$PART1_LENGTH"
  208. PART2_START=$(fdisk -l $VEOS_VMDK_BASENAME.raw | grep "\.raw2" | tr -s " " | cut -d ' ' -f 2)
  209. PART2_END=$(fdisk -l $VEOS_VMDK_BASENAME.raw | grep "\.raw2" | tr -s " " | cut -d ' ' -f 3)
  210. PART2_LENGTH=$(expr $PART2_END - $PART2_START)
  211. echo "raw2 start=$PART2_START,end=$PART2_END,length=$PART2_LENGTH"
  212. # extend the bootloader iso to be able to append the two partitions
  213. EXTENSION_SIZE=$(ls -lk $VEOS_VMDK_BASENAME.raw | tr -s " " | cut -d " " -f 5)
  214. echo "extending image $TMP_NAME.raw to +$EXTENSION_SIZE"
  215. truncate -s +$EXTENSION_SIZE $TMP_NAME.raw
  216. echo "appending partitions from vmdk in the bootloader iso in $TMP_NAME.raw..."
  217. # append the two partitions from vmdk in the bootloader iso
  218. echo -e "n
  219. p
  220. +$PART1_LENGTH
  221. t
  222. 2
  223. c
  224. a
  225. 2
  226. n
  227. p
  228. +$PART2_LENGTH
  229. t
  230. 3
  231. 12
  232. w" | fdisk $TMP_NAME.raw
  233. echo "$TMP_NAME.raw (new combined image layout):"
  234. fdisk -l $TMP_NAME.raw
  235. # copy the partitions from vEOS vmdk to new image
  236. LOOPDEV=$(kpartx -av $TMP_NAME.raw)
  237. LOOPDEV_FIRST_PART=$(echo "$LOOPDEV" | sed '2q;d' | cut -d " " -f 3)
  238. LOOPDEV_SECOND_PART=$(echo "$LOOPDEV" | sed '3q;d' | cut -d " " -f 3)
  239. echo "Output from kpartx: $LOOPDEV"
  240. echo "PART2: $LOOPDEV_FIRST_PART"
  241. echo "PART3: $LOOPDEV_SECOND_PART"
  242. wait_for_loop_dev_partitions $LOOPDEV_FIRST_PART $LOOPDEV_SECOND_PART
  243. echo
  244. echo "copying the partitions from vEOS vmdk to new image..."
  245. dd if=$VEOS_VMDK_BASENAME-p1.raw of=/dev/mapper/$LOOPDEV_FIRST_PART
  246. dd if=$VEOS_VMDK_BASENAME-p2.raw of=/dev/mapper/$LOOPDEV_SECOND_PART
  247. remove_loop_devs $TMP_NAME.raw
  248. rm $VEOS_VMDK_BASENAME-p1.raw
  249. rm $VEOS_VMDK_BASENAME-p2.raw
  250. elif [ "$PARTITION_TYPE_PART1" == "17" ] && [ "$PARTITION_TYPE_PART2" == "83" ]; then
  251. PARTITION_LAYOUT_TYPE="new"
  252. CONFIG_DISK_TYPE="cdrom"
  253. echo
  254. echo "Detected new combined partition layout. Bootloader already integrated in vEOS image ..."
  255. # extract bootloader partition
  256. LOOPDEV=$(kpartx -av $ABOOT_SERIAL_ISO)
  257. LOOPDEV_FIRST_PART=$(echo "$LOOPDEV" | sed '1q;d' | cut -d " " -f 3)
  258. echo "Output from kpartx: $LOOPDEV"
  259. echo "PART1: $LOOPDEV_FIRST_PART"
  260. # waiting for loop parts can be improved, using only one arg, better solution: get rid of kpartx completely and extract parts using dd etc.
  261. wait_for_loop_dev_partitions $LOOPDEV_FIRST_PART $LOOPDEV_FIRST_PART
  262. echo
  263. echo "copying the bootloader partition..."
  264. dd if=/dev/mapper/$LOOPDEV_FIRST_PART of=$ABOOT_SERIAL_ISO_BASENAME-p1.raw
  265. remove_loop_devs $ABOOT_SERIAL_ISO
  266. # create a copy of vEOS image
  267. echo "Creating a copy of vEOS image in $TMP_NAME.raw..."
  268. cp $VEOS_VMDK_BASENAME.raw $TMP_NAME.raw
  269. # modify the content of the partitions of the image
  270. LOOPDEV=$(kpartx -av $TMP_NAME.raw)
  271. LOOPDEV_FIRST_PART=$(echo "$LOOPDEV" | sed '1q;d' | cut -d " " -f 3)
  272. LOOPDEV_SECOND_PART=$(echo "$LOOPDEV" | sed '2q;d' | cut -d " " -f 3)
  273. echo "Output from kpartx: $LOOPDEV"
  274. echo "PART1: $LOOPDEV_FIRST_PART"
  275. echo "PART2: $LOOPDEV_SECOND_PART"
  276. wait_for_loop_dev_partitions $LOOPDEV_FIRST_PART $LOOPDEV_SECOND_PART
  277. # inject rc.eos file to allow configuration from VM Maestro
  278. inject_rc_eos $TIMESTAMP /dev/mapper/$LOOPDEV_SECOND_PART
  279. # next lines currently commented out to use supplied bootloader in image
  280. # boot process can maybe be improved as Aboot is looking for /mnt/flash.conf and fails in new images
  281. #echo
  282. #echo "Overwrite Aboot bootloader in vEOS image..."
  283. #echo "==========================================================="
  284. #dd if=$ABOOT_SERIAL_ISO_BASENAME-p1.raw of=/dev/mapper/$LOOPDEV_FIRST_PART
  285. sleep 1
  286. remove_loop_devs $TMP_NAME.raw
  287. rm $ABOOT_SERIAL_ISO_BASENAME-p1.raw
  288. else
  289. echo
  290. echo "ERROR: Provided vEOS image uses unknown partition layout. Unable to extract and modify partitions."
  291. exit -1
  292. fi
  293. echo
  294. echo "Convert new image to qcow2..."
  295. echo "==========================================================="
  296. # convert raw to qcow2
  297. qemu-img convert -O qcow2 $TMP_NAME.raw $TMP_NAME.qcow2
  298. echo
  299. echo "Cleaning up..."
  300. echo "==========================================================="
  301. #cleanup
  302. rm $TMP_NAME.raw
  303. rm $VEOS_VMDK_BASENAME.raw
  304. echo
  305. echo "Importing image into glance..."
  306. echo "==========================================================="
  307. glance image-create --container-format bare --disk-format qcow2 --visibility public --name $GLANCE_IMAGE_NAME \
  308. --file $TMP_NAME.qcow2 --property hw_disk_bus=ide --property serial=1 \
  309. --property hw_vif_model=e1000 --property hw_cdrom_type=ide --property release="$GLANCE_IMAGE_RELEASE" --property subtype=$GLANCE_IMAGE_NAME --property config_disk_type=$CONFIG_DISK_TYPE
  310. # create default flavor
  311. CHECKING_FOR_EXISTING_FLAVOR=$(nova flavor-show $GLANCE_IMAGE_NAME.tiny 2>&1)
  312. if [ $? == 1 ]; then
  313. echo "Creating default flavor $GLANCE_IMAGE_NAME.tiny..."
  314. echo "==========================================================="
  315. nova flavor-create --is-public true $GLANCE_IMAGE_NAME.tiny auto 1024 0 1
  316. else
  317. echo "Default flavor $GLANCE_IMAGE_NAME.tiny already exists..."
  318. fi
  319. CHECKING_FOR_EXISTING_FLAVOR=$(nova flavor-show $GLANCE_IMAGE_NAME.small 2>&1)
  320. if [ $? == 1 ]; then
  321. echo "Creating default flavor $GLANCE_IMAGE_NAME.small..."
  322. echo "==========================================================="
  323. nova flavor-create --is-public true $GLANCE_IMAGE_NAME.small auto 1536 0 1
  324. else
  325. echo "Default flavor $GLANCE_IMAGE_NAME.small already exists..."
  326. fi
  327. CHECKING_FOR_EXISTING_FLAVOR=$(nova flavor-show $GLANCE_IMAGE_NAME.medium 2>&1)
  328. if [ $? == 1 ]; then
  329. echo "Creating default flavor $GLANCE_IMAGE_NAME.medium..."
  330. echo "==========================================================="
  331. nova flavor-create --is-public true $GLANCE_IMAGE_NAME.medium auto 2048 0 1
  332. else
  333. echo "Default flavor $GLANCE_IMAGE_NAME.medium already exists..."
  334. fi
  335. # create default subtype
  336. cat << EOF > dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-above-1.3
  337. {
  338. "dynamic-subtypes": [
  339. {
  340. "plugin_base": "generic",
  341. "device_type": "switch",
  342. "plugin_desc": "Arista vEOS",
  343. "cli_protocol": "ssh",
  344. "plugin_name": "$GLANCE_IMAGE_NAME",
  345. "cli_serial": 1,
  346. "interface_range": 22,
  347. "gui_visible": true,
  348. "interface_pattern": "Ethernet{0}",
  349. "baseline_image": "$GLANCE_IMAGE_NAME",
  350. "config_disk_type": "$CONFIG_DISK_TYPE",
  351. "hw_ram": 2048,
  352. "hw_vm_extra": "",
  353. "gui_icon": "iosvl2",
  354. "config_file": "/veos_config.txt",
  355. "interface_first": 1,
  356. "deprecated_use": "",
  357. "baseline_flavor": "$GLANCE_IMAGE_NAME.medium",
  358. "interface_management": "Management1"
  359. }
  360. ]
  361. }
  362. EOF
  363. cat << EOF > dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-below-1.3
  364. {
  365. "dynamic-subtypes": [
  366. {
  367. "plugin_base": "generic",
  368. "plugin_desc": "Arista vEOS",
  369. "cli_protocol": "ssh",
  370. "plugin_name": "$GLANCE_IMAGE_NAME",
  371. "cli_serial": 1,
  372. "interface_range": 22,
  373. "gui_visible": true,
  374. "interface_pattern": "Ethernet{0}",
  375. "baseline_image": "$GLANCE_IMAGE_NAME",
  376. "config_disk_type": "$CONFIG_DISK_TYPE",
  377. "hw_ram": 2048,
  378. "hw_vm_extra": "",
  379. "gui_icon": "iosvl2",
  380. "config_file": "/veos_config.txt",
  381. "interface_first": 1,
  382. "deprecated_use": "",
  383. "baseline_flavor": "$GLANCE_IMAGE_NAME.medium",
  384. "interface_management": "Management1"
  385. }
  386. ]
  387. }
  388. EOF
  389. CHECKING_FOR_EXISTING_SUBTYPE=$(virl_uwm_client subtype-info --name $GLANCE_IMAGE_NAME 2>&1)
  390. if [ $? == 255 ]; then
  391. echo "Creating default subtype $GLANCE_IMAGE_NAME..."
  392. echo "==========================================================="
  393. CHECKING_FOR_EXISTING_DEVICE_TYPE=$(virl_uwm_client subtype-info 2>&1 | grep u\'device_type\':)
  394. if [ $? == 1 ]; then
  395. # device_type attribute is not available in VIRL < 1.3, use JSON definition of subtype without this attribute
  396. echo "detected VIRL version < 1.3.0, selecting appropriate subtype to import"
  397. virl_uwm_client subtype-import --dynamic-subtypes @dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-below-1.3
  398. else
  399. echo "detected VIRL version > 1.3.0, selecting appropriate subtype to import"
  400. # device_type attribute is available, use JSON definition of subtype including this attribute
  401. virl_uwm_client subtype-import --dynamic-subtypes @dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-above-1.3
  402. fi
  403. else
  404. echo "Default subtype $GLANCE_IMAGE_NAME already exists..."
  405. fi
  406. rm dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-above-1.3
  407. rm dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-below-1.3
  408. echo
  409. echo "Image creation successful."
  410. echo "==========================================================="
  411. echo
  412. echo "You can import and use the subtype $GLANCE_IMAGE_NAME in VM Maestro..."
  413. #testing:
  414. #
  415. # nova boot --image "Arista vEOS Disk" --flavor m1.small veos --nic net-id=abc7ad47-55fd-4396-8d31-91dd4d41a18a --nic net-id=abc7ad47-55fd-4396-8d31-91dd4d41a18a --nic net-id=abc7ad47-55fd-4396-8d31-91dd4d41a18a --nic net-id=abc7ad47-55fd-4396-8d31-91dd4d41a18a --nic net-id=abc7ad47-55fd-4396-8d31-91dd4d41a18a
  416. #
  417. # using VM Maestro, the image can be chosen as "VM image", e.g., for an IOSv or IOSvL2 node