Tutorial ======== Python-RHEV is a set of bindings for the Python language that facilitate accessing the Red Hat Enterprise Virtualization RESTful API. Normally, RESTful APIs can be accessed via a regular HTTP library, and the Red Hat Enterprise Virtualization API is no exception. Python-RHEV however implements a few features on top of the normal HTTP access that make consumption of the API easier: * Connection management. Python-RHEV will automatically connect, authenticated, reconnect in case of errors, and follow links. * Object mapping. Python-RHEV provides Python classes for the resources and collections that are exposed in the API (e.g. Cluster, VM, etc.). This allows you to interact with the API in an object oriented way. * Exceptions. Errors are raised as exceptions. This means that you don't need to use a lot of in-line code to deal with errors. This section provides a tutorial on how to use the Red Hat Enterprise Virtualization API for first-time users. Connecting to the API --------------------- Before you can use the API, you need to connect to it. Three pieces of information are required before a connection can be made: * The URL of the API entry point * A user name * A password Connections are established via the :class:`rhev.Connection` class. The following example illustrates this:: >>> from rhev import Connection >>> conn = Connection() >>> conn.connect(url, username, password) >>> conn.ping() The ``connect()`` method connects to the API at *url*, and sets the supplied *username* and *password* as the default credentials. The ``ping()`` method will retrieve the entry point. If no exception is raised in either call, you can be certain that a proper connection is available. Resources and Collections ------------------------- The Red Hat Enterprise Virtualization API is a RESTful API, and the RESTful nature of it permeates through the Python bindings. This is on purpose, both for philosophical reasons (I believe the RESTful paradigm is a particularly useful way to consume this type of functionality), and for practical reasons (the language bindings become a simple shim). If you are new to RESTful APIs, you need to know the two key concepts of any RESTful API. These are the **Collection** and the **Resource**. Think of a resource as an object with a fixed API (create, read, update and delete) that contains a set of attributes that are relevant for the resource type. Think of a collection as a set of resources of the same type. The following example shows how to retrieve a list of all virtual machines:: >>> from rhev import schema >>> vms = conn.getall(schema.VM) >>> type(vms) The ``getall()`` method accepts a class from the :mod:`rhev.schema` module that specifies the type of object we want to retrieve. We call these classes *binding classes*. Each binding class corresponds to one type within Red Hat Enterprise Virtualization. Instances of binding classes are called *binding instances*. Two kinds of binding classes exist, corresponding to resources and collections. Resources have a singular name (like "VM" and "Cluster") and derive from :class:`BaseResource`. Collections have a plural name ("VMs", "Clusters") and derive from :class:`BaseResources`. If you have the ID of a resource, you can retrieve only that resource using the ``get()`` call:: >>> vm = conn.get(schema.VM, 'xxx') Collections behave like lists. Resources inside collections can be accessed using the Python ``[]`` operator. Resources behave like Python objects. Attributes inside resources are accessed as regular Python attributes:: >>> len(vms) 3 >>> vm = vms[0] >>> type(vm) >>> vm.name 'vm0' >>> vm.status.state 'running' Working with Resources ---------------------- Resources are always created inside a collection. The collection that the resource is created in, depends on the resource type, and is determined automatically by the API. For example, a resource of type :class:`rhev.schema.VM` will be created in the :class:`rhev.schema.VMs` collection. For example:: >>> vm = schema.new(schema.VM) >>> vm.name = 'vm0' >>> # set other mandatory attributes >>> conn.create(vm) A resource can be updated by updating its attributes, and then calling the :meth:`Connection.update()` method:: >>> vm.name = 'newname' >>> connection.update(vm) A resource is deleted using the :meth:`Connnection.delete()` method. Example:: >>> connection.delete(vm) Sometimes a resource can be changed by other users of the API while you have a reference to it. The ``reload()`` call reloads the resource in-place:: >>> conn.reload(vm) Many resources in the Red Hat Enterprise Virtualization API contain a ``state`` sub-resource with a ``status`` attribute. When a VM is down for example, ``state.status`` will have the value ``'down'``. When starting the VM, the state will go via ``'wait for launch`'' to ``'powering up'`` to ``'up'``. Sometimes it is necessary to wait for a resource to be in a certain state. Python-RHEV facilitates this with the ``wait_for_status()`` method:: >>> conn.wait_for_status(vm, 'up') .. note:: This will poll the API repeatedly. They between requests will increase over time up to a certain maximum. The ``update()``, ``delete()``, ``reload()`` and ``wait_for_status()`` operations are also made available on ``BaseResource``. This allows you to use a more object-oriented style of using the API. For example:: >>> vm.name = 'newname' >>> vm.update() >>> vm.reload() >>> vm.wait_for_status('up') >>> vm.delete() The ``create()`` method is available as well, but it needs to be called on the containing resource. See the section on sub-resources and sub-collections below for more information. Sub-resources and Sub-collections --------------------------------- We have already introduced resources and collection. An extension to these concepts are the sub-collection and the sub-resource. A resource can have sub-collections inside it, which can have sub-resources, and so on. Sub-collections and sub-resources are used extensively in the Red Hat Enterprise Virtualization API when modeling dependent objects. For example, a virtual machine will have multiple disks. Those disks related only to the virtual machine, and have no use outside it. The API models this as a sub-collection ``schema.Disks`` under the resource of type ``schema.VM``. Sub-collections and sub-resources can be accessed and/or modified by supplying the ``base`` parameter to any of the ``get()``, ``getall()``, ``create()``, ``update()`` and ``delete()`` calls. For example:: >>> disks = conn.getall(schema.Disk, base=vm) >>> disk = schema.new(schema.Disk) >>> # set disk.size, etc. >>> conn.create(disk, base=vm) .. note:: Formally, the top-level collections are resources are also considered sub-collections and sub-resources. Their containing resource is the API entry point, can be considered as the only top-level resource. It does not have a corresponding collection, and it can be obtained using:: >>> api = conn.get(schema.API) Or its short hand:: >>> api = conn.api() By not specifying a ``base`` parameter to any of the functions mentioned above, you are implicitly specifying a base of ``schema.API``. Actions ------- Certain operations in the API do not match cleanly to the 4 standard REST methods ``get()``, ``update()``, ``delete()`` and ``create()``. These operations are exposed as so-called *actions* by the API. Python-RHEV allows you to call an action via the :meth:`Connection.action()` method. For example, to execute the "start" action on a virtual machine:: >>> connection.action(vm, 'start') Some actions can take input. In this case, you must pass an instance of a ``schema.Action`` class. For example:: >>> action = schema.new(schema.Action) >>> action.stateless = true >>> conn.action(vm, 'start', action) Actions are also available as methods on the ``BaseResource`` class. So the above is the same as:: >>> vm.start(action) Searching --------- For certain collections, the RHEV API provides search capabilities. This functionality can be accessed by providing the ``search`` keyword argument to ``get()`` and ``getall()``:: >>> vms = conn.getall(schema.VM, search='name = a* sortby creation_time') Searches that are just simple AND combinations of attribute searches and that do not have a "sortby" or "page" clause can also be provides as keyword arguments:: >>> vms = conn.getall(schema.VM, name='a*') Search functionality is particularly useful in two cases. First, to identify an object by its name:: >>> vm = conn.get(schema.VM, name='foo') Second, by using the "page" clause for the search syntax, you can retrieve more than the default of 100 objects that is normally returned:: >>> vms = [] >>> vms += conn.getall(schema.VM, search='sortby name page 1') >>> vms += conn.getall(schema.VM, search='sortby name page 2') Using Relationship Names ------------------------ Instead of providing a binding type, you can also specify a type by its name. The name needs to correspond to the *relationship name* as it is provided by the API (this is value of the "rel" attribute in elements). For example:: >>> # These two are equivalent >>> vm = conn.getall('vms') >>> vm = conn.getall(schema.VM) Using relationship names is useful to access some non-standards relationship, for example:: >>> tag = conn.get('tags/root') >>> template = conn.get('templates/blank') Populating Resources -------------------- So far we have see that new resources can be populated by setting their attributes simply as Python attributes:: >>> vm = schema.new(schema.VM) >>> vm.name = 'myvm' However, the data types involved in the API are not flat, but nested. In order to set a scheduling policy on a VM for example, you need to do this:: >>> vm.placement_policy = schema.new(schema.VmPlacementPolicy) >>> vm.placement_policy.host = schema.new(schema.Host) >>> vm.placement_plilcy.host.name = 'myhost' This requires that you need to know the name of all the types involved. Some types (like "Host" in the example above) are also top-level types so you probably already know them. Other types however (like VmPlacementPolicy) are internal types that may be relevant only to one specific resource. Python-RHEV contains a utility function called ``subtype()`` that can faciliated creating nested attribute where you do not know the exact type:: >>> vm.placement_policy = schema.new(schema.subtype(vm.placement_policy)) Error Handling -------------- Error handling is mostly done through exceptions. All exceptions are subclasses of the :class:`rhev.Error` class. Generally, any HTTP response that is not in the 2xx or 3xx category is considered an error, and an appropriate exception is raised. Sometimes, a 4xx response contains a valid resource of type ``Fault`` which describes the error in more detail. If that happens, an exception of type ``Fault`` is raised that gives you access to the fault itself:: >>> vm.name = 'my vm' # space not allowed >>> try: ... vm.update() ... except Fault, f: ... print 'fault', f.fault.detail ... Can not edit VM. The given name contains special characters. Only lower-case and upper-case letters, numbers, '_', '-' allowed.