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.

406 lines
14 KiB

  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. # usage
  20. if [ ! $# -eq 3 ] ; then
  21. echo -e "usage: $0 <Aboot-veos-serial-version.iso> <vEOS-version.vmdk> <new glance image name>, e.g.:\n"
  22. echo "$0 Aboot-veos-serial-8.0.0.iso vEOS-lab-4.16.9M.vmdk vEOS"
  23. exit -1
  24. fi
  25. # sudo check
  26. if [ ! $UID -eq 0 ] ; then
  27. echo "Insufficient privileges. Please consider using sudo -s."
  28. exit -1
  29. fi
  30. ABOOT_SERIAL_ISO=$1
  31. ABOOT_SERIAL_ISO_BASENAME=$(basename -s .iso $1)
  32. VEOS_VMDK=$2
  33. VEOS_VMDK_BASENAME=$(basename -s .vmdk $2)
  34. GLANCE_IMAGE_NAME=$3
  35. GLANCE_IMAGE_RELEASE=$VEOS_VMDK_BASENAME-$ABOOT_SERIAL_ISO_BASENAME
  36. TMP_NAME="vEOS-$GLANCE_IMAGE_RELEASE"
  37. TIMESTAMP=$(date +%Y%m%d%H%M%S)
  38. function safe_unmount() {
  39. echo -n "Unmounting $1..."
  40. RETRY=0
  41. until umount $1 &>/dev/null
  42. do
  43. echo -n "."
  44. sleep 1
  45. RETRY=$((RETRY+1))
  46. if [ "$RETRY" -ge "5" ] ; then
  47. echo
  48. echo "ERROR: unable to unmount working directory $1"
  49. exit 1
  50. fi
  51. done
  52. echo
  53. return 0
  54. }
  55. # check for an existing image with the same name and offer to delete it prior to creating a new one
  56. CHECK_FOR_EXISTING_IMAGE=$(glance --os-image-api-version 1 image-show $GLANCE_IMAGE_NAME 2>&1)
  57. if [ $? == 0 ] ; then
  58. glance --os-image-api-version 1 image-show $GLANCE_IMAGE_NAME
  59. echo
  60. echo
  61. read -r -p "There is already an image with the same name in glance. Do you want to overwrite it? [y/N] " RESPONSE
  62. if [[ $RESPONSE =~ ^([yY][eE][sS]|[yY])$ ]] ; then
  63. echo "Deleting existing image $GLANCE_IMAGE_NAME..."
  64. echo "==========================================================="
  65. glance --os-image-api-version 1 image-delete $GLANCE_IMAGE_NAME
  66. else
  67. echo "An image with the same name already exists. Either delete this image or choose another name."
  68. exit 1
  69. fi
  70. fi
  71. echo
  72. echo "Creating vEOS image..."
  73. echo "==========================================================="
  74. # create a copy of Aboot bootloader and extend it to 3G
  75. echo "Creating a copy of Aboot bootloader in $TMP_NAME.raw..."
  76. cp $1 $TMP_NAME.raw
  77. echo
  78. echo "Extracting partitions from vEOS vmdk..."
  79. echo "==========================================================="
  80. # convert vmdk to raw and extract two partitions in it
  81. echo "Converting vmdk image to raw..."
  82. qemu-img convert -O raw $2 $VEOS_VMDK_BASENAME.raw
  83. LOOPDEV=$(kpartx -av $VEOS_VMDK_BASENAME.raw)
  84. LOOPDEV_PART1=$(echo "$LOOPDEV" | sed '1q;d' | cut -d " " -f 3)
  85. LOOPDEV_PART2=$(echo "$LOOPDEV" | sed '2q;d' | cut -d " " -f 3)
  86. echo "Output from kpartx: $LOOPDEV"
  87. echo "PART1: $LOOPDEV_PART1"
  88. echo "PART2: $LOOPDEV_PART2"
  89. echo -n "waiting for loop devs from kpartx"
  90. LOOP_DEV_RETRIES=0
  91. until dd if=/dev/mapper/$LOOPDEV_PART1 of=/dev/null bs=1k count=1 &>/dev/null && dd if=/dev/mapper/$LOOPDEV_PART2 of=/dev/null bs=1k count=1 &>/dev/null ; do
  92. echo -n "."
  93. LOOP_DEV_RETRIES=$(expr $LOOP_DEV_RETRIES + 1)
  94. if [ $LOOP_DEV_RETRIES -eq 10 ]; then
  95. echo
  96. echo "ERROR: timeout waiting for loop devs from kaprtx"
  97. exit
  98. fi
  99. sleep 1
  100. done
  101. echo
  102. echo "using dd to extract partitions..."
  103. dd if=/dev/mapper/$LOOPDEV_PART1 of=$VEOS_VMDK_BASENAME-p1.raw
  104. dd if=/dev/mapper/$LOOPDEV_PART2 of=$VEOS_VMDK_BASENAME-p2.raw
  105. LOOP_DEV_DEL_RETRIES=0
  106. echo "removing loop devs from kpartx"
  107. until kpartx -vd $VEOS_VMDK_BASENAME.raw ; do
  108. echo -n "."
  109. LOOP_DEV_DEL_RETRIES=$(expr $LOOP_DEV_DEL_RETRIES + 1)
  110. if [ $LOOP_DEV_DEL_RETRIES -eq 10 ]; then
  111. echo
  112. echo "ERROR: timeout waiting for loop dev removal from kaprtx"
  113. exit
  114. fi
  115. sleep 1
  116. done
  117. echo
  118. echo
  119. echo "Injecting rc.eos startup script to get switch config..."
  120. echo "==========================================================="
  121. # inject rc.eos script in first partition of the image, to get switch config defined in VM Maestro (config-drive)
  122. mkdir swi-$TIMESTAMP
  123. echo "loop mounting image..."
  124. mount -o loop $VEOS_VMDK_BASENAME-p1.raw swi-$TIMESTAMP
  125. cd swi-$TIMESTAMP
  126. echo "injecting rc.eos..."
  127. cat << EOF > rc.eos
  128. #!/bin/sh
  129. #
  130. # startup script to get node configs from VM Maestro
  131. #
  132. echo "Getting switch config from config drive..."
  133. echo "=========================================="
  134. mkdir /config-drive
  135. mount /dev/sdb1 /config-drive
  136. echo "Getting ip address for ma1 via dhcp..."
  137. echo "=========================================="
  138. MANAGEMENT_INTERFACE="ma1"
  139. ip link show \$MANAGEMENT_INTERFACE
  140. if [ \$? -ne 0 ]; then
  141. # if using virtio ma1 will not be up during Eos Init 1, hence we use eth0
  142. MANAGEMENT_INTERFACE="eth0"
  143. fi
  144. dhclient -r \$MANAGEMENT_INTERFACE
  145. dhclient -1 -v \$MANAGEMENT_INTERFACE >/mnt/flash/dhclient.log
  146. IP=\$(ip addr show \$MANAGEMENT_INTERFACE | grep inet | tr -s ' ' | cut -d ' ' -f 3 | sed s/"\/"/"\\\\\\\\\/"/g)
  147. echo \$IP
  148. sed s/"! ip of ma1 configured on launch"/"ip address \$IP"/g /config-drive/veos_config.txt >/mnt/flash/startup-config.tmp
  149. cat /mnt/flash/startup-config.tmp
  150. echo
  151. echo "Copying switch config from config drive..."
  152. echo "=========================================="
  153. cp /mnt/flash/startup-config.tmp /mnt/flash/startup-config
  154. EOF
  155. chmod 755 rc.eos
  156. cd ..
  157. echo "unmounting image..."
  158. safe_unmount swi-$TIMESTAMP
  159. rm -rf swi-$TIMESTAMP
  160. echo
  161. echo "Injecting new partitions from vEOS vmdk in Aboot image..."
  162. echo "==========================================================="
  163. echo "$VEOS_VMDK_BASENAME.raw (original vEOS vmdk layout):"
  164. fdisk -l $VEOS_VMDK_BASENAME.raw
  165. echo "$TMP_NAME.raw (original ABoot bootloader layout):"
  166. fdisk -l $TMP_NAME.raw
  167. # calulate size of the two partitions
  168. PART1_START=$(fdisk -l $VEOS_VMDK_BASENAME.raw | grep "\.raw1" | tr -s " " | cut -d ' ' -f 3)
  169. PART1_END=$(fdisk -l $VEOS_VMDK_BASENAME.raw | grep "\.raw1" | tr -s " " | cut -d ' ' -f 4)
  170. PART1_LENGTH=$(expr $PART1_END - $PART1_START)
  171. echo "raw1 start=$PART1_START,end=$PART1_END,length=$PART1_LENGTH"
  172. PART2_START=$(fdisk -l $VEOS_VMDK_BASENAME.raw | grep "\.raw2" | tr -s " " | cut -d ' ' -f 2)
  173. PART2_END=$(fdisk -l $VEOS_VMDK_BASENAME.raw | grep "\.raw2" | tr -s " " | cut -d ' ' -f 3)
  174. PART2_LENGTH=$(expr $PART2_END - $PART2_START)
  175. echo "raw2 start=$PART2_START,end=$PART2_END,length=$PART2_LENGTH"
  176. # extend the bootloader iso to be able to append the two partitions
  177. EXTENSION_SIZE=$(ls -lk $VEOS_VMDK_BASENAME.raw | tr -s " " | cut -d " " -f 5)
  178. echo "extending image $TMP_NAME.raw to +$EXTENSION_SIZE"
  179. truncate -s +$EXTENSION_SIZE $TMP_NAME.raw
  180. echo "appending partitions from vmdk in the bootloader iso in $TMP_NAME.raw..."
  181. # append the two partitions from vmdk in the bootloader iso
  182. echo -e "n
  183. p
  184. +$PART1_LENGTH
  185. t
  186. 2
  187. c
  188. a
  189. 2
  190. n
  191. p
  192. +$PART2_LENGTH
  193. t
  194. 3
  195. 12
  196. w" | fdisk $TMP_NAME.raw
  197. echo "$TMP_NAME.raw (new combined image layout):"
  198. fdisk -l $TMP_NAME.raw
  199. # copy the partitions from vEOS vmdk to new image
  200. LOOPDEV=$(kpartx -av $TMP_NAME.raw)
  201. LOOPDEV_PART2=$(echo "$LOOPDEV" | sed '2q;d' | cut -d " " -f 3)
  202. LOOPDEV_PART3=$(echo "$LOOPDEV" | sed '3q;d' | cut -d " " -f 3)
  203. echo "Output from kpartx: $LOOPDEV"
  204. echo "PART1: $LOOPDEV_PART1"
  205. echo "PART2: $LOOPDEV_PART2"
  206. echo -n "waiting for loop devs from kpartx"
  207. LOOP_DEV_RETRIES=0
  208. until dd if=/dev/mapper/$LOOPDEV_PART2 of=/dev/null bs=1k count=1 &>/dev/null && dd if=/dev/mapper/$LOOPDEV_PART3 of=/dev/null bs=1k count=1 &>/dev/null ; do
  209. echo -n "."
  210. LOOP_DEV_RETRIES=$(expr $LOOP_DEV_RETRIES + 1)
  211. if [ $LOOP_DEV_RETRIES -eq 10 ]; then
  212. echo
  213. echo "ERROR: timeout waiting for loop devs from kaprtx"
  214. exit
  215. fi
  216. sleep 1
  217. done
  218. echo
  219. echo "copying the partitions from vEOS vmdk to new image..."
  220. dd if=$VEOS_VMDK_BASENAME-p1.raw of=/dev/mapper/$LOOPDEV_PART2
  221. dd if=$VEOS_VMDK_BASENAME-p2.raw of=/dev/mapper/$LOOPDEV_PART3
  222. LOOP_DEV_DEL_RETRIES=0
  223. echo "removing loop devs from kpartx"
  224. until kpartx -vd $TMP_NAME.raw ; do
  225. echo -n "."
  226. LOOP_DEV_DEL_RETRIES=$(expr $LOOP_DEV_DEL_RETRIES + 1)
  227. if [ $LOOP_DEV_DEL_RETRIES -eq 10 ]; then
  228. echo
  229. echo "ERROR: timeout waiting for loop dev removal from kaprtx"
  230. exit
  231. fi
  232. sleep 1
  233. done
  234. echo
  235. echo
  236. echo "Convert new image to qcow2..."
  237. echo "==========================================================="
  238. # convert raw to qcow2
  239. qemu-img convert -O qcow2 $TMP_NAME.raw $TMP_NAME.qcow2
  240. echo
  241. echo "Cleaning up..."
  242. echo "==========================================================="
  243. #cleanup
  244. rm $TMP_NAME.raw
  245. rm $VEOS_VMDK_BASENAME-p1.raw
  246. rm $VEOS_VMDK_BASENAME-p2.raw
  247. rm $VEOS_VMDK_BASENAME.raw
  248. echo
  249. echo "Importing image into glance..."
  250. echo "==========================================================="
  251. glance image-create --container-format bare --disk-format qcow2 --visibility public --name $GLANCE_IMAGE_NAME \
  252. --file $TMP_NAME.qcow2 --property hw_disk_bus=ide --property serial=1 \
  253. --property hw_vif_model=e1000 --property hw_cdrom_type=ide --property release="$GLANCE_IMAGE_RELEASE" --property subtype=IOSv --property config_disk_type=disk
  254. # create default flavor
  255. CHECKING_FOR_EXISTING_FLAVOR=$(nova flavor-show $GLANCE_IMAGE_NAME.tiny 2>&1)
  256. if [ $? == 1 ]; then
  257. echo "Creating default flavor $GLANCE_IMAGE_NAME.tiny..."
  258. echo "==========================================================="
  259. nova flavor-create --is-public true $GLANCE_IMAGE_NAME.tiny auto 1024 0 1
  260. else
  261. echo "Default flavor $GLANCE_IMAGE_NAME.tiny already exists..."
  262. fi
  263. CHECKING_FOR_EXISTING_FLAVOR=$(nova flavor-show $GLANCE_IMAGE_NAME.small 2>&1)
  264. if [ $? == 1 ]; then
  265. echo "Creating default flavor $GLANCE_IMAGE_NAME.small..."
  266. echo "==========================================================="
  267. nova flavor-create --is-public true $GLANCE_IMAGE_NAME.small auto 1536 0 1
  268. else
  269. echo "Default flavor $GLANCE_IMAGE_NAME.small already exists..."
  270. fi
  271. CHECKING_FOR_EXISTING_FLAVOR=$(nova flavor-show $GLANCE_IMAGE_NAME.medium 2>&1)
  272. if [ $? == 1 ]; then
  273. echo "Creating default flavor $GLANCE_IMAGE_NAME.medium..."
  274. echo "==========================================================="
  275. nova flavor-create --is-public true $GLANCE_IMAGE_NAME.medium auto 2048 0 1
  276. else
  277. echo "Default flavor $GLANCE_IMAGE_NAME.medium already exists..."
  278. fi
  279. # create deault subtype
  280. cat << EOF > dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-above-1.3
  281. {
  282. "dynamic-subtypes": [
  283. {
  284. "plugin_base": "generic",
  285. "device_type": "switch",
  286. "plugin_desc": "Arista vEOS",
  287. "cli_protocol": "ssh",
  288. "plugin_name": "$GLANCE_IMAGE_NAME",
  289. "cli_serial": 1,
  290. "interface_range": 22,
  291. "gui_visible": true,
  292. "interface_pattern": "Ethernet{0}",
  293. "baseline_image": "$GLANCE_IMAGE_NAME",
  294. "config_disk_type": "disk",
  295. "hw_ram": 2048,
  296. "hw_vm_extra": "",
  297. "gui_icon": "iosvl2",
  298. "config_file": "/veos_config.txt",
  299. "interface_first": 1,
  300. "deprecated_use": "",
  301. "baseline_flavor": "$GLANCE_IMAGE_NAME.medium",
  302. "interface_management": "Management1"
  303. }
  304. ]
  305. }
  306. EOF
  307. cat << EOF > dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-below-1.3
  308. {
  309. "dynamic-subtypes": [
  310. {
  311. "plugin_base": "generic",
  312. "plugin_desc": "Arista vEOS",
  313. "cli_protocol": "ssh",
  314. "plugin_name": "$GLANCE_IMAGE_NAME",
  315. "cli_serial": 1,
  316. "interface_range": 22,
  317. "gui_visible": true,
  318. "interface_pattern": "Ethernet{0}",
  319. "baseline_image": "$GLANCE_IMAGE_NAME",
  320. "config_disk_type": "disk",
  321. "hw_ram": 2048,
  322. "hw_vm_extra": "",
  323. "gui_icon": "iosvl2",
  324. "config_file": "/veos_config.txt",
  325. "interface_first": 1,
  326. "deprecated_use": "",
  327. "baseline_flavor": "$GLANCE_IMAGE_NAME.medium",
  328. "interface_management": "Management1"
  329. }
  330. ]
  331. }
  332. EOF
  333. CHECKING_FOR_EXISTING_SUBTYPE=$(virl_uwm_client subtype-info --name $GLANCE_IMAGE_NAME 2>&1)
  334. if [ $? == 255 ]; then
  335. echo "Creating default subtype $GLANCE_IMAGE_NAME..."
  336. echo "==========================================================="
  337. CHECKING_FOR_EXISTING_DEVICE_TYPE=$(virl_uwm_client subtype-info 2>&1 | grep u\'device_type\':)
  338. if [ $? == 1 ]; then
  339. # device_type attribute is not available in VIRL < 1.3, use JSON definition of subtype without this attribute
  340. echo "detected VIRL version < 1.3, selecting appropriate subtype to import"
  341. virl_uwm_client subtype-import --dynamic-subtypes @dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-below-1.3
  342. else
  343. echo "detected VIRL version > 1.3.0, selecting appropriate subtype to import"
  344. # device_type attribute is available, use JSON definition of subtype including this attribute
  345. virl_uwm_client subtype-import --dynamic-subtypes @dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-above-1.3
  346. fi
  347. else
  348. echo "Default subtype $GLANCE_IMAGE_NAME already exists..."
  349. fi
  350. rm dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-above-1.3
  351. rm dynamic-subtype-$GLANCE_IMAGE_NAME.json.default.virl-below-1.3
  352. echo
  353. echo "Image creation successful."
  354. echo "==========================================================="
  355. echo
  356. echo "You can import and use the subtype $GLANCE_IMAGE_NAME in VM Maestro..."
  357. #testing:
  358. #
  359. # 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
  360. #
  361. # using VM Maestro, the image can be chosen as "VM image", e.g., for an IOSv or IOSvL2 node