diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a63e48b --- /dev/null +++ b/.gitignore @@ -0,0 +1,97 @@ +.idea/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..420ba0f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +language: python +python: '2.7' +branches: + only: + - master + - develop +install: + - pip install pytest coverage codacy-coverage +script: + - printenv + - ls -la + - coverage run -m py.test tests + - coverage xml + - python-codacy-coverage -r coverage.xml +deploy: + provider: pypi + user: devopshq + password: + secure: "KBNBXu5h38LnDbQkjr0+2FqpU27Y0cwNjtG0x44tnsxQQOgnidU5r3khjqe7cpiOAySlU0yOarSH1Py7IJiRK2XpdYgRz6ZdM29nq9uJvdyQ9jNNgWAoXMl2olWRe5I2IcAvTQ8Dc5j8EmWmds2GuPFrSh3HAVjZXv4yGkY4bXsXlp1SHPTzJUbHQBntKQJLBVV6/jgCAJ6drHQLKSIx4zWaxub3OmePPC0/y4R4Qj/O+sfGMTdOnrNnNkzjEhAo3sxHn1lWlwit4Is6m7J5dA5m7G6jNNDqVJ+r+2b+q575NXKbSqurRqkVHiw7BplkD4vcImRlSilfI83cHyKA2j5qprI6jFakkjyu79zOaa7HuDnWBPbdIpT69F0de9iRPtdlivEyfo0Kmtg0VGUaJkb52QMO5SkdU7XpVRKzSIDvyUFwN7igsUoe052yISILpOk3Y5qjZq+Fsx7XgeZmF8tLGTdsHJIHFLwFHrQbozF9SEMEtuRFzpAGxwavmvgRcCAuR7HPfQxywE0a9dZyF4odGg8QwoCNzwVl5qsQFagCJ9RjDngTf3bcHcwaV35tIHxdmXijkdY1+d2vP7hu+b0EnMELSOTiYcjsmgBK3Mn45WzOoq/gCJdZ1AKjRdDylmcDLo7ddKvTpAgeZux/2r3MQ6tMLGH4bHcJcsA9XYc=" + distributions: bdist_wheel + on: + all_branches: true + skip_cleanup: true +after_script: + - echo "Deploy to PyPI finished." +env: + global: + - secure: "laINM+2dOwjZe1pw5pzt1f4L3Pqtd867taoFV+XSlqtxe1onyZje7x1iVbslmNdCTeo62Lidx9z9I3Vwv9dDBkjEfGwyUHHHz+kRwJ4zJbCQ4HzYQ6auIjP7MuyKsl6bysnKAdrjo/OtPX1a6iKKaIOv1RoDD4FQldps5Chl6GLJjbpsQPHn07vj2XvcDQcMQH0AFBxIROAxQir1Ox01LLsC2DxiCxQ4QxosLBjv0Gat1h7bT7bKu74qV+OPo5VNPg/H4/u+fIirx8V6r0HHXsoFEhlyYkKSvAfYljyZREUwI8cuu1Q463CBstHH4IokEDzhZO0hJrgLPYnNJyNhgfc3BEgRAiCVOplWBXjvk5k+Fi8NeogvbS4dNExt6am1sy9iKml2p34aErVgh5RVFYrezAJlUPDTcGSOW6eXUfl62ORDbvXth48lbbzxqOi9iTu6UduImTAKuyK0rnQcRTSfRHKqoo/Ptc+p5tJoAU/NKoaJRmYdC0taXJCLWFZqZl7n/ERaknUFBmmmBunmgQ38t3T1Qt9UMdzYshxNSoonTNJELlcRlRZLrZAl0w0BW1SNkB636yku2s9a0gidi25JK+Hx0Mquax0Y+I0bKhxiwVFI/2bCZQyJxGVRzm/elsQzFmIiUBbB4mcfdJpxeZ/2SdVPiPT0pwqY7oVLOks=" \ No newline at end of file diff --git a/README.md b/README.md index 786ddaa..a250681 100644 --- a/README.md +++ b/README.md @@ -1,295 +1,320 @@ # vspheretools -[![vspheretools code quality](https://api.codacy.com/project/badge/Grade/185f7c8a13c84f88bf8b93280e457ffc)](https://www.codacy.com/app/tim55667757/vspheretools/dashboard) +[![vspheretools build status](https://travis-ci.org/devopshq/vspheretools.svg)](https://travis-ci.org/devopshq/vspheretools) [![vspheretools code quality](https://api.codacy.com/project/badge/Grade/185f7c8a13c84f88bf8b93280e457ffc)](https://www.codacy.com/app/tim55667757/vspheretools/dashboard) [![vspheretools code coverage](https://api.codacy.com/project/badge/Coverage/185f7c8a13c84f88bf8b93280e457ffc)](https://www.codacy.com/app/tim55667757/vspheretools/dashboard) [![vspheretools on PyPI](https://img.shields.io/pypi/v/vspheretools.svg)](https://pypi.python.org/pypi/vspheretools) [![vspheretools license](https://img.shields.io/pypi/l/vspheretools.svg)](https://github.com/devopshq/vspheretools/blob/master/LICENSE) -# How to use vspheretools-metarunners -* Install (or copy) xml metarunners on your TeamCity admin page: https://[teamcity_server]/admin/editProject.html?projectId=_Root&tab=metaRunner (Administration - Root project - Meta-Runners) -* Create new VCS from vspheretools project: https://github.com/devopshq/vspheretools -* Attach new VCS to your TeamCity project and set up checkout rules: -+:.=>%default_devops_tools_path_local%/vspheretools -* Create variable default_devops_tools_path_local in your TeamCity project and set value, e.g. "devops-tools". It is local path for devops-tools repository. +***Russian README: https://github.com/devopshq/vspheretools/blob/master/README_RUS.md*** +***Index:*** +- [Introduction](#Chapter_1) + - [Environment requirements](#Chapter_1_1) + - [Setup](#Chapter_1_2) +- [Console mode](#Chapter_2) + - [Options](#Chapter_2_1) + - [Commands](#Chapter_2_2) + - [Usage examples](#Chapter_2_3) +- [vspheretools and TeamCity metarunners](#Chapter_3) + - [How to add vspheretools-metarunners](#Chapter_3_1) +- [API usage](#Chapter_4) + - [Global variables](#Chapter_4_1) + - [API methods](#Chapter_4_2) +- [Troubleshooting](#Chapter_5) -***Содержание:*** -- [Общие сведения](#Chapter_1) - - [Требования к окружению](#Chapter_1_1) -- [Работа в консоли](#Chapter_2) - - [Опции](#Chapter_2_1) - - [Команды](#Chapter_2_2) - - [Примеры использования](#Chapter_2_3) -- [Работа c vspheretools через метараннеры в TeamCity](#Chapter_3) - - [Как добавить vspheretools-metarunners](#Chapter_3_1) -- [Работа через API](#Chapter_4) - - [Глобальные переменные](#Chapter_4_1) - - [Методы](#Chapter_4_2) -- [Типичные проблемы и способы их устранения](#Chapter_5) +# Introduction +**vSphereTools** - vSphereTools is a set of scripts from DevOpsHQ to support working with vSphere and virtual machines (VMs) on it, which are based on the pysphere library. These scripts were written for the purpose of integrating vSphere with TeamCity through meta-runners. It is also possible to use the vSphereTools tools from the console or by importing the necessary modules and methods into their projects. -# Общие сведения -**vSphereTools** - это набор скриптов от DevOpsHQ для поддержки работы с vSphere и виртуальными машинами (ВМ) на ней, в основе которых лежит библиотека pysphere. Эти скрипты были написаны для целей интеграции vSphere с TeamCity на уровне шагов конфигураций, реализованных через мета-раннеры. Также возможно использование инструментов vSphereTools из консоли или импортируя нужные модули и методы в свои проекты. Для работы vSphereTools из консоли используются пайтоновские скрипты PySphereRoutine.py. - -Как работает инструмент? Смотрите на схеме ниже. +How does it work? Let see the process scheme below. ![vSphereTools work process](vspheretools.png "vSphereTools work process") -## Требования к окружению +## Environment requirements -1. Служебный пользователь для доступа к Сфере, чьи логин и пароль вы будете подставлять в скриптах и метараннерах: желательно с ограниченными правами доступа только до вашего каталога с ВМ. Рекомендуется создавать для доменного аккаунта логин и пароль без спецсимволов и пробелов в имени, допускаются только буквы и цифры. -2. С машины, где выполняется запуск скриптов для пользователя, от имени которого исполняются эти скрипты, обязательно должен быть доступен сервис vcenter - Сфера и нужный кластер, а также ESX на котором работает ВМ. -3. На ВМ должны быть установлены инструмент VMvare Tools (контекстное меню ВМ -> Guest -> Install/Upgrade VMvare Tools). -4. На машине, где выполняется запуск скриптов, установлен Python 2*, версий 2.7 или старше. +1. The service account for access to the vSphere, whose login and password you will substitute in scripts. Only letters and numbers are recommended in login and password. +2. From the machine where you are running scripts vSphere and the ESX must be available. +3. The VMvare Tools tool should be installed on the VM (VM -> Guest -> Install / Upgrade VMvare Tools context menu). +4. On the machine where you are running scripts, Python 2 *, version 2.7 or later is installed. -# Работа в консоли -Необходимо скачать vSphereTools из гит-репозитория, открыть консоль и перейти в каталог vSphereTools. +## Setup -Использование: +1. Download vSphereTools from the git repository: https://github.com/devopshq/vspheretools and unpack it to some directory. +2. Either you can install the tool through pip: - python PySphereRoutine.py [options] [command]` + pip install vspheretools -Допускается указание множества опций и одна команда, которую нужно выполнить на Сфере. +# Console mode -## Опции +Use (if you did not install the tool via pip, but simply downloaded and unpacked): - -s SERVER_NAME, --server SERVER_NAME - Указать кластер на сервере vSphere. Например, vcenter-01.example.com. - - -l USERLOGIN, --login USERLOGIN - Указать логин юзера, у которого есть права на работу с проектом на Сфере. Значение по умолчанию не определено. - - -p USERPASSWORD, --password USERPASSWORD - Указать пароль пользователя. Значение по умолчанию не определено. - - -n VM_NAME, --name VM_NAME - Указать имя ВМ с которой будут производиться действия. Значение по умолчанию не определено. - - -gl GUESTLOGIN, --guest-login GUESTLOGIN - Указать логин юзера для ОС, установленной на ВМ. Значение по умолчанию не определено. - - -gp GUESTPASSWORD, --password GUESTPASSWORD - Указать пароль юзера для ОС, установленной на ВМ. Значение по умолчанию не определено. - - -d CLONE_DIRECTORY, --clone-dir CLONE_DIRECTORY - Указать имя директории на Сфере, в которую юзер, из под которого выполняется работа, имеет право записи. По умолчанию установлено значение директории Clones - она должна быть создана в проекте на Сфере. - - -t TIMEOUT_SEC, --timeout TIMEOUT_SEC - Указать таймаут в секундах для некоторых операций, например, ожидание ip-адреса машины. По умолчанию установлено значение таймаута 300 с. + python -m vspheretools [options] [command] +If the tool is installed via pip, then simply type: -## Команды + vspheretools [options] [command] + +It is possible to specify many options and only one command to be executed on the Sphere. + +In the examples below, we will assume that vspheretools is installed via pip and the keyword python omited. - --status - Получить статус ВМ, заданной по имени ключом --name. Доступные статусы: 'POWERING ON', 'POWERING OFF', 'SUSPENDING', 'RESETTING', 'BLOCKED ON MSG', 'REVERTING TO SNAPSHOT', 'UNKNOWN'. - - --start - Запустить ВМ, заданную по имени ключом --name. - - --start-wait - Запустить ВМ, заданную по имени ключом --name, и дождаться полной загрузки OS. Таймаут задаётся опцией --timeout. - - --stop - Остановить ВМ, заданную по имени ключом --name. - - --snapshots - Отобразить список снапшотов для ВМ, заданной по имени ключом --name. - - --create-snapshot SNAPSHOT_NAME - Создать именованный снапшот для ВМ. - - --revert-to-current-snapshot - Откатить ВМ, заданную по имени ключом --name, на текущий (активный) снапшот. - - --revert-to-snapshot SNAPSHOT_NAME - Откатить ВМ, заданную по имени ключом --name, на снапшот, указанный по имени. - - --properties - Отобразить список доступных свойств ВМ, заданной по имени ключом --name. - - --get-ip - Отобразить ip-адрес ВМ, заданной по имени ключом --name, если он у неё имеется. - - --set-ip-into-teamcity-param TEAMCITY_PARAMETER - Установить параметру в TeamCity с именем TEAMCITY_PARAMETER значение строки с ip-адресом ВМ, заданной по имени ключом --name. По умолчанию, TEAMCITY_PARAMETER = vm_ip. - - --clone CLONE_NAME - Клонировать ВМ, заданную по имени ключом --name, в директорию, определенную ключом --clone-dir. К имени машины добавляется префикс "clone-<уникальное_число>-", если указано имя None (по умолчанию) для параметра CLONE_NAME. У пользователя под которым выполняются действия должны быть права на чтение/запись в этот каталог на Сфере. - - --delete - Удаляет ВМ, заданную по имени ключом --name. Перед удалением проверяется, что ВМ выключена, и если нет, то выполняется попытка её остановки, затем удаление. ВНИМАНИЕ! Будьте осторожны с этой опцией! Пользователь, из под которого выполняется удаление должен иметь крайне ограниченные права на чтение / запись / удаление только из определённых каталогов проектов на Сфере! - - --upload-file SCR DST REWRITE - Копирует локальный файл на ВМ. Пути обоих файлов должны быть указаны полными. REWRITE=True указывается для перезаписи существующего файла. - - --download-file SCR DST REWRITE - Копирует файл из ВМ на указанный локальный путь. Пути обоих файлов должны быть полными. REWRITE=True указывается для перезаписи существующего файла. - - --mkdir DIR_PATH CREATESUBS - Создает на ВМ указанную директорию. Если CREATESUBS=True, то создаются все промежуточные поддиректории. - - --execute PROGRAM ARGS ENV CWD PYTHONBIN WAIT - Запускает на ВМ указанную программу PROGRAM и возвращает её PID после запуска. ARGS - список дополнительных параметров или ключей запуска программы, разделённые запятыми. ENV - переменные окружения вида ИМЯ:ЗНАЧЕНИЕ, разделенные запятыми. CWD - рабочая директория программы. PYTHONBIN - полный путь до бинаря python 2.* на ВМ. WAIT - ожидать или нет окончания работы программы и пробрасывать код выхода и логи процесса. +## Options -## Примеры использования + -s SERVER_NAME, --server SERVER_NAME - Specify a cluster on the vSphere server. For example, vcenter-01.example.com. - Если во входных параметрах используются пробелы, то задавайте их значения в двойных кавычках: "имя ВМ с пробелом" + -l USERLOGIN, --login USERLOGIN - Specify the login of the user who has the rights to work with the project on the Sphere. The default value is undefined. -Получить текущий статус ВМ: + -p USERPASSWORD, --password USERPASSWORD - Specify the user password. The default value is undefined. - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --status + -n VM_NAME, --name VM_NAME - Specify the name of the VM with which to perform the actions. The default value is undefined. -Запустить ВМ: + -gl GUESTLOGIN, --guest-login GUESTLOGIN - Specify the user login for the OS installed on the VM. The default value is undefined. - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --start + -gp GUESTPASSWORD, --password GUESTPASSWORD - Specify the user password for the OS installed on the VM. The default value is undefined. -Запустить ВМ и дожидаться загрузки OS в течение 5 минут: + -d CLONE_DIRECTORY, --clone-dir CLONE_DIRECTORY - Specify the name of the directory in the Sphere to which the user has the write permission. The default value is the "Clones" directory - it must be created on the Sphere. - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --timeout 300 --start-wait + -t TIMEOUT_SEC, --timeout TIMEOUT_SEC - Specify the timeout in seconds for certain operations. The default value is 300s. -Остановить ВМ: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --stop +## Commands + + --status - Get the status of the VM, given by the --name key. The available statuses are: 'POWERING ON', 'POWERING OFF', 'SUSPENDING', 'RESETTING', 'BLOCKED ON MSG', 'REVERTING TO SNAPSHOT', 'UNKNOWN'. + + --start - Start the VM, given by the --name key. + + --start-wait - Start the VM, given by the --name key, and wait until the OS is fully loaded. The timeout is specified by the --timeout key. + + --stop - Stop the VM, given by the --name key. + + --snapshots - Display the list of snapshots for the VM, specified by the --name key. + + --create-snapshot SNAPSHOT_NAME - Create a named snapshot for the VM. + + --revert-to-current-snapshot - Revert VM to the current (active) snapshot. + + --revert-to-snapshot SNAPSHOT_NAME - Revert VM to the named snapshot. + + --properties - Display the list of available VM properties. + + --get-ip - Display the IP address of the VM if it has it. + + --set-ip-into-teamcity-param TEAMCITY_PARAMETER - Write the TEAMCITY_PARAMETER with the ip-address of the VM. By default, TEAMCITY_PARAMETER name is "vm_ip". + + --clone CLONE_NAME - Clone VM to the directory defined by --clone-dir key. + + --delete - Deletes the VM. Before deleting, it is checked that the VM is off, and if not, it is attempted to stop it, then delete it. ATTENTION! Be careful with this option! + + --upload-file SCR DST REWRITE - Copies a local file to the VM. The full paths of both files must be specified. REWRITE = True is specified to overwrite an existing file. + + --download-file SCR DST REWRITE - Copies a file from the VM to the specified local path. The full paths of both files must be specified. REWRITE = True is specified to overwrite an existing file. + + --mkdir DIR_PATH CREATESUBS - Creates the specified directory on the VM. If CREATESUBS = True, then all intermediate subdirectories are created. + + --execute PROGRAM ARGS ENV CWD PYTHONBIN WAIT - Starts the specified PROGRAM on the VM and returns its PID. ARGS - list of additional parameters, separated by commas. ENV - environment variables NAME:VALUE, separated by commas. CWD is the working directory of the program. PYTHONBIN - the full path to the python 2.* on the VM. WAIT or no the end of the program after is starts. + + +## Usage examples + +If spaces are used in the input parameters, then use theirs value with double quotes, e.g. "VM name with space" + +Get the current VM status: + + vspheretools --server vcenter-01.example.com --login --password --name --status + +Start VM: + + vspheretools --server vcenter-01.example.com --login --password --name --start + +Start VM and wait until guest OS loaded with 5 minute timeout: + + vspheretools --server vcenter-01.example.com --login --password --name --timeout 300 --start-wait + +Stop VM: + + vspheretools --server vcenter-01.example.com --login --password --name --stop -Получить список снапшотов: +Get VM snapshots: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --snapshots + vspheretools --server vcenter-01.example.com --login --password --name --snapshots -Создать снапшот: +Create named snapshot of the VM state: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --create-snapshot name="Snapshot name" rewrite=True fail-if-exist=False + vspheretools --server vcenter-01.example.com --login --password --name --create-snapshot name="Snapshot name" rewrite=True fail-if-exist=False -Откатить ВМ на текущий (активный) снапшот: +Revert VM to the current (active) snapshot: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --revert-to-current-snapshot + vspheretools --server vcenter-01.example.com --login --password --name --revert-to-current-snapshot -Откатить ВМ на снапшот по его имени: +Revert VM to the named snapshot: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --revert-to-snapshot + vspheretools --server vcenter-01.example.com --login --password --name --revert-to-snapshot -Получить список свойств ВМ: +Get VM properties: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --properties + vspheretools --server vcenter-01.example.com --login --password --name --properties -Получить текущий ip-адрес ВМ с таймаутом данной операции в 10 секунд: +Get an ip-address of the VM with 10 seconds timeout: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --timeout 10 --get-ip + vspheretools --server vcenter-01.example.com --login --password --name --timeout 10 --get-ip -Установить в TeamCity значение параметра vm_ip равное текущему ip-адресу ВМ, с таймаутом данной операции в 10 секунд: +Get an ip-address of the VM and save it to the TeamCity "vm_ip"-named parameter with 10 seconds timeout: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --timeout 10 --set-ip-into-teamcity-param vm_ip + vspheretools --server vcenter-01.example.com --login --password --name --timeout 10 --set-ip-into-teamcity-param vm_ip -Сделать новый клон ВМ в указанную директорию: +Create VM clone: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --clone-dir Clones --clone new_clone_name + vspheretools --server vcenter-01.example.com --login --password --name --clone-dir Clones --clone new_clone_name -Удалить ВМ. ВНИМАНИЕ! Крайне осторожно используйте эту опцию! У пользователя должны быть ограниченные права на удаление только из конкретных каталогов на Сфере! +Delete VM: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name --delete + vspheretools --server vcenter-01.example.com --login --password --name --delete -Скопировать локальный файл на ВМ с его перезаписью: +Copy local file to VM: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name -gl -gp --upload-file True + vspheretools --server vcenter-01.example.com --login --password --name -gl -gp --upload-file True -Скопировать файл из ВМ и положить по локальному пути без перезаписи (по умолчанию): +Copy file from VM to the local path: - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name -gl -gp --download-file + vspheretools --server vcenter-01.example.com --login --password --name -gl -gp --download-file -Создать директорию и все промежуточные поддиректории (по умолчанию): +Create a directory and all intermediate subdirectories (by default): - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name -gl -gp --mkdir + vspheretools --server vcenter-01.example.com --login --password --name -gl -gp --mkdir -Запустить консоль Windows с параметрами: - - python PySphereRoutine.py --server vcenter-01.example.com --login --password --name -gl -gp --execute program="C:\Windows\System32\cmd.exe" args="/T:Green /C echo %aaa% & echo %bbb%" env="aaa:10, bbb:20" cwd="C:\Windows\System32" pythonbin="c:\python27\python.exe" wait=True - - -# Работа c vspheretools через метараннеры в TeamCity - -Для работы со Сферой в TemCity можно добавить следующие метараннеры: - -* DevOps-runner: vSphereTools - Show VM status - отображает в логе информацию о статусе ВМ. -* DevOps-runner: vSphereTools - Start VM - запускает указанную ВМ. -* DevOps-runner: vSphereTools - Start VM and waiting until guest OS started - запускает указанную ВМ и ожидает загрузки OS в течение указанного времени. -* DevOps-runner: vSphereTools - Stop VM - останавливает указанную ВМ. -* DevOps-runner: vSphereTools - Show VM snapshots - выводит список доступных снапшотов для ВМ. -* DevOps-runner: vSphereTools - Create VM snapshot - создаёт снапшот для ВМ. -* DevOps-runner: vSphereTools - Revert VM to current snapshot - откатывает ВМ на текущий (активный) снапшот. -* DevOps-runner: vSphereTools - Revert VM to named snapshot - откатывает ВМ на указанный снапшот. Нужно задать полное имя снапшота. -* DevOps-runner: vSphereTools - Show VM properties - выводит список свойств ВМ. -* DevOps-runner: vSphereTools - Show VM ip-address - получает и выводит текущий ip-адрес ВМ, если он есть. -* DevOps-runner: vSphereTools - Set VM ip-address into TC parameter - получает текущий ip-адрес ВМ, если он есть, и затем устанавливает его значение в указанную переменную для конфигурации TeamCity. Переменная должна существовать в конфигурации. -* DevOps-runner: vSphereTools - Clone VM into directory - клонирует ВМ в указанный каталог на Сфере, куда должен быть доступ на запись для пользователя Сферы. -* DevOps-runner: vSphereTools - Delete VM - удаляет ВМ со Сферы. ВНИМАНИЕ! Крайне осторожно используйте этот метараннер! У пользователя должны быть ограниченные права на удаление и только из конкретных каталогов на Сфере! Добейтесь это через отдел IT. -* DevOps-runner: vSphereTools - Upload file to VM - копирует локальный файл на указанную ВМ на Сфере, по указанному пути. -* DevOps-runner: vSphereTools - Download file from VM - копирует файл из ВМ на Сфере, по указанному локальному пути. -* DevOps-runner: vSphereTools - Create directory on VM - создает директорию по указанному пути на ВМ и все промежуточные поддиректории. -* DevOps-runner: vSphereTools - Execute command on VM - запускает указанную программу на ВМ с возможностью ожидания её окончания и получения консольных логов и exit-кода. - - -## Как добавить vspheretools-metarunners -* Загрузите xml-метараннеры в вашей админке TeamCity: https://[teamcity_server]/admin/editProject.html?projectId=_Root&tab=metaRunner (Administration - Root project - Meta-Runners) -* Создайте новую VCS в вашем проекте и добавьте в неё vspheretools: https://github.com/devopshq/vspheretools -* Добавьте VCS к вашему TeamCity-проекту и настройте checkout rules: +Start the Windows console with options: + + vspheretools --server vcenter-01.example.com --login --password --name -gl -gp --execute program="C:\Windows\System32\cmd.exe" args="/T:Green /C echo %aaa% & echo %bbb%" env="aaa:10, bbb:20" cwd="C:\Windows\System32" pythonbin="c:\python27\python.exe" wait=True + + +# vspheretools and TeamCity metarunners + +To work with the Sphere in TemCity, you can add the following metarunners: + +* DevOps-runner: vspheretools - Show VM status +* DevOps-runner: vspheretools - Start VM +* DevOps-runner: vspheretools - Start VM and waiting until guest OS started +* DevOps-runner: vspheretools - Stop VM +* DevOps-runner: vspheretools - Show VM snapshots +* DevOps-runner: vspheretools - Create VM snapshot +* DevOps-runner: vspheretools - Revert VM to current snapshot +* DevOps-runner: vspheretools - Revert VM to named snapshot +* DevOps-runner: vspheretools - Show VM properties +* DevOps-runner: vspheretools - Show VM ip-address +* DevOps-runner: vspheretools - Set VM ip-address into TC parameter +* DevOps-runner: vspheretools - Clone VM into directory +* DevOps-runner: vspheretools - Delete VM +* DevOps-runner: vspheretools - Upload file to VM +* DevOps-runner: vspheretools - Download file from VM +* DevOps-runner: vspheretools - Create directory on VM +* DevOps-runner: vspheretools - Execute command on VM + + +## How to add vspheretools-metarunners + +* Install (or copy) xml metarunners from vspheretools-metarunners directory on your TeamCity admin page: https://[teamcity_server]/admin/editProject.html?projectId=_Root&tab=metaRunner (Administration - Root project - Meta-Runners) +* Create new VCS from vspheretools project: https://github.com/devopshq/vspheretools +* Attach new VCS to your TeamCity project and set up checkout rules: +:.=>%default_devops_tools_path_local%/vspheretools -* Создайте переменную default_devops_tools_path_local в вашем TeamCity-проекте и установите значение по умолчанию "devops-tools". Оно используется внутри метараннеров в виде %default_devops_tools_path_local%/vspheretools и указывает на каталог, внутри которого находится подкаталог с выкачанным через VCS репозиторием vspheretools. +* Create variable default_devops_tools_path_local in your TeamCity project and set value, e.g. "devops-tools". It is local path for devops-tools repository. -# Работа через API +# API usage -Нужно импортировать модуль PySphereRoutine.py. При использовании модуля нужно определить основные константы: +Make import of the vspheretools module. When using the module, you need to determine the basic constants. Let's consider the simplest examples: + from vspheretools import VSphereTools as vs -## Глобальные переменные + version = vs.Version() + print(version) - VC_SERVER = r"" # имя кластера сервера Сферы, например, vcenter-01.example.com - - VC_LOGIN = r"" # логин учётки, у которой есть доступ к проектам на Сфере на чтение - - VC_PASSWORD = r"" # пароль учётки - - VM_NAME = r"" # полное имя виртуальной машины, видимое пользователю, с которой предполагается работать - - VM_GUEST_LOGIN = r"" # логин для ОС, установленной на ВМ - - VM_GUEST_PASSWORD = r"" # пароль для ОС, установленной на ВМ - - VM_CLONES_DIR = "Clones" # поддиректория для клонированных vm внутри проекта на Сфере, к которой должен быть доступ на чтение и запись для аккаунта, из под которого идёт работа со Сферой - - OP_TIMEOUT = 300 # таймауты операций + vs.VC_SERVER = r"VSPHERE NAME" + vs.VC_LOGIN = r"VSPHERE USER LOGIN" + vs.VC_PASSWORD = r"PASSWORD" + vs.VM_NAME = r"VIRTUAL MACHINE" -Для работы со сферой нужно инициализировать любую переменную класса Sphere(), который инициализирует подключение к Сфере, нужному кластеру и виртуальной машине, и содержит основные методы. + sphere = vs.Sphere() + sphere.VMStatus() -## Методы +The remaining variables and commands that you can use to work through the API are shown below. - VMStatus() - возвращает строку текущего состояния ВМ заданной по имени VM_NAME - - VMStart() - стартует ВМ (VM_NAME) имеющую статус POWERED OFF - - VMStartWait() - стартует ВМ (VM_NAME) имеющую статус POWERED OFF и дожидается полной загрузки OS на ней, в течение времени указанного в OP_TIMEOUT - - VMStop() - останавливает ВМ (VM_NAME) имеющую статус POWERED ON - - GetVMProperties() - возвращает словарь, содержащий все доступные свойства ВМ (VM_NAME) - - GetVMSnapshotsList() - возвращает лист инстансов снапшотов ВМ (VM_NAME), которые затем можно, например, использовать для поиска и отката на нужный снапшот по его имени. - - CreateVMSnapshot() - создаёт именованный снапшот для ВМ (VM_NAME). - - GetVMIPaddress() - возвращает строку с ip-адресом ВМ (VM_NAME), если он у неё есть. Попытки определить ip-адрес выполняются в течении количества секунд, заданных параметром OP_TIMEOUT - - SetVMIPaddressIntoTeamCityParameter(paramName) - устанавливает в качестве значения параметра TeamCity, указанного через входную переменную paramName, строку с ip-адресом ВМ (VM_NAME), если он имеется, иначе переменная не изменяется - - VMRevertToCurrentSnapshot() - выполняет откат ВМ (VM_NAME) на текущий снапшот - - VMRevertToSnapshot(snapshotName) - выполняет откат ВМ (VM_NAME) на указанный снапшот, заданный своим именем через входную переменную snapshotName - - CloneVM(cloneName) - клонирует ВМ (VM_NAME) в директорию VM_CLONES_DIR - - DeleteVM() - удаляет ВМ (VM_NAME). Перед этим, если требуется, выполняется выключение ВМ. - - CopyFileIntoVM(srcFile, dstFile, overwrite) - копирует локальный файл по полному пути srcFile с агента, где выполняется скрипт, на ВМ по полному пути dstFile. overwrite=True для перезаписи файла, если он существует на ВМ. - - CopyFileFromVM(srcFile, dstFile, overwrite) - копирует файл по полному пути srcFile с указанной ВМ, на локальный путь dstFile. overwrite=True для перезаписи файла, если он существует локально. - - MakeDirectoryOnVM(dirPath, createSubDirs) - создает директорию по указанному пути dirPath и все промежуточные поддиректории, если указано createSubDirs=True. + +## Global variables + + VC_SERVER = r"vcenter-01.example.com" + + VC_LOGIN = r"login" + + VC_PASSWORD = r"password" + + VM_NAME = r"VM name" + + VM_GUEST_LOGIN = r"os_login" + + VM_GUEST_PASSWORD = r"os_password" + + VM_CLONES_DIR = "Clones" + + OP_TIMEOUT = 300 + +To work with the sphere, you need to initialize any variable of the Sphere() class, which initializes the connection to the Sphere, the necessary cluster and the virtual machine, and contains the basic methods. + + +## API methods + + VMStatus() - State of the VM_NAME virtual machine + + VMStart() - Start VM_NAME virtual machine with POWERED OFF status - ExecuteProgramOnVM(\*\*kwargs) - запускает программу с указанными параметрами. Аргументы могут быть следующими: program - путь к программе, например, r"C:\Windows\System32\cmd.exe"; args - аргументы, передаваемые в программу, например, r"/T:Green /C echo %aaa% & echo %bbb%"; env - переменные окружения, например, r"aaa:10, bbb:20"; cwd - путь к рабочей директории программы, например, r"C:\Windows\System32\"; python - полный путь до бинаря python 2.* на ВМ, например, "c:\python27\python.exe"; wait - True, False - ожидать или нет завершения процесса и получать его exit-код. + VMStartWait() - Start VM_NAME virtual machine with POWERED OFF status and wait until OS loaded + + VMStop() - Stop VM_NAME virtual machine with POWERED ON status + + GetVMProperties() - Get dictionary with properties of VM_NAME virtual machine + + GetVMSnapshotsList() - list of virtual machine snapshot instances + + CreateVMSnapshot() - Create named snapshot of the VM_NAME virtual machine + + GetVMIPaddress() - Return an ip-address of VM_NAME virtual machine + + SetVMIPaddressIntoTeamCityParameter(paramName) - Get an ip-address of the VM and save it to the TeamCity "vm_ip"-named parameter + + VMRevertToCurrentSnapshot() - Revert VM_NAME virtual machine to current (active) snapshot + + VMRevertToSnapshot(snapshotName) - Revert VM_NAME virtual machine to snapshotName snapshot + + CloneVM(cloneName) - Cloning VM_NAME virtual machine to the VM_CLONES_DIR directory + + DeleteVM() - Delete VM_NAME virtual machine + + CopyFileIntoVM(srcFile, dstFile, overwrite) - Copy from local srcFile path to dstFile on VM + + CopyFileFromVM(srcFile, dstFile, overwrite) - Copy from VM srcFile to local dstFile path + + MakeDirectoryOnVM(dirPath, createSubDirs) - Create dirPath directory on VM + + ExecuteProgramOnVM(\*\*kwargs) - Run program with some parameters on VM. Arguments: program e.g. r"C:\Windows\System32\cmd.exe"; args e.g. r"/T:Green /C echo %aaa% & echo %bbb%"; env e.g. r"aaa:10, bbb:20"; cwd e.g. r"C:\Windows\System32\" -# Типичные проблемы и способы их устранения +# Troubleshooting -**1. Если вы видите в логах ошибки вида:** +**1. If you see errors in the log:** - PySphereRoutine.py [Line:89] ERROR [2015-08-28 16:48:44,490] Can not connect to vSphere! server = vcenter-01.example.com, + VSphereTools.py [Line:89] ERROR [2015-08-28 16:48:44,490] Can not connect to vSphere! server = vcenter-01.example.com, VIApiException: [InvalidLoginFault]: Cannot complete login due to an incorrect user name or password, pysphere.resources.vi_exception.VIApiException: [GuestOperationsUnavailableFault]: The guest operations agent could not be contacted. -и прочие, связанные с авторизацией, то проверьте, что вы правильно указываете логин и пароль: доменный логин должен иметь вид: domain\login, например, .\administartor - учетка локального админа, пароль и логин не должны содержать спецсимволов или экранироваться кавычками. +and other authorization problems: check that you correctly specify the login and password: the domain login should look like: domain\login, e.g. .\administartor for the local administrator. The password and the login should not contain special characters or be escaped with quotes. **2. ValueError** -Если вы видите в логах ошибку похожую на: +If you see an error in the log similar to: exitCode = sphere.ExecuteProgramOnVM(**dict(kw.split('=') for kw in args.execute)) ValueError: dictionary update sequence element #3 has length 4; 2 is required -то проверьте правильность заполнения поля "Command-line arguments". Запятыми разделяются только отдельные переменные! Длинные строковые значения одной переменной разделять запятой не надо. +then check the "Command-line arguments" field is correct. Separate variables are separated by commas! diff --git a/README_RUS.md b/README_RUS.md new file mode 100644 index 0000000..05a55bb --- /dev/null +++ b/README_RUS.md @@ -0,0 +1,320 @@ +# vspheretools + +[![vspheretools build status](https://travis-ci.org/devopshq/vspheretools.svg)](https://travis-ci.org/devopshq/vspheretools) [![vspheretools code quality](https://api.codacy.com/project/badge/Grade/185f7c8a13c84f88bf8b93280e457ffc)](https://www.codacy.com/app/tim55667757/vspheretools/dashboard) [![vspheretools code coverage](https://api.codacy.com/project/badge/Coverage/185f7c8a13c84f88bf8b93280e457ffc)](https://www.codacy.com/app/tim55667757/vspheretools/dashboard) [![vspheretools on PyPI](https://img.shields.io/pypi/v/vspheretools.svg)](https://pypi.python.org/pypi/vspheretools) [![vspheretools license](https://img.shields.io/pypi/l/vspheretools.svg)](https://github.com/devopshq/vspheretools/blob/master/LICENSE) + + +***Содержание:*** +- [Общие сведения](#Chapter_1) + - [Требования к окружению](#Chapter_1_1) + - [Установка](#Chapter_1_2) +- [Работа в консоли](#Chapter_2) + - [Опции](#Chapter_2_1) + - [Команды](#Chapter_2_2) + - [Примеры использования](#Chapter_2_3) +- [Работа c vspheretools через метараннеры в TeamCity](#Chapter_3) + - [Как добавить vspheretools-metarunners](#Chapter_3_1) +- [Работа через API](#Chapter_4) + - [Глобальные переменные](#Chapter_4_1) + - [Методы](#Chapter_4_2) +- [Типичные проблемы и способы их устранения](#Chapter_5) + + +# Общие сведения +**vSphereTools** - это набор скриптов от DevOpsHQ для поддержки работы с vSphere и виртуальными машинами (ВМ) на ней, в основе которых лежит библиотека pysphere. Эти скрипты были написаны для целей интеграции vSphere с TeamCity на уровне шагов конфигураций, реализованных через мета-раннеры. Также возможно использование инструментов vSphereTools из консоли или импортируя нужные модули и методы в свои проекты. Для работы vSphereTools из консоли используются пайтоновские скрипты VSphereTools.py. + +Как работает инструмент? Смотрите на схеме ниже. + +![vSphereTools work process](vspheretools.png "vSphereTools work process") + + +## Требования к окружению + +1. Служебный пользователь для доступа к Сфере, чьи логин и пароль вы будете подставлять в скриптах и метараннерах: желательно с ограниченными правами доступа только до вашего каталога с ВМ. Рекомендуется создавать для доменного аккаунта логин и пароль без спецсимволов и пробелов в имени, допускаются только буквы и цифры. +2. С машины, где выполняется запуск скриптов для пользователя, от имени которого исполняются эти скрипты, обязательно должен быть доступен сервис vcenter - Сфера и нужный кластер, а также ESX на котором работает ВМ. +3. На ВМ должны быть установлены инструмент VMvare Tools (контекстное меню ВМ -> Guest -> Install/Upgrade VMvare Tools). +4. На машине, где выполняется запуск скриптов, установлен Python 2*, версий 2.7 или старше. + + +## Установка + +1. Необходимо скачать vSphereTools из гит-репозитория https://github.com/devopshq/vspheretools и распаковать в любой каталог. +2. Либо вы можете установить инструмент через pip: + + pip install vspheretools + + +# Работа в консоли + +Использование (если вы не устанавливали инструмент через pip, а просто скачали и распаковали): + + python -m vspheretools [options] [command] + +Либо, если инструмент установлен через pip, то просто наберите в консоли: + + vspheretools [options] [command] + +Допускается указание множества опций и одна команда, которую нужно выполнить на Сфере. + +В примерах далее будем считать, что vspheretools установлен через pip и опускать ключевое слово python. + + +## Опции + + -s SERVER_NAME, --server SERVER_NAME - Указать кластер на сервере vSphere. Например, vcenter-01.example.com. + + -l USERLOGIN, --login USERLOGIN - Указать логин юзера, у которого есть права на работу с проектом на Сфере. Значение по умолчанию не определено. + + -p USERPASSWORD, --password USERPASSWORD - Указать пароль пользователя. Значение по умолчанию не определено. + + -n VM_NAME, --name VM_NAME - Указать имя ВМ с которой будут производиться действия. Значение по умолчанию не определено. + + -gl GUESTLOGIN, --guest-login GUESTLOGIN - Указать логин юзера для ОС, установленной на ВМ. Значение по умолчанию не определено. + + -gp GUESTPASSWORD, --password GUESTPASSWORD - Указать пароль юзера для ОС, установленной на ВМ. Значение по умолчанию не определено. + + -d CLONE_DIRECTORY, --clone-dir CLONE_DIRECTORY - Указать имя директории на Сфере, в которую юзер, из под которого выполняется работа, имеет право записи. По умолчанию установлено значение директории Clones - она должна быть создана в проекте на Сфере. + + -t TIMEOUT_SEC, --timeout TIMEOUT_SEC - Указать таймаут в секундах для некоторых операций, например, ожидание ip-адреса машины. По умолчанию установлено значение таймаута 300 с. + + +## Команды + + --status - Получить статус ВМ, заданной по имени ключом --name. Доступные статусы: 'POWERING ON', 'POWERING OFF', 'SUSPENDING', 'RESETTING', 'BLOCKED ON MSG', 'REVERTING TO SNAPSHOT', 'UNKNOWN'. + + --start - Запустить ВМ, заданную по имени ключом --name. + + --start-wait - Запустить ВМ, заданную по имени ключом --name, и дождаться полной загрузки OS. Таймаут задаётся опцией --timeout. + + --stop - Остановить ВМ, заданную по имени ключом --name. + + --snapshots - Отобразить список снапшотов для ВМ, заданной по имени ключом --name. + + --create-snapshot SNAPSHOT_NAME - Создать именованный снапшот для ВМ. + + --revert-to-current-snapshot - Откатить ВМ, заданную по имени ключом --name, на текущий (активный) снапшот. + + --revert-to-snapshot SNAPSHOT_NAME - Откатить ВМ, заданную по имени ключом --name, на снапшот, указанный по имени. + + --properties - Отобразить список доступных свойств ВМ, заданной по имени ключом --name. + + --get-ip - Отобразить ip-адрес ВМ, заданной по имени ключом --name, если он у неё имеется. + + --set-ip-into-teamcity-param TEAMCITY_PARAMETER - Установить параметру в TeamCity с именем TEAMCITY_PARAMETER значение строки с ip-адресом ВМ, заданной по имени ключом --name. По умолчанию, TEAMCITY_PARAMETER = vm_ip. + + --clone CLONE_NAME - Клонировать ВМ, заданную по имени ключом --name, в директорию, определенную ключом --clone-dir. К имени машины добавляется префикс "clone-<уникальное_число>-", если указано имя None (по умолчанию) для параметра CLONE_NAME. У пользователя под которым выполняются действия должны быть права на чтение/запись в этот каталог на Сфере. + + --delete - Удаляет ВМ, заданную по имени ключом --name. Перед удалением проверяется, что ВМ выключена, и если нет, то выполняется попытка её остановки, затем удаление. ВНИМАНИЕ! Будьте осторожны с этой опцией! Пользователь, из под которого выполняется удаление должен иметь крайне ограниченные права на чтение / запись / удаление только из определённых каталогов проектов на Сфере! + + --upload-file SCR DST REWRITE - Копирует локальный файл на ВМ. Пути обоих файлов должны быть указаны полными. REWRITE=True указывается для перезаписи существующего файла. + + --download-file SCR DST REWRITE - Копирует файл из ВМ на указанный локальный путь. Пути обоих файлов должны быть полными. REWRITE=True указывается для перезаписи существующего файла. + + --mkdir DIR_PATH CREATESUBS - Создает на ВМ указанную директорию. Если CREATESUBS=True, то создаются все промежуточные поддиректории. + + --execute PROGRAM ARGS ENV CWD PYTHONBIN WAIT - Запускает на ВМ указанную программу PROGRAM и возвращает её PID после запуска. ARGS - список дополнительных параметров или ключей запуска программы, разделённые запятыми. ENV - переменные окружения вида ИМЯ:ЗНАЧЕНИЕ, разделенные запятыми. CWD - рабочая директория программы. PYTHONBIN - полный путь до бинаря python 2.* на ВМ. WAIT - ожидать или нет окончания работы программы и пробрасывать код выхода и логи процесса. + + +## Примеры использования + +Если во входных параметрах используются пробелы, то задавайте их значения в двойных кавычках: "имя ВМ с пробелом" + +Получить текущий статус ВМ: + + vspheretools --server vcenter-01.example.com --login --password --name --status + +Запустить ВМ: + + vspheretools --server vcenter-01.example.com --login --password --name --start + +Запустить ВМ и дожидаться загрузки OS в течение 5 минут: + + vspheretools --server vcenter-01.example.com --login --password --name --timeout 300 --start-wait + +Остановить ВМ: + + vspheretools --server vcenter-01.example.com --login --password --name --stop + +Получить список снапшотов: + + vspheretools --server vcenter-01.example.com --login --password --name --snapshots + +Создать снапшот: + + vspheretools --server vcenter-01.example.com --login --password --name --create-snapshot name="Snapshot name" rewrite=True fail-if-exist=False + +Откатить ВМ на текущий (активный) снапшот: + + vspheretools --server vcenter-01.example.com --login --password --name --revert-to-current-snapshot + +Откатить ВМ на снапшот по его имени: + + vspheretools --server vcenter-01.example.com --login --password --name --revert-to-snapshot + +Получить список свойств ВМ: + + vspheretools --server vcenter-01.example.com --login --password --name --properties + +Получить текущий ip-адрес ВМ с таймаутом данной операции в 10 секунд: + + vspheretools --server vcenter-01.example.com --login --password --name --timeout 10 --get-ip + +Установить в TeamCity значение параметра vm_ip равное текущему ip-адресу ВМ, с таймаутом данной операции в 10 секунд: + + vspheretools --server vcenter-01.example.com --login --password --name --timeout 10 --set-ip-into-teamcity-param vm_ip + +Сделать новый клон ВМ в указанную директорию: + + vspheretools --server vcenter-01.example.com --login --password --name --clone-dir Clones --clone new_clone_name + +Удалить ВМ. ВНИМАНИЕ! Крайне осторожно используйте эту опцию! У пользователя должны быть ограниченные права на удаление только из конкретных каталогов на Сфере! + + vspheretools --server vcenter-01.example.com --login --password --name --delete + +Скопировать локальный файл на ВМ с его перезаписью: + + vspheretools --server vcenter-01.example.com --login --password --name -gl -gp --upload-file True + +Скопировать файл из ВМ и положить по локальному пути без перезаписи (по умолчанию): + + vspheretools --server vcenter-01.example.com --login --password --name -gl -gp --download-file + +Создать директорию и все промежуточные поддиректории (по умолчанию): + + vspheretools --server vcenter-01.example.com --login --password --name -gl -gp --mkdir + +Запустить консоль Windows с параметрами: + + vspheretools --server vcenter-01.example.com --login --password --name -gl -gp --execute program="C:\Windows\System32\cmd.exe" args="/T:Green /C echo %aaa% & echo %bbb%" env="aaa:10, bbb:20" cwd="C:\Windows\System32" pythonbin="c:\python27\python.exe" wait=True + + +# Работа c vspheretools через метараннеры в TeamCity + +Для работы со Сферой в TemCity можно добавить следующие метараннеры: + +* DevOps-runner: vspheretools - Show VM status - отображает в логе информацию о статусе ВМ. +* DevOps-runner: vspheretools - Start VM - запускает указанную ВМ. +* DevOps-runner: vspheretools - Start VM and waiting until guest OS started - запускает указанную ВМ и ожидает загрузки OS в течение указанного времени. +* DevOps-runner: vspheretools - Stop VM - останавливает указанную ВМ. +* DevOps-runner: vspheretools - Show VM snapshots - выводит список доступных снапшотов для ВМ. +* DevOps-runner: vspheretools - Create VM snapshot - создаёт снапшот для ВМ. +* DevOps-runner: vspheretools - Revert VM to current snapshot - откатывает ВМ на текущий (активный) снапшот. +* DevOps-runner: vspheretools - Revert VM to named snapshot - откатывает ВМ на указанный снапшот. Нужно задать полное имя снапшота. +* DevOps-runner: vspheretools - Show VM properties - выводит список свойств ВМ. +* DevOps-runner: vspheretools - Show VM ip-address - получает и выводит текущий ip-адрес ВМ, если он есть. +* DevOps-runner: vspheretools - Set VM ip-address into TC parameter - получает текущий ip-адрес ВМ, если он есть, и затем устанавливает его значение в указанную переменную для конфигурации TeamCity. Переменная должна существовать в конфигурации. +* DevOps-runner: vspheretools - Clone VM into directory - клонирует ВМ в указанный каталог на Сфере, куда должен быть доступ на запись для пользователя Сферы. +* DevOps-runner: vspheretools - Delete VM - удаляет ВМ со Сферы. ВНИМАНИЕ! Крайне осторожно используйте этот метараннер! У пользователя должны быть ограниченные права на удаление и только из конкретных каталогов на Сфере! Добейтесь это через отдел IT. +* DevOps-runner: vspheretools - Upload file to VM - копирует локальный файл на указанную ВМ на Сфере, по указанному пути. +* DevOps-runner: vspheretools - Download file from VM - копирует файл из ВМ на Сфере, по указанному локальному пути. +* DevOps-runner: vspheretools - Create directory on VM - создает директорию по указанному пути на ВМ и все промежуточные поддиректории. +* DevOps-runner: vspheretools - Execute command on VM - запускает указанную программу на ВМ с возможностью ожидания её окончания и получения консольных логов и exit-кода. + + +## Как добавить vspheretools-metarunners + +* Загрузите xml-метараннеры из каталога vspheretools-metarunners в админке TeamCity: https://[teamcity_server]/admin/editProject.html?projectId=_Root&tab=metaRunner (Administration - Root project - Meta-Runners) +* Создайте новую VCS в вашем проекте и добавьте в неё vspheretools: https://github.com/devopshq/vspheretools +* Добавьте VCS к вашему TeamCity-проекту и настройте checkout rules: ++:.=>%default_devops_tools_path_local%/vspheretools +* Создайте переменную default_devops_tools_path_local в вашем TeamCity-проекте и установите значение по умолчанию "devops-tools". Оно используется внутри метараннеров в виде %default_devops_tools_path_local%/vspheretools и указывает на каталог, внутри которого находится подкаталог с выкачанным через VCS репозиторием vspheretools. + + +# Работа через API + +Нужно импортировать модуль vspheretools. При использовании модуля нужно определить основные константы. Рассмотрим простейшие примеры: + + from vspheretools import VSphereTools as vs + + version = vs.Version() # получить строку с версией vspheretools + print(version) # вывести версию + + # Задаём параметры подключения, инициализируем соединение и передаём команду: + + vs.VC_SERVER = r"VSPHERE NAME" # имя сервера vSphere + vs.VC_LOGIN = r"VSPHERE USER LOGIN" # логин учётки на Сфере + vs.VC_PASSWORD = r"PASSWORD" # пароль учётки + vs.VM_NAME = r"VIRTUAL MACHINE" # полное имя виртуальной машины + + sphere = vs.Sphere() # создаём экземпляр класса для работы со Сферой и инициализируем подключение + + sphere.VMStatus() # для примера получаем статус виртуальной машины + +Остальные переменные и команды, которые можно использовать для работы через API, приведены ниже. + + +## Глобальные переменные + + VC_SERVER = r"" # имя кластера сервера Сферы, например, vcenter-01.example.com + + VC_LOGIN = r"" # логин учётки, у которой есть доступ к проектам на Сфере на чтение + + VC_PASSWORD = r"" # пароль учётки + + VM_NAME = r"" # полное имя виртуальной машины, видимое пользователю, с которой предполагается работать + + VM_GUEST_LOGIN = r"" # логин для ОС, установленной на ВМ + + VM_GUEST_PASSWORD = r"" # пароль для ОС, установленной на ВМ + + VM_CLONES_DIR = "Clones" # поддиректория для клонированных vm внутри проекта на Сфере, к которой должен быть доступ на чтение и запись для аккаунта, из под которого идёт работа со Сферой + + OP_TIMEOUT = 300 # таймауты операций + +Для работы со сферой нужно инициализировать любую переменную класса Sphere(), который инициализирует подключение к Сфере, нужному кластеру и виртуальной машине, и содержит основные методы. + + +## Методы + + VMStatus() - возвращает строку текущего состояния ВМ заданной по имени VM_NAME + + VMStart() - стартует ВМ (VM_NAME) имеющую статус POWERED OFF + + VMStartWait() - стартует ВМ (VM_NAME) имеющую статус POWERED OFF и дожидается полной загрузки OS на ней, в течение времени указанного в OP_TIMEOUT + + VMStop() - останавливает ВМ (VM_NAME) имеющую статус POWERED ON + + GetVMProperties() - возвращает словарь, содержащий все доступные свойства ВМ (VM_NAME) + + GetVMSnapshotsList() - возвращает лист инстансов снапшотов ВМ (VM_NAME), которые затем можно, например, использовать для поиска и отката на нужный снапшот по его имени. + + CreateVMSnapshot() - создаёт именованный снапшот для ВМ (VM_NAME). + + GetVMIPaddress() - возвращает строку с ip-адресом ВМ (VM_NAME), если он у неё есть. Попытки определить ip-адрес выполняются в течении количества секунд, заданных параметром OP_TIMEOUT + + SetVMIPaddressIntoTeamCityParameter(paramName) - устанавливает в качестве значения параметра TeamCity, указанного через входную переменную paramName, строку с ip-адресом ВМ (VM_NAME), если он имеется, иначе переменная не изменяется + + VMRevertToCurrentSnapshot() - выполняет откат ВМ (VM_NAME) на текущий снапшот + + VMRevertToSnapshot(snapshotName) - выполняет откат ВМ (VM_NAME) на указанный снапшот, заданный своим именем через входную переменную snapshotName + + CloneVM(cloneName) - клонирует ВМ (VM_NAME) в директорию VM_CLONES_DIR + + DeleteVM() - удаляет ВМ (VM_NAME). Перед этим, если требуется, выполняется выключение ВМ. + + CopyFileIntoVM(srcFile, dstFile, overwrite) - копирует локальный файл по полному пути srcFile с агента, где выполняется скрипт, на ВМ по полному пути dstFile. overwrite=True для перезаписи файла, если он существует на ВМ. + + CopyFileFromVM(srcFile, dstFile, overwrite) - копирует файл по полному пути srcFile с указанной ВМ, на локальный путь dstFile. overwrite=True для перезаписи файла, если он существует локально. + + MakeDirectoryOnVM(dirPath, createSubDirs) - создает директорию по указанному пути dirPath и все промежуточные поддиректории, если указано createSubDirs=True. + + ExecuteProgramOnVM(\*\*kwargs) - запускает программу с указанными параметрами. Аргументы могут быть следующими: program - путь к программе, например, r"C:\Windows\System32\cmd.exe"; args - аргументы, передаваемые в программу, например, r"/T:Green /C echo %aaa% & echo %bbb%"; env - переменные окружения, например, r"aaa:10, bbb:20"; cwd - путь к рабочей директории программы, например, r"C:\Windows\System32\"; python - полный путь до бинаря python 2.* на ВМ, например, "c:\python27\python.exe"; wait - True, False - ожидать или нет завершения процесса и получать его exit-код. + + +# Типичные проблемы и способы их устранения + +**1. Если вы видите в логах ошибки вида:** + + VSphereTools.py [Line:89] ERROR [2015-08-28 16:48:44,490] Can not connect to vSphere! server = vcenter-01.example.com, + VIApiException: [InvalidLoginFault]: Cannot complete login due to an incorrect user name or password, + pysphere.resources.vi_exception.VIApiException: [GuestOperationsUnavailableFault]: The guest operations agent could not be contacted. + +и прочие, связанные с авторизацией, то проверьте, что вы правильно указываете логин и пароль: доменный логин должен иметь вид: domain\login, например, .\administartor - учетка локального админа, пароль и логин не должны содержать спецсимволов или экранироваться кавычками. + +**2. ValueError** + +Если вы видите в логах ошибку похожую на: + + exitCode = sphere.ExecuteProgramOnVM(**dict(kw.split('=') for kw in args.execute)) + ValueError: dictionary update sequence element #3 has length 4; 2 is required + +то проверьте правильность заполнения поля "Command-line arguments". Запятыми разделяются только отдельные переменные! Длинные строковые значения одной переменной разделять запятой не надо. diff --git a/pysphere/resources/vi_exception.py b/pysphere/resources/vi_exception.py index 3601438..ea8f31b 100644 --- a/pysphere/resources/vi_exception.py +++ b/pysphere/resources/vi_exception.py @@ -1,4 +1,3 @@ -#-- # Copyright (c) 2012, Sebastian Tello # All rights reserved. @@ -24,13 +23,13 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -#-- + class VIException(Exception): def __init__(self, msg, fault): self.message = str(msg) self.fault = str(fault) + def __str__(self): return "[%s]: %s" % (self.fault, self.message) @@ -39,23 +38,28 @@ class VIApiException(VIException): def __init__(self, e): try: message = e.fault.args[1] - except: - message = str(e) + + except Exception as eX: + message = str(eX) + try: fault = e.fault.detail[0].typecode.pname + except: fault = 'Undefined' super(self.__class__, self).__init__(message, fault) + class UnsupportedPerfIntervalError(VIException): pass + class FaultTypes: - PARAMETER_ERROR = 'Parameter Error' - OBJECT_NOT_FOUND = 'Object Not Found' - NOT_CONNECTED = 'Not Connected' - TIME_OUT = 'Operation Timed Out' - TASK_ERROR = 'Task Error' - NOT_SUPPORTED = 'Operation Not Supported' - INVALID_OPERATION = 'Invalid Operation' \ No newline at end of file + PARAMETER_ERROR = 'Parameter Error' + OBJECT_NOT_FOUND = 'Object Not Found' + NOT_CONNECTED = 'Not Connected' + TIME_OUT = 'Operation Timed Out' + TASK_ERROR = 'Task Error' + NOT_SUPPORTED = 'Operation Not Supported' + INVALID_OPERATION = 'Invalid Operation' diff --git a/pysphere/vi_managed_entity.py b/pysphere/vi_managed_entity.py index 535290b..b6d0bb7 100644 --- a/pysphere/vi_managed_entity.py +++ b/pysphere/vi_managed_entity.py @@ -1,4 +1,3 @@ -#-- # Copyright (c) 2012, Sebastian Tello # All rights reserved. @@ -24,13 +23,12 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -#-- + from pysphere.resources import VimService_services as VI from pysphere.vi_task import VITask -from pysphere.resources.vi_exception import VIException, VIApiException, \ - FaultTypes +from pysphere.resources.vi_exception import VIException, VIApiException, FaultTypes + class VIManagedEntity(object): @@ -69,7 +67,7 @@ def rename(self, new_name, sync_run=True): return vi_task - except (VI.ZSI.FaultException), e: + except VI.ZSI.FaultException as e: raise VIApiException(e) def reload(self): @@ -90,7 +88,8 @@ def reload(self): _this.set_attribute_type(self._mor.get_attribute_type()) request.set_element__this(_this) self._server._proxy.Reload(request) - except (VI.ZSI.FaultException), e: + + except VI.ZSI.FaultException as e: raise VIApiException(e) def destroy(self, sync_run=True): @@ -106,7 +105,6 @@ def destroy(self, sync_run=True): _this = request.new__this(self._mor) _this.set_attribute_type(self._mor.get_attribute_type()) request.set_element__this(_this) - task = self._server._proxy.Destroy_Task(request)._returnval vi_task = VITask(task, self._server) @@ -119,5 +117,6 @@ def destroy(self, sync_run=True): return return vi_task - except (VI.ZSI.FaultException), e: - raise VIApiException(e) \ No newline at end of file + + except VI.ZSI.FaultException as e: + raise VIApiException(e) diff --git a/pysphere/vi_server.py b/pysphere/vi_server.py index 63211ab..4d7f687 100644 --- a/pysphere/vi_server.py +++ b/pysphere/vi_server.py @@ -1,4 +1,3 @@ -#-- # Copyright (c) 2012, Sebastian Tello # All rights reserved. @@ -24,8 +23,7 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -#-- + import sys @@ -38,6 +36,7 @@ from pysphere.vi_mor import VIMor, MORTypes from pysphere.vi_task import VITask + class VIServer: def __init__(self): @@ -63,11 +62,10 @@ def connect(self, host, user, password, trace_file=None, sock_timeout=None): timeout for sockets, in python 2.5 you'll have to use socket.setdefaulttimeout(secs) to change the global setting. """ - self.__user = user self.__password = password - #Generate server's URL - if not isinstance(host, basestring): + # Generate server's URL + if not isinstance(host, str): raise VIException("'host' should be a string with the ESX/VC url." ,FaultTypes.PARAMETER_ERROR) @@ -93,21 +91,19 @@ def connect(self, host, user, password, trace_file=None, sock_timeout=None): for header, value in self.__initial_headers.iteritems(): self._proxy.binding.AddHeader(header, value) - #get service content from service instance + # get service content from service instance request = VI.RetrieveServiceContentRequestMsg() mor_service_instance = request.new__this('ServiceInstance') mor_service_instance.set_attribute_type(MORTypes.ServiceInstance) request.set_element__this(mor_service_instance) - self._do_service_content = self._proxy.RetrieveServiceContent( - request)._returnval + self._do_service_content = self._proxy.RetrieveServiceContent(request)._returnval self.__server_type = self._do_service_content.About.Name self.__api_version = self._do_service_content.About.ApiVersion self.__api_type = self._do_service_content.About.ApiType - #login + # login request = VI.LoginRequestMsg() - mor_session_manager = request.new__this( - self._do_service_content.SessionManager) + mor_session_manager = request.new__this(self._do_service_content.SessionManager) mor_session_manager.set_attribute_type(MORTypes.SessionManager) request.set_element__this(mor_session_manager) request.set_element_userName(user) @@ -115,7 +111,7 @@ def connect(self, host, user, password, trace_file=None, sock_timeout=None): self.__session = self._proxy.Login(request)._returnval self.__logged = True - except (VI.ZSI.FaultException), e: + except VI.ZSI.FaultException as e: raise VIApiException(e) def keep_session_alive(self): @@ -149,7 +145,8 @@ def disconnect(self): mor_session_manager.set_attribute_type(MORTypes.SessionManager) request.set_element__this(mor_session_manager) self._proxy.Logout(request) - except (VI.ZSI.FaultException), e: + + except VI.ZSI.FaultException as e: raise VIApiException(e) def get_performance_manager(self): @@ -179,7 +176,6 @@ def get_hosts(self, from_mor=None): """ return self._get_managed_objects(MORTypes.HostSystem, from_mor) - def get_datastores(self, from_mor=None): """ Returns a dictionary of the existing datastores. Keys are @@ -227,7 +223,7 @@ def get_path(nodes, node): from_node=from_mor, obj_type=MORTypes.ResourcePool) for oc in prop: - this_rp = {} + this_rp = dict() this_rp["children"] = [] this_rp["mor"] = oc.get_element_obj() mor_str = str(this_rp["mor"]) @@ -285,7 +281,8 @@ def get_vm_by_path(self, path, datacenter=None): else: if vm: return VIVirtualMachine(self, vm) - except (VI.ZSI.FaultException), e: + + except VI.ZSI.FaultException as e: raise VIApiException(e) raise VIException("Could not find a VM with path '%s'" % path, @@ -317,7 +314,8 @@ def get_vm_by_name(self, name, datacenter=None): for k,v in vms.iteritems(): if v == name: return VIVirtualMachine(self, k) - except (VI.ZSI.FaultException), e: + + except VI.ZSI.FaultException as e: raise VIApiException(e) raise VIException("Could not find a VM named '%s'" % name, @@ -369,7 +367,7 @@ def get_registered_vms(self, datacenter=None, cluster=None, if not 'config.files.vmPathName' in property_filter: property_filter.insert(0, 'config.files.vmPathName') - #Root MOR filters + # Root MOR filters ret = [] nodes = [None] if resource_pool and VIMor.is_mor(resource_pool): @@ -419,7 +417,7 @@ def get_registered_vms(self, datacenter=None, cluster=None, ret.append(ppath) return ret - except (VI.ZSI.FaultException), e: + except VI.ZSI.FaultException as e: raise VIApiException(e) def acquire_clone_ticket(self): @@ -440,7 +438,8 @@ def acquire_clone_ticket(self): _this.set_attribute_type(MORTypes.SessionManager) request.set_element__this(_this) return self._proxy.AcquireCloneTicket(request)._returnval - except (VI.ZSI.FaultException), e: + + except VI.ZSI.FaultException as e: raise VIApiException(e) def _get_object_properties(self, mor, property_names=[], get_all=False): @@ -486,7 +485,7 @@ def _get_object_properties(self, mor, property_names=[], get_all=False): if ret and isinstance(ret, list): return ret[0] - except (VI.ZSI.FaultException), e: + except VI.ZSI.FaultException as e: raise VIApiException(e) def _get_object_properties_bulk(self, mor_list, properties): @@ -545,12 +544,11 @@ def _get_object_properties_bulk(self, mor_list, properties): return request_call(request) - except (VI.ZSI.FaultException), e: + except VI.ZSI.FaultException as e: raise VIApiException(e) - def _retrieve_properties_traversal(self, property_names=[], - from_node=None, obj_type='ManagedEntity'): + from_node=None, obj_type='ManagedEntity'): """Uses VI API's property collector to retrieve the properties defined in @property_names of Managed Objects of type @obj_type ('ManagedEntity' by default). Starts the search from the managed object reference @@ -569,9 +567,7 @@ def _retrieve_properties_traversal(self, property_names=[], request, request_call = self._retrieve_property_request() - - _this = request.new__this( - self._do_service_content.PropertyCollector) + _this = request.new__this(self._do_service_content.PropertyCollector) _this.set_attribute_type(MORTypes.PropertyCollector) request.set_element__this(_this) @@ -590,7 +586,7 @@ def _retrieve_properties_traversal(self, property_names=[], do_ObjectSpec_objSet.set_element_obj(mor_obj) do_ObjectSpec_objSet.set_element_skip(False) - #Recurse through all ResourcePools + # Recurse through all ResourcePools rp_to_rp = VI.ns0.TraversalSpec_Def('rpToRp').pyclass() rp_to_rp.set_element_name('rpToRp') rp_to_rp.set_element_type(MORTypes.ResourcePool) @@ -609,7 +605,7 @@ def _retrieve_properties_traversal(self, property_names=[], rp_to_rp.set_element_selectSet(spec_array_resource_pool) - #Traversal through resource pool branch + # Traversal through resource pool branch cr_to_rp = VI.ns0.TraversalSpec_Def('crToRp').pyclass() cr_to_rp.set_element_name('crToRp') cr_to_rp.set_element_type(MORTypes.ComputeResource) @@ -621,14 +617,14 @@ def _retrieve_properties_traversal(self, property_names=[], spec_array_computer_resource[1].set_element_name('rpToVm'); cr_to_rp.set_element_selectSet(spec_array_computer_resource) - #Traversal through host branch + # Traversal through host branch cr_to_h = VI.ns0.TraversalSpec_Def('crToH').pyclass() cr_to_h.set_element_name('crToH') cr_to_h.set_element_type(MORTypes.ComputeResource) cr_to_h.set_element_path('host') cr_to_h.set_element_skip(False) - #Traversal through hostFolder branch + # Traversal through hostFolder branch dc_to_hf = VI.ns0.TraversalSpec_Def('dcToHf').pyclass() dc_to_hf.set_element_name('dcToHf') dc_to_hf.set_element_type(MORTypes.Datacenter) @@ -638,7 +634,7 @@ def _retrieve_properties_traversal(self, property_names=[], spec_array_datacenter_host[0].set_element_name('visitFolders') dc_to_hf.set_element_selectSet(spec_array_datacenter_host) - #Traversal through vmFolder branch + # Traversal through vmFolder branch dc_to_vmf = VI.ns0.TraversalSpec_Def('dcToVmf').pyclass() dc_to_vmf.set_element_name('dcToVmf') dc_to_vmf.set_element_type(MORTypes.Datacenter) @@ -648,7 +644,7 @@ def _retrieve_properties_traversal(self, property_names=[], spec_array_datacenter_vm[0].set_element_name('visitFolders') dc_to_vmf.set_element_selectSet(spec_array_datacenter_vm) - #Traversal through datastore branch + # Traversal through datastore branch dc_to_ds = VI.ns0.TraversalSpec_Def('dcToDs').pyclass() dc_to_ds.set_element_name('dcToDs') dc_to_ds.set_element_type(MORTypes.Datacenter) @@ -658,7 +654,7 @@ def _retrieve_properties_traversal(self, property_names=[], spec_array_datacenter_ds[0].set_element_name('visitFolders') dc_to_ds.set_element_selectSet(spec_array_datacenter_ds) - #Recurse through all hosts + # Recurse through all hosts h_to_vm = VI.ns0.TraversalSpec_Def('hToVm').pyclass() h_to_vm.set_element_name('hToVm') h_to_vm.set_element_type(MORTypes.HostSystem) @@ -668,7 +664,7 @@ def _retrieve_properties_traversal(self, property_names=[], spec_array_host_vm[0].set_element_name('visitFolders') h_to_vm.set_element_selectSet(spec_array_host_vm) - #Recurse through all datastores + # Recurse through all datastores ds_to_vm = VI.ns0.TraversalSpec_Def('dsToVm').pyclass() ds_to_vm.set_element_name('dsToVm') ds_to_vm.set_element_type(MORTypes.Datastore) @@ -678,7 +674,7 @@ def _retrieve_properties_traversal(self, property_names=[], spec_array_datastore_vm[0].set_element_name('visitFolders') ds_to_vm.set_element_selectSet(spec_array_datastore_vm) - #Recurse through the folders + # Recurse through the folders visit_folders = VI.ns0.TraversalSpec_Def('visitFolders').pyclass() visit_folders.set_element_name('visitFolders') visit_folders.set_element_type(MORTypes.Folder) @@ -704,7 +700,7 @@ def _retrieve_properties_traversal(self, property_names=[], spec_array_visit_folders[8].set_element_name('rpToVm') visit_folders.set_element_selectSet(spec_array_visit_folders) - #Add all of them here + # Add all of them here spec_array = [visit_folders, dc_to_vmf, dc_to_ds, dc_to_hf, cr_to_h, cr_to_rp, rp_to_rp, h_to_vm, ds_to_vm, rp_to_vm] @@ -717,10 +713,9 @@ def _retrieve_properties_traversal(self, property_names=[], return request_call(request) - except (VI.ZSI.FaultException), e: + except VI.ZSI.FaultException as e: raise VIApiException(e) - def _retrieve_property_request(self): """Returns a base request object an call request method pointer for either RetrieveProperties or RetrievePropertiesEx depending on @@ -750,11 +745,11 @@ def call_retrieve_properties_ex(request): return ret if self.__api_version >= "4.1": - #RetrieveProperties is deprecated (but supported) in sdk 4.1. - #skd 4.1 adds RetrievePropertiesEx with an extra 'options' arg + # RetrieveProperties is deprecated (but supported) in sdk 4.1. + # skd 4.1 adds RetrievePropertiesEx with an extra 'options' arg request = VI.RetrievePropertiesExRequestMsg() - #set options + # set options options = request.new_options() request.set_element_options(options) call_pointer = call_retrieve_properties_ex @@ -769,7 +764,7 @@ def _set_header(self, name, value): """Sets a HTTP header to be sent with the SOAP requests. E.g. for impersonation of a particular client. Both name and value should be strings.""" - if not (isinstance(name, basestring) and isinstance(value, basestring)): + if not (isinstance(name, str) and isinstance(value, str)): return if not self.__logged: @@ -794,43 +789,9 @@ def _get_managed_objects(self, mo_type, from_mor=None): if not content: return {} try: return dict([(o.Obj, o.PropSet[0].Val) for o in content]) - except VI.ZSI.FaultException, e: - raise VIApiException(e) - - #---- DEPRECATED METHODS ----# - - def _get_clusters(self, from_cache=True): - """DEPRECATED: use get_clusters instead.""" - import warnings - from exceptions import DeprecationWarning - warnings.warn("method '_get_clusters' is DEPRECATED use "\ - "'get_clusters' instead", - DeprecationWarning) - - ret = self.get_clusters() - return dict([(v,k) for k,v in ret.iteritems()]) - - def _get_datacenters(self, from_cache=True): - """DEPRECATED: use get_datacenters instead.""" - import warnings - from exceptions import DeprecationWarning - warnings.warn("method '_get_datacenters' is DEPRECATED use "\ - "'get_datacenters' instead", - DeprecationWarning) - - ret = self.get_datacenters() - return dict([(v,k) for k,v in ret.iteritems()]) - - def _get_resource_pools(self, from_cache=True): - """DEPRECATED: use get_resource_pools instead.""" - import warnings - from exceptions import DeprecationWarning - warnings.warn("method '_get_resource_pools' is DEPRECATED use "\ - "'get_resource_pools' instead", - DeprecationWarning) - ret = self.get_resource_pools() - return dict([(v,k) for k,v in ret.iteritems()]) + except VI.ZSI.FaultException as e: + raise VIApiException(e) def delete_vm_by_path(self, path, remove_files=True): """ @@ -838,19 +799,17 @@ def delete_vm_by_path(self, path, remove_files=True): @path is the path to VM. @remove_files - if True (default) will delete VM files from datastore. """ - #TODO: there is an issue with wait_for_state for UnregisterVM statusLine = '' success = False if not self.__logged: - raise VIException("Must call 'connect' before invoking this method", - FaultTypes.NOT_CONNECTED) + raise VIException("Must call 'connect' before invoking this method", FaultTypes.NOT_CONNECTED) try: - #Get VM + # Get VM vm = self.get_vm_by_path(path) if remove_files: - #Invoke Destroy_Task + # Invoke Destroy_Task request = VI.Destroy_TaskRequestMsg() _this = request.new__this(vm._mor) @@ -859,7 +818,7 @@ def delete_vm_by_path(self, path, remove_files=True): ret = self._proxy.Destroy_Task(request)._returnval task = VITask(ret, self) - #Wait for the task to finish + # Wait for the task to finish status = task.wait_for_state([task.STATE_SUCCESS, task.STATE_ERROR]) if status == task.STATE_SUCCESS: @@ -871,7 +830,7 @@ def delete_vm_by_path(self, path, remove_files=True): success = False elif not remove_files: - #Invoke UnregisterVMRequestMsg + # Invoke UnregisterVMRequestMsg request = VI.UnregisterVMRequestMsg() _this = request.new__this(vm._mor) @@ -883,7 +842,7 @@ def delete_vm_by_path(self, path, remove_files=True): statusLine = "VM successfully unregistered (files still on datastore)" success = True - except (VI.ZSI.FaultException), e: + except VI.ZSI.FaultException as e: raise VIApiException(e) finally: @@ -895,19 +854,17 @@ def delete_vm_by_name(self, name, remove_files=True): @name is the VM name. @remove_files - if True (default) will delete VM files from datastore. """ - #TODO: there is an issue with wait_for_state for UnregisterVM statusLine = '' success = False if not self.__logged: - raise VIException("Must call 'connect' before invoking this method", - FaultTypes.NOT_CONNECTED) + raise VIException("Must call 'connect' before invoking this method", FaultTypes.NOT_CONNECTED) try: - #Get VM + # Get VM vm = self.get_vm_by_name(name) if remove_files: - #Invoke Destroy_Task + # Invoke Destroy_Task request = VI.Destroy_TaskRequestMsg() _this = request.new__this(vm._mor) @@ -916,7 +873,7 @@ def delete_vm_by_name(self, name, remove_files=True): ret = self._proxy.Destroy_Task(request)._returnval task = VITask(ret, self) - #Wait for the task to finish + # Wait for the task to finish status = task.wait_for_state([task.STATE_SUCCESS, task.STATE_ERROR]) if status == task.STATE_SUCCESS: @@ -928,7 +885,7 @@ def delete_vm_by_name(self, name, remove_files=True): success = False else: - #Invoke UnregisterVMRequestMsg + # Invoke UnregisterVMRequestMsg request = VI.UnregisterVMRequestMsg() _this = request.new__this(vm._mor) @@ -940,8 +897,8 @@ def delete_vm_by_name(self, name, remove_files=True): statusLine = "VM successfully unregistered (files still on datastore)" success = True - except (VI.ZSI.FaultException), e: + except VI.ZSI.FaultException as e: raise VIApiException(e) finally: - return success, statusLine \ No newline at end of file + return success, statusLine diff --git a/pysphere/vi_task.py b/pysphere/vi_task.py index ec95664..62bcd6c 100644 --- a/pysphere/vi_task.py +++ b/pysphere/vi_task.py @@ -1,4 +1,3 @@ -#-- # Copyright (c) 2012, Sebastian Tello # All rights reserved. @@ -24,8 +23,7 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -#-- + import time @@ -33,12 +31,13 @@ from pysphere.vi_property import VIProperty from pysphere.resources import VimService_services as VI + class VITask: - STATE_ERROR = 'error' - STATE_QUEUED = 'queued' - STATE_RUNNING = 'running' - STATE_SUCCESS = 'success' + STATE_ERROR = 'error' + STATE_QUEUED = 'queued' + STATE_RUNNING = 'running' + STATE_SUCCESS = 'success' def __init__(self, mor, server): self._mor = mor @@ -80,12 +79,11 @@ def wait_for_state(self, states, check_interval=2, timeout=-1): def get_error_message(self): """If the task finished with error, returns the related message""" self.__poll_task_info() - if hasattr(self.info, "error") and hasattr(self.info.error, - "localizedMessage"): + if hasattr(self.info, "error") and hasattr(self.info.error, "localizedMessage"): return self.info.error.localizedMessage def get_result(self): - "Returns the task result (if any) if it has successfully finished" + """Returns the task result (if any) if it has successfully finished""" self.__poll_task_info() if hasattr(self.info, "result"): return self.info.result @@ -107,7 +105,7 @@ def cancel(self): self._server._proxy.CancelTask(request) - except (VI.ZSI.FaultException), e: + except VI.ZSI.FaultException as e: raise VIApiException(e) def __poll_task_info(self, retries=3, interval=2): @@ -115,7 +113,9 @@ def __poll_task_info(self, retries=3, interval=2): try: self.info = VIProperty(self._server, self._mor).info return True - except Exception, e: - if i == retries -1: + + except Exception as e: + if i == retries - 1: raise e - time.sleep(interval) \ No newline at end of file + + time.sleep(interval) diff --git a/script.py b/script.py deleted file mode 100644 index 251f08d..0000000 --- a/script.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Author: Timur Gilmullin, tim55667757@gmail.com - - -# This is script wrapper using for run user-command on VM side. - - -import os -import sys -import subprocess -import traceback - -returnCode = 0 -output = [] - -if os.path.exists(r'C:\temp\vm_process.log'): - os.remove(r'C:\temp\vm_process.log') - -try: - proc = subprocess.Popen(args=['C:\\temp\\MPXSiemSetup.exe', '/silent /norestart /log c:\\log\\siem\\log.txt'], shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=None, cwd=r'c:\temp') - proc.wait() - - for line in proc.stdout: - line = bytes(line).decode(encoding='utf-8', errors='ignore').strip() - - lines = line.split('\r') - for item in lines: - item = item.strip() - if item: - output.append(' ' + item) - - returnCode = proc.returncode - -except: - output += traceback.format_exc().splitlines() - output.append('Unknown error occured!') - returnCode = -1 - -finally: - with open(r'C:\temp\vm_process.log', 'w') as fH: - for item in output: - fH.write(item + '\n') - - sys.exit(returnCode) \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..b88034e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f511491 --- /dev/null +++ b/setup.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +from setuptools import setup +import os + +__version__ = '1.0' # identify main version of vspheretools +devStatus = '4 - Beta' # default build status, see: https://pypi.python.org/pypi?%3Aaction=list_classifiers + +if 'TRAVIS_BUILD_NUMBER' in os.environ and 'TRAVIS_BRANCH' in os.environ: + print("This is TRAVIS-CI build") + print("TRAVIS_BUILD_NUMBER = {}".format(os.environ['TRAVIS_BUILD_NUMBER'])) + print("TRAVIS_BRANCH = {}".format(os.environ['TRAVIS_BRANCH'])) + + __version__ += '.{}{}'.format( + '' if 'release' in os.environ['TRAVIS_BRANCH'] or os.environ['TRAVIS_BRANCH'] == 'master' else 'dev', + os.environ['TRAVIS_BUILD_NUMBER'], + ) + + devStatus = '5 - Production/Stable' if 'release' in os.environ['TRAVIS_BRANCH'] or os.environ['TRAVIS_BRANCH'] == 'master' else devStatus + +else: + print("This is local build") + __version__ += '.dev0' # set version as major.minor.localbuild if local build: python setup.py install + +print("vspheretools build version = {}".format(__version__)) + +setup( + name='vspheretools', + + version=__version__, + + description='vSphereTools is a set of scripts from DevOpsHQ to support working with vSphere and virtual machines (VMs) on it, which are based on the pysphere library.', + + long_description='You can see detailed user manual here: https://devopshq.github.io/vspheretools/', + + license='MIT', + + author='Timur Gilmullin', + + author_email='tim55667757@gmail.com', + + url='https://devopshq.github.io/vspheretools/', + + download_url='https://github.com/devopshq/vspheretools.git', + + entry_points={'console_scripts': ['vspheretools = vspheretools.VSphereTools:Main']}, + + classifiers=[ + 'Development Status :: {}'.format(devStatus), + 'Environment :: Console', + 'Intended Audience :: System Administrators', + 'Topic :: Utilities', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Programming Language :: Python :: 2.7', + ], + + keywords=[ + 'vsphere', + 'sphere client', + 'client', + 'classificator', + 'utility', + 'virtual', + 'VM', + 'virtualization', + 'routines', + ], + + packages=[ + 'vspheretools', + 'pysphere', + 'pysphere.resources', + 'pysphere.ZSI', + 'pysphere.ZSI.generate', + 'pysphere.ZSI.wstools', + + ], + + setup_requires=[ + ], + + tests_require=[ + 'pytest', + ], + + install_requires=[ + ], + + package_data={ + '': [ + './vspheretools-metarunners/*' + './tests/*' + + 'LICENSE', + 'README.md', + 'README_RUS.md', + ], + }, + + zip_safe=True, +) diff --git a/__init__.py b/tests/__init__.py similarity index 100% rename from __init__.py rename to tests/__init__.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..11a887b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +import pysphere + + +def pytest_sessionstart(session): + + class VMInstanceWrapper(object): + + def __init__(self, status='POWERED OFF'): + self.status = status + + def get_status(self, *args, **kwargs): + return self.status + + def power_on(self, *args, **kwargs): + return 'TEST POWER ON' + + def power_off(self, *args, **kwargs): + return 'TEST POWER OFF' + + def wait_for_tools(self, *args, **kwargs): + return 'Waiting until OS started...' + + def get_properties(self, *args, **kwargs): + return {'ip_address': '0.0.0.0', 'test': 123, 'testSub': {'subName': {'subSubName': 'qqq'}}} + + def get_current_snapshot_name(self, *args, **kwargs): + return '' + + def get_snapshots(self, *args, **kwargs): + return ['current snapshot', 'another snapshot'] + + def revert_to_snapshot(self, *args, **kwargs): + return 'reverting to current snapshot...' + + def revert_to_named_snapshot(self, *args, **kwargs): + return 'reverting to named snapshot...' + + def delete_named_snapshot(self, *args, **kwargs): + return 'deleting named snapshot...' + + def create_snapshot(self, *args, **kwargs): + return 'creating new snapshot...' + + def clone(self, *args, **kwargs): + return 'cloning vm...' + + def login_in_guest(self, *args, **kwargs): + return 'login in guest' + + def send_file(self, *args, **kwargs): + return 'sending file...' + + def get_file(self, *args, **kwargs): + return 'geting file...' + + def make_directory(self, *args, **kwargs): + return 'making directory...' + + class VIServerWrapper(object): + + def connect(self, *args, **kwargs): + return 'CONNECTED' + + def get_vm_by_name(self, *args, **kwargs): + if 'FAKE' in args: + raise Exception('No Name found for CloneVM test') + + else: + return VMInstanceWrapper() + + def delete_vm_by_name(self, *args, **kwargs): + return True, 'DELETED' + + pysphere.VIServer = VIServerWrapper diff --git a/tests/test_vspheretools.py b/tests/test_vspheretools.py new file mode 100644 index 0000000..8862652 --- /dev/null +++ b/tests/test_vspheretools.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- + +import pytest +from vspheretools import VSphereTools + + +class TestVSphereTools(): + + @pytest.fixture(scope='class', autouse=True) + def init(self): + VSphereTools.LOGGER.setLevel(50) # Disable debug logging while test + + def test_Version(self): + assert isinstance(VSphereTools.Version(True), str), 'Input: [ True ] expected output: [ isinstance(True, str) == True]' + assert isinstance(VSphereTools.Version(False), str), 'Input: [ False ] expected output: [ isinstance(False, str) == True]' + + def test_Constants(self): + assert VSphereTools.VC_SERVER == "" # empty is default + assert VSphereTools.VC_LOGIN == "" # empty is default + assert VSphereTools.VC_PASSWORD == "" # empty is default + assert VSphereTools.VM_NAME == "" # empty is default + assert VSphereTools.VM_GUEST_LOGIN == "" # empty is default + assert VSphereTools.VM_GUEST_PASSWORD == "" # empty is default + assert VSphereTools.VM_CLONES_DIR == "Clones" # "Clones" is default + assert VSphereTools.OP_TIMEOUT == 300 # 300 is default + + def test_init(self): + sphere = VSphereTools.Sphere() + assert sphere.vSphereServerInstance.connect() == 'CONNECTED' + assert isinstance(sphere, VSphereTools.Sphere) + + def test_VMStatus(self): + sphere = VSphereTools.Sphere() + assert sphere.VMStatus() == 'POWERED OFF' + + def test_VMStart(self): + sphere = VSphereTools.Sphere() + assert sphere.VMStart() == 'POWERED OFF' + + def test_VMStartWait(self): + sphere = VSphereTools.Sphere() + assert sphere.VMStart() == 'POWERED OFF' + + def test_VMStop(self): + sphere = VSphereTools.Sphere() + # TODO: How to check this method if status == 'POWERED ON' + assert sphere.VMStop() == 'POWERED OFF' + + def test_GetVMProperties(self): + sphere = VSphereTools.Sphere() + assert sphere.GetVMProperties() == {'ip_address': '0.0.0.0', 'test': 123, 'testSub': {'subName': {'subSubName': 'qqq'}}} + + def test_GetVMSnapshotsList(self): + sphere = VSphereTools.Sphere() + assert isinstance(sphere.GetVMSnapshotsList(), list) + assert sphere.GetVMSnapshotsList() == ['current snapshot', 'another snapshot'] + + def test_CreateVMSnapshot(self): + sphere = VSphereTools.Sphere() + # TODO: check statusCode == 0 and statusCode == 2. How to prevent snap.get_name() call? + assert sphere.CreateVMSnapshot(name=None) == 1 # Fail because snapshot name not define + assert sphere.CreateVMSnapshot(name='FAKE') == -1 # Fail because an error occured while creating snapshots + + def test_GetVMIPaddress(self): + sphere = VSphereTools.Sphere() + assert sphere.GetVMIPaddress() == '0.0.0.0' + + def test_SetVMIPaddressIntoTeamCityParameter(self): + sphere = VSphereTools.Sphere() + assert sphere.SetVMIPaddressIntoTeamCityParameter(paramName=None) is None # return None by default + + def test_VMRevertToCurrentSnapshot(self): + sphere = VSphereTools.Sphere() + assert sphere.VMRevertToCurrentSnapshot() == 0 # Success operation + + def test_VMRevertToSnapshot(self): + sphere = VSphereTools.Sphere() + # TODO: How to raise exception for check statusCode == -2 + assert sphere.VMRevertToSnapshot(snapshotName=None) == 0 # Success operation if current snapshot + assert sphere.VMRevertToSnapshot(snapshotName='FAKE') == 0 # Success operation if named snapshot + + def test_CloneVM(self): + sphere = VSphereTools.Sphere() + # TODO: How to check with 'POWERED ON' status? + assert sphere.CloneVM(cloneName='FAKE') is None # return None by default + + def test_DeleteVM(self): + sphere = VSphereTools.Sphere() + assert sphere.DeleteVM() is None # Success delete operation return None by default + + def test_CopyFileIntoVM(self): + sphere = VSphereTools.Sphere() + # TODO: How to check this method if status == 'POWERED ON'? + with pytest.raises(Exception): + sphere.CopyFileIntoVM(srcFile=None, dstFile=None, overwrite=False) + with pytest.raises(Exception): + sphere.CopyFileIntoVM(srcFile=None, dstFile=None, overwrite=True) + with pytest.raises(Exception): + sphere.CopyFileIntoVM(srcFile='SOMEPATH', dstFile='SOMEPATH', overwrite=False) + with pytest.raises(Exception): + sphere.CopyFileIntoVM(srcFile='SOMEPATH', dstFile='SOMEPATH', overwrite=True) + + def test_CopyFileFromVM(self): + sphere = VSphereTools.Sphere() + # TODO: How to check this method if status == 'POWERED ON'? + with pytest.raises(Exception): + sphere.CopyFileFromVM(srcFile=None, dstFile=None, overwrite=False) + with pytest.raises(Exception): + sphere.CopyFileFromVM(srcFile=None, dstFile=None, overwrite=True) + with pytest.raises(Exception): + sphere.CopyFileFromVM(srcFile='SOMEPATH', dstFile='SOMEPATH', overwrite=False) + with pytest.raises(Exception): + sphere.CopyFileFromVM(srcFile='SOMEPATH', dstFile='SOMEPATH', overwrite=True) + + def test_MakeDirectoryOnVM(self): + sphere = VSphereTools.Sphere() + # TODO: How to check this method if status == 'POWERED ON'? + with pytest.raises(Exception): + sphere.MakeDirectoryOnVM(dirPath=None, createSubDirs=True) + with pytest.raises(Exception): + sphere.MakeDirectoryOnVM(dirPath=None, createSubDirs=False) + with pytest.raises(Exception): + sphere.MakeDirectoryOnVM(dirPath='SOMEDIR', createSubDirs=True) + with pytest.raises(Exception): + sphere.MakeDirectoryOnVM(dirPath='SOMEDIR', createSubDirs=True) + + def test_MonitoringProcessOnVM(self): + sphere = VSphereTools.Sphere() + assert sphere.MonitoringProcessOnVM(pID=None, remoteLogFile=None) == -1 # -1 because state is 'POWERED OFF' + assert sphere.MonitoringProcessOnVM(pID=None, remoteLogFile='SOMEFILE') == -1 + assert sphere.MonitoringProcessOnVM(pID=123, remoteLogFile=None) == -1 + assert sphere.MonitoringProcessOnVM(pID=123, remoteLogFile='SOMEFILE') == -1 + + def test_ExecuteProgramOnVM(self): + sphere = VSphereTools.Sphere() + # TODO: How to check this method if status == 'POWERED ON'? How to check if program and if wait code blocks? + with pytest.raises(Exception): + sphere.ExecuteProgramOnVM() + with pytest.raises(Exception): + sphere.ExecuteProgramOnVM( + program=r"C:\Windows\System32\cmd.exe", + args=r"/T:Green /C echo %aaa% & echo %bbb%", + env=r"aaa:10, bbb:20", + cwd=r"C:\Windows\System32\\", + pythonbin=r"/python32/python", + wait=True, + ) diff --git a/Logger.py b/vspheretools/Logger.py similarity index 100% rename from Logger.py rename to vspheretools/Logger.py diff --git a/PySphereRoutine.py b/vspheretools/VSphereTools.py similarity index 87% rename from PySphereRoutine.py rename to vspheretools/VSphereTools.py index f74c92d..c18ab52 100644 --- a/PySphereRoutine.py +++ b/vspheretools/VSphereTools.py @@ -4,11 +4,19 @@ # This module realize some functions for work with vSphere and virtual machines. -# Home wiki-page: https://github.com/devopshq/vspheretools/wiki/vSphereTools-Instruction-(ru) +# Home wiki-page: http://devopshq.github.io/vspheretools/ import os -import sys + +import argparse +import traceback +from datetime import datetime +import time + +from pysphere import VIServer +from vspheretools.Logger import * + # ssl compatibilities: if sys.platform is not 'win32': @@ -16,24 +24,42 @@ import ssl ssl._create_default_https_context = ssl._create_unverified_context -import argparse -import traceback -from datetime import datetime -import time -from pysphere import VIServer -from Logger import * +def Version(onlyPrint=False): + """ + Return current version of FuzzyClassificator build + """ + import pkg_resources # part of standart setuptools + + try: + version = pkg_resources.get_distribution('vspheretools').version + + except Exception: + if onlyPrint: + LOGGER.setLevel(logging.CRITICAL) + print('unknown') + + return 'unknown' + if onlyPrint: + LOGGER.setLevel(logging.CRITICAL) + print(version) + return version + + +# ---------------------------------------------------------------------------------------------------------------------- # Global variables: VC_SERVER = r"" # e.g. vcenter-01.example.com -VC_LOGIN = r"" -VC_PASSWORD = r"" -VM_NAME = r"" -VM_GUEST_LOGIN = r"" -VM_GUEST_PASSWORD = r"" -VM_CLONES_DIR = "Clones" # dir for cloning vm +VC_LOGIN = r"" # login to Sphere +VC_PASSWORD = r"" # password to Sphere +VM_NAME = r"" # name of virtual machine +VM_GUEST_LOGIN = r"" # login to VM guest OS +VM_GUEST_PASSWORD = r"" # password to VM guest +VM_CLONES_DIR = "Clones" # directory for cloning vm OP_TIMEOUT = 300 # operations timeout in seconds +__version__ = Version() # set version of current vSphereTools build +# ---------------------------------------------------------------------------------------------------------------------- def ParseArgsMain(): @@ -42,8 +68,11 @@ def ParseArgsMain(): """ parser = argparse.ArgumentParser() # command-line string parser - parser.description = 'This program realize some helper functions for work with vSphere and virtual machines.' - parser.epilog = 'PySphereRoutine using Python 2*' + parser.description = 'vSphereTools version: {}. vSphereTools is a set of scripts from DevOpsHQ to support working with vSphere and virtual machines (VMs) on it, which are based on the pysphere library.'.format(__version__) + parser.epilog = 'See examples on GitHub: http://devopshq.github.io/vspheretools/' + parser.usage = 'vspheretools [options] [command]' + + parser.add_argument('-v', '--version', action='store_true', help='Show current version of vSphereTools.') # --- server options: parser.add_argument('-s', '--server', type=str, help='main vSphere Server Cluster, e.g. vcenter-01.example.com.') @@ -81,7 +110,7 @@ def ParseArgsMain(): parser.add_argument('--download-file', type=str, nargs='+', help='Download file from virtual machine with True to overwrite local file. Example: --download-file srcFile dstFile True') parser.add_argument('--mkdir', type=str, nargs='+', help='Creating directory and all sub-directory in given path with True to create sub-dirs. Example: --mkdir dir_path True') - parser.add_argument('--execute', type=str, nargs='+', help='Execute program on guest OS with parameters. Example: --execute program="C:\Windows\System32\cmd.exe" args="/T:Green /C echo %aaa% & echo %bbb%" env="aaa:10, bbb:20" cwd="C:\Windows\System32" pythonbin="c:\python27\python.exe" wait=True') + parser.add_argument('--execute', type=str, nargs='+', help=r'Execute program on guest OS with parameters. Example: --execute program="C:\Windows\System32\cmd.exe" args="/T:Green /C echo %%aaa%% & echo %%bbb%%" env="aaa:10, bbb:20" cwd="C:\Windows\System32" pythonbin="c:\python27\python.exe" wait=True') parser.add_argument('--not-skip-run', type=str, help='This is parameter for TeamCity support. Scripts executed if "TRUE". Scripts skipped if "FALSE". Otherwise exception raised.') @@ -102,40 +131,42 @@ class Sphere(): def __init__(self): try: + LOGGER.info('vSphereTools version used: {}'.format(__version__)) + LOGGER.debug('vSphereTools Sphere() class initializing...') self.vSphereServerInstance = VIServer() # Initialize main vSphere Server self.vSphereServerInstance.connect(VC_SERVER, VC_LOGIN, VC_PASSWORD) # Connect vSphere Client self.vmInstance = self.vSphereServerInstance.get_vm_by_name(VM_NAME) # Get instance of virtual machine - except: + except Exception as e: + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) self.vSphereServerInstance = None self.vm = None - LOGGER.error('Can not connect to vSphere! server = {}, vm_name = {}'.format(VC_SERVER, VM_NAME)) + LOGGER.error('Can not connect to vSphere! Maybe incorrect command? Show examples: vspheretools -h') def VMStatus(self): """ Get status of virtual machine. """ - status = None try: status = self.vmInstance.get_status() + LOGGER.info('Current status of virtual machine "{}": {}'.format(VM_NAME, status)) - except: + except Exception as e: status = None + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while getting status of virtual machine "{}"!'.format(VM_NAME)) - finally: - LOGGER.info('Current status of virtual machine "{}": {}'.format(VM_NAME, status)) - return status + return status def VMStart(self): """ Starting virtual machine. """ - status = None try: status = self.VMStatus() + if status == 'POWERED OFF': LOGGER.debug('Trying to start VM...') self.vmInstance.power_on() @@ -145,21 +176,21 @@ def VMStart(self): else: LOGGER.warning('Virtual machine "{}" powered on already!'.format(VM_NAME)) - except: + except Exception as e: status = None + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while starting virtual machine "{}"!'.format(VM_NAME)) - finally: - return status + return status def VMStartWait(self): """ Starting virtual machine and wait while guest OS started. """ - status = None try: status = self.VMStatus() + if status == 'POWERED OFF': LOGGER.debug('Trying to start VM...') self.vmInstance.power_on() @@ -172,21 +203,21 @@ def VMStartWait(self): else: LOGGER.warning('Virtual machine "{}" powered on already!'.format(VM_NAME)) - except: + except Exception as e: status = None + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while starting virtual machine "{}" and waiting for guest OS start!'.format(VM_NAME)) - finally: - return status + return status def VMStop(self): """ Stopping virtual machine. """ - status = None try: status = self.VMStatus() + if status == 'POWERED ON': LOGGER.debug('Trying to stop VM...') self.vmInstance.power_off() @@ -196,27 +227,26 @@ def VMStop(self): else: LOGGER.warning('Virtual machine "{}" powered off already!'.format(VM_NAME)) - except: + except Exception as e: status = None + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while stopping virtual machine "{}"!'.format(VM_NAME)) - finally: - return status + return status def GetVMProperties(self): """ Read all VM properties and return dictionary. """ - properties = {} try: properties = self.vmInstance.get_properties(from_cache=False) - LOGGER.info('All properties of virtual machine "{}":'.format(VM_NAME)) + for key in properties.keys(): if isinstance(properties[key], dict): - LOGGER.info(' {}:'.format(key)) + for subKey in properties[key].keys(): if isinstance(properties[key], dict): @@ -230,20 +260,18 @@ def GetVMProperties(self): else: LOGGER.info(' {}: {}'.format(key, properties[key])) - except: + except Exception as e: properties = None + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while getting properties of virtual machine "{}"!'.format(VM_NAME)) - finally: - return properties + return properties def GetVMSnapshotsList(self): """ Read and return list of all VM snapshots. """ - current = None - snapshots = [] try: current = self.vmInstance.get_current_snapshot_name() snapshots = self.vmInstance.get_snapshots() @@ -258,13 +286,13 @@ def GetVMSnapshotsList(self): else: LOGGER.warning('No snapshots found for virtual machine "{}"!'.format(VM_NAME)) - except: + except Exception as e: snapshots = None + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while getting list of snapshots of virtual machine "{}"!'.format(VM_NAME)) - finally: - return snapshots + return snapshots def CreateVMSnapshot(self, **kwargs): """ @@ -341,42 +369,41 @@ def CreateVMSnapshot(self, **kwargs): LOGGER.warning('To do nothing because snapshot already exist and not rewrite and not fail enabled.') statusCode = 0 - except: + except Exception as e: statusCode = -1 + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while creating snapshots of virtual machine "{}"!'.format(VM_NAME)) - finally: - return statusCode + return statusCode def GetVMIPaddress(self): """ Read VM property and return ip-adress. """ ip = None + try: - ip = None startTime = datetime.now() while not ip and (datetime.now() - startTime).seconds <= OP_TIMEOUT: LOGGER.debug('Trying to get ip-address (timeout = {})...'.format(OP_TIMEOUT)) properties = self.vmInstance.get_properties(from_cache=False) ip = None if 'ip_address' not in properties.keys() else properties['ip_address'] - time.sleep(5) + time.sleep(1) if ip: - LOGGER.info('Virtual machine "{}" has ip-adress: {}'.format(VM_NAME, ip)) + LOGGER.info('Virtual machine "{}" has ip-address: {}'.format(VM_NAME, ip)) else: - LOGGER.info('Virtual machine "{}" has no ip-adress.'.format(VM_NAME)) + LOGGER.info('Virtual machine "{}" has no ip-address.'.format(VM_NAME)) - except: - ip = '0.0.0.0' + except Exception as e: + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('Can not set up ip-address of virtual machine "{}" into TeamCity!'.format(VM_NAME)) - finally: - return ip + return ip def SetVMIPaddressIntoTeamCityParameter(self, paramName): """ @@ -409,14 +436,13 @@ def VMRevertToCurrentSnapshot(self): self.VMStatus() - except: + except Exception as e: statusCode = -1 - + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while revert virtual machine "{}" into current snapshot!'.format(VM_NAME)) - finally: - return statusCode + return statusCode def VMRevertToSnapshot(self, snapshotName=None): """ @@ -436,14 +462,13 @@ def VMRevertToSnapshot(self, snapshotName=None): else: statusCode = self.VMRevertToCurrentSnapshot() - except: + except Exception as e: statusCode = -2 - + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while revert virtual machine "{}" into snapshot "{}"!'.format(VM_NAME, snapshotName)) - finally: - return statusCode + return statusCode def CloneVM(self, cloneName=None): """ @@ -457,7 +482,8 @@ def CloneVM(self, cloneName=None): LOGGER.debug('Name already used: {}'.format(cloneName)) cloneName = None - except: + except Exception as e: + LOGGER.debug(e) LOGGER.debug('Name not used: {}'.format(cloneName)) i = 1 @@ -469,7 +495,8 @@ def CloneVM(self, cloneName=None): cloneName = None i += 1 - except: + except Exception as e: + LOGGER.debug(e) LOGGER.debug('Name not used: {}'.format(cloneName)) LOGGER.debug('Cloning in progress...') @@ -477,7 +504,8 @@ def CloneVM(self, cloneName=None): LOGGER.info('Virtual machine "{}" successful clone into directory "{}" with new name "{}".'.format(VM_NAME, VM_CLONES_DIR, cloneName)) - except: + except Exception as e: + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while cloning virtual machine "{}" into directory "{}"!'.format(VM_NAME, VM_CLONES_DIR)) @@ -505,7 +533,8 @@ def DeleteVM(self): else: LOGGER.warning('Virtual machine "{}" NOT delete with message: "{}".'.format(VM_NAME, msg)) - except: + except Exception as e: + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while deleting virtual machine "{}"!'.format(VM_NAME)) @@ -528,7 +557,8 @@ def CopyFileIntoVM(self, srcFile=None, dstFile=None, overwrite=False): LOGGER.info('File "{}" on guest OS successful copied as file "{}".'.format(srcFile, dstFile)) - except: + except Exception as e: + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while copying file "{}" into destination file "{}" inside virtual machine "{}"!'.format(srcFile, dstFile, VM_NAME)) @@ -554,7 +584,8 @@ def CopyFileFromVM(self, srcFile=None, dstFile=None, overwrite=False): LOGGER.info('File "{}" successful copied from VM guest OS as local file "{}".'.format(srcFile, dstFile)) - except: + except Exception as e: + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while copying file "{}" from virtual machine "{}" into destination file "{}"!'.format(srcFile, VM_NAME, dstFile)) @@ -580,9 +611,11 @@ def MakeDirectoryOnVM(self, dirPath=None, createSubDirs=True): LOGGER.info('Directory "{}" on guest OS successful created.'.format(dirPath)) - except: + except Exception as e: + LOGGER.debug(e) trace = traceback.format_exc() - if not 'FileAlreadyExistsFault' in trace: + + if 'FileAlreadyExistsFault' not in trace: LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while directory "{}" created inside virtual machine "{}"!'.format(dirPath, VM_NAME)) @@ -646,13 +679,13 @@ def MonitoringProcessOnVM(self, pID, remoteLogFile=None): for line in output: LOGGER.debug(' {}'.format(line.strip())) - except: + except Exception as e: + LOGGER.debug(e) LOGGER.error('Unknown exception occurred while process executing!') LOGGER.error(traceback.format_exc()) statusCode = -1 - finally: - return statusCode + return statusCode def ExecuteProgramOnVM(self, **kwargs): """ @@ -660,8 +693,8 @@ def ExecuteProgramOnVM(self, **kwargs): program - string path to executable file, e.g. r"C:\Windows\System32\cmd.exe", args - comma-separated strings with arguments to the program, e.g. r"/T:Green /C echo %aaa% & echo %bbb%", env - comma-separated strings with environment variables, e.g. r"aaa:10, bbb:20", - cwd - string path to working directory, e.g. r"C:\Windows\System32\", - pythonbin - path to python binary on VM, + cwd - string path to working directory, e.g. r'C:\Windows\System32\', + pythonbin - path to python binary on VM, e.g. r'/python32/python' wait - wait for process end and then return stderr, stdout and exit code. """ returnCode = 0 # process return code @@ -705,7 +738,12 @@ def ExecuteProgramOnVM(self, **kwargs): remoteLogFile = os.path.join(os.path.dirname(program[0]), 'vm_process.log') # log file on VM remoteScriptFile = os.path.join(os.path.dirname(program[0]), os.path.basename(scriptFile)) # wrapper on VM - script = r"""# This is script wrapper using for run user-command on VM. + script = r"""# -*- coding: utf-8 -*- +# +# Author: Timur Gilmullin, tim55667757@gmail.com +# This is script wrapper using for run user-command on VM side. + + import os, sys, subprocess, traceback returnCode = 0 @@ -773,8 +811,9 @@ def ExecuteProgramOnVM(self, **kwargs): returnCode = self.MonitoringProcessOnVM(pid, remoteLogFile) - except: + except Exception as e: returnCode = -1 + LOGGER.debug(e) LOGGER.error(traceback.format_exc()) LOGGER.error('An error occured while executing command inside virtual machine!') @@ -787,9 +826,25 @@ def ExecuteProgramOnVM(self, **kwargs): return returnCode -if __name__ == "__main__": +def Main(): + """ + Main entry point. + """ + global VC_SERVER + global VC_LOGIN + global VC_PASSWORD + global VM_NAME + global VM_GUEST_LOGIN + global VM_GUEST_PASSWORD + global VM_CLONES_DIR + global OP_TIMEOUT + args = ParseArgsMain() # get and parse command-line parameters + if args.version: + Version(onlyPrint=True) # Show current version of vSphereTools + sys.exit(0) + if args.not_skip_run: if args.not_skip_run == "TRUE": LOGGER.info('--not-skip-run="TRUE". All scripts will be execute (parameter _execute_step_0_if = "TRUE" in TeamCity metaranner).') @@ -937,4 +992,8 @@ def ExecuteProgramOnVM(self, **kwargs): else: LOGGER.warning('Please, select command!') - sys.exit(exitCode) \ No newline at end of file + sys.exit(exitCode) + + +if __name__ == "__main__": + Main() diff --git a/vspheretools/__init__.py b/vspheretools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vspheretools/__main__.py b/vspheretools/__main__.py new file mode 100644 index 0000000..6edd202 --- /dev/null +++ b/vspheretools/__main__.py @@ -0,0 +1,4 @@ +from vspheretools.VSphereTools import Main + +if __name__ == "__main__": + Main()