Getting Started
===============

.. note:: As a convention throughout this documentation, whenever we write an entity such as ``pool``, ``volume``, ``system`` etc., it will *always* refer to a Python object -- never to a name string or to an object identifier. When we want to indicate a name or an id we will name variables accordingly (e.g. ``volume_id``, ``pool_name`` etc.).

Installation
------------

Installing InfiniSDK is done by using ``pip``::

   $ pip install infinisdk

.. note:: Depending on your Python installation, the above command might require root privileges


.. seealso:: For more information on pip and how to use it to install Python packages, see https://pip.pypa.io/en/stable/

Creating the InfiniBox Object
-----------------------------

In your Python interpreter, import the :class:`infinisdk.InfiniBox <infinisdk.infinibox.InfiniBox>` class, and initialize it with your system address:

.. code-block:: python

		>>> from infinisdk import InfiniBox
		>>> system = InfiniBox(SYSTEM_ADDRESS)

.. note:: ``SYSTEM_ADDRESS`` can be a hostname for your system's management address, or an IP address

SSL is disabled by default, but can be easily turned on by passing ``use_ssl=True`` to the system constructor:

.. code-block:: python

		>>> system = InfiniBox(SYSTEM_ADDRESS, use_ssl=True)

.. note:: By default, constructing a system does not send any traffic or API calls to the system. Only performing actual actions or queries does.


Authentication
--------------

Authentication information can also be specified via the constructor:

.. code-block:: python

       >>> system = InfiniBox(SYSTEM_ADDRESS, auth=("admin", "password"))

Note that you need to explicitly call ``login`` to actually log in to the system:

.. code-block:: python

       >>> system.login()
       <Response [200]>


Another way authentication information can be provided is through an ``.ini`` file. Create a file named ``~/.infinidat/infinisdk.ini``, with the following structure::

  [infinibox]
  username=admin
  password=password

Now constructing an InfiniBox object will use the credentials above by default. You can also specify authorization for specific system, by adding sections to the **.ini** file titled ``infinibox:<system name>``::

  [infinibox] # will be used for default
  username=defaultlogin
  password=defaultpassword

  [infinibox:system01] # will be used for interacting with the InfiniBox named 'system01'
  username=other
  password=otherpassword


Logging
-------

InfiniSDK uses `Logbook <http://logbook.pocoo.org>`_ for logging, and by default all logs are emitted to the standard error stream.

The emitted logs also include the full debug outputs of the API calls made to the system, which might be a bit too much in some cases, overflowing your console unnecessarily. If you prefer less verbosity, you can set up a different logging scheme. For instance, the following code will only emit ``INFO`` logs to the console:

.. code-block:: python

		>>> import logbook
		>>> import sys
		>>> with logbook.NestedSetup([
		...        logbook.NullHandler(),
		...        logbook.StreamHandler(sys.stderr, level=logbook.INFO)]):
		...     pass  # your code here

.. seealso:: `Logbook's documentation <http://logbook.pocoo.org>`_


Approving Dangerous Operations
------------------------------

By default, InfiniSDK performs operations regardless of the level of caution required for them. When a user uses a CLI or a GUI, Infinidat products often require confirmation before carrying out some dangerous operations requiring extra attention.

If you want your script to interactively ask the user for confirmation for such operations, use the :meth:`.set_interactive_approval` method:

.. code-block:: python

		>>> system.api.set_interactive_approval()

You can also turn off approvals temporarily, causing your script to fail with an exception in case dangerous operations are about to be carried out:

.. code-block:: python

		>>> with system.api.get_unapproved_context():
		...     pass # operations here

.. seealso::

   :meth:`.get_unapproved_context`, :meth:`.set_interactive_approval`


Representing API Entities
-------------------------

InfiniSDK provides reflection for objects or entities defined on the system in the form of Pythonic objects. This makes creation, deletion and manipulation of objects easier. Supported objects are defined as Python classes such as :class:`infinisdk.infinibox.volume.Volume` or :class:`infinisdk.infinibox.pool.Pool`, and are accessed more easily through **collection proxies**, such as *system.volumes*, *system.pools* etc. For each supported object type ``X``, there exists ``system.Xs``.

The following examples illustrate how to use those proxies.

Creating Objects
----------------

Creation of objects can be done easily via the :func:`create <infinisdk.core.type_binder.TypeBinder.create>` method. InfiniSDK provides defaults for all required fields that can be autogenerated. For instance, creating a pool can be done via *system.pools.create()*:

.. code-block:: python

		>>> pool = system.pools.create()

.. note:: the *create* shortcut used above is a very thin wrapper around :meth:`the create method of the Pool class <infinisdk.infinibox.pool.Pool.create>`. All it does is automatically assign the "right" system to the first argument.

Object Attributes
-----------------

Once an object is obtained (either by creation or querying as described further down), it can be inspected for its attributes or manipulated in various ways. This is done using getter/setter methods. For most used names, there are direct setters and getters:

.. code-block:: python

		>>> pool.update_name('new_name')
		>>> pool.get_name() == 'new_name'
		True


All fields can be accessed via the :meth:`.SystemObject.get_field` / :meth:`.SystemObject.update_field` methods:

.. code-block:: python

		>>> pool.update_field('name', 'yet_another_name')
		>>> pool.get_field('name') == 'yet_another_name'
		True

.. _caching:

Caching
-------

Whenever an object attribute is fetched, it is cached for later use. By default, getting fields always fetches them from the cache of the requested object.

In case you need to fetch an up-to-date value for a field, there are several options:

1. Use ``from_cache=False``:

   .. code-block:: python

		   >>> print(pool.get_field('name', from_cache=False))
		   yet_another_name

   The above forces InfiniSDK to fetch the name from the system regardless of the cache

2. Disable caching completely:

   .. code-block:: python

		   >>> system.disable_caching()

.. _capacities:

Storage Capacity Handling
-------------------------

InfiniSDK reflects data sizes using the ``capacity`` module, allowing easy computations and manipulations of data sizes, including units:

.. code-block:: python

		>>> from capacity import GiB

		>>> size = pool.get_virtual_capacity()
		>>> print(size)
		1 TB
		>>> print(size * 2)
		2 TB
		>>> print(size // GiB)
		931

.. seealso:: `Documentation for the capacity module <https://github.com/vmalloc/capacity/>`_



Querying Objects
----------------

Querying objects of various types is done relatively easily through InfiniSDK. The InfiniBox system exposes collection proxies, which provide iteration and filtering. Here's an example of querying all volumes on a system:

.. code-block:: python

		>>> system.volumes.count()
		0

		>>> system.volumes.to_list()
		[]

.. seealso:: :ref:`querying`


Deleting Objects
----------------

Deleting objects can be done by the ``delete`` method, which is available for the vast majority of the object types.

.. code-block:: python

                >>> host = system.hosts.create()
                >>> host.delete() # <-- host gets deleted

.. note:: The ``delete`` method usually doesn't take care of indirect deletion needed to fullfill the request (like deleting volumes inside pools). This is a design decision that has been made to prevent unintended operations from being unwittingly made on the user's behalf.


Accessing HTTP/REST API Directly
--------------------------------

InfiniSDK supports calling the HTTP/REST API of the system directly:

.. code-block:: python

		>>> response = system.api.get('system/product_id')

The above accesses ``/api/rest/system/product_id``. :meth:`.API.get`, :meth:`.API.post`, :meth:`.API.delete` and :meth:`.API.put` all return :class:`.Response` objects. Results can be fetched by :meth:`.Response.get_result`:

.. code-block:: python

		>>> print(response.get_result())
		INFINIBOX

You can always access the response belonging to `requests <http://docs.python-requests.org/en/latest/>`_ through ``.response``:

.. code-block:: python

		>>> response.response.status_code
		200


By default, requests are checked for success. This behavior can be overriden by providing ``assert_success=False``:

.. code-block:: python

		>>> response = system.api.get('nonexistent/path', assert_success=False)
		>>> response.response.status_code
		404
