Clarify future changes in docs

The README was discussing future design decisions as if they were
already implemented. This can be confusing for new users. This
separates that discussion into a separate doc page and clarifies
its intentions.

Also, fix sphinx doc build warnings.

Change-Id: Ie66b60d972cae25a9805804ad17632aed0932627
This commit is contained in:
David Shrewsbury 2015-08-04 09:08:32 -04:00
parent 83acc22166
commit 0af29bda2f
4 changed files with 209 additions and 201 deletions

View File

@ -1,5 +1,5 @@
shade
=====
Introduction
============
shade is a simple client library for operating OpenStack clouds. The
key word here is *simple*. Clouds can do many many many things - but there are
@ -17,18 +17,18 @@ library, and adding logic and features that the OpenStack Infra team had
developed to run client applications at scale, it turned out that we'd written
nine-tenths of what we'd need to have a standalone library.
example
-------
Example
=======
Sometimes an example is nice.
::
from shade import *
import shade
import time
# Initialize cloud
# Cloud configs are read with os-client-config
cloud = openstack_cloud(cloud='mordred')
cloud = shade.openstack_cloud(cloud='mordred')
# OpenStackCloud object has an interface exposing OpenStack services methods
print cloud.list_servers()
@ -50,171 +50,3 @@ Sometimes an example is nice.
attachments = cinder.volumes.get(volume_id).attachments
print attachments
Object Design
=============
Shade is a library for managing resources, not for operating APIs. As such,
it is the resource in question that is the primary object and not the service
that may or may not provide that resource, much as we may feel warm and fuzzy
to one of the services.
Every resource at minimum has CRUD functions. Additionally, every resource
action should have a "do this task blocking" or "request that the cloud start
this action and give me a way to check its status" The creation and deletion
of Resources will be handled by a ResourceManager that is attached to the Cloud
::
class Cloud:
ResourceManager<Server> server
servers = server
ResourceManager<FloatingIp> floating_ip
floating_ips = floating_ip
ResourceManager<Image> image
images = image
ResourceManager<Role> role
roles = role
ResourceManager<Volume> volume
volumes = volume
getting, listing and searching
------------------------------
In addition to creating a resource, there are different ways of getting your
hands on a resource. A `get`, a `list` and a `search`.
`list` has the simplest semantics - it takes no parameters and simply returns
a list of all of the resources that exist.
`search` takes a set of parameters to match against and returns a list of
resources that match the parameters given. If no resources match, it returns
an empty list.
`get` takes the same set of parameters that `search` takes, but will only ever
return a single matching resource or None. If multiple resources are matched,
an exception will be raised.
::
class ResourceManager<Resource>:
def get -> Resource
def list -> List<Resource>
def search -> List<Resource>
def create -> Resource
Cloud and ResourceManager interface
-----------------------------------
All ResourceManagers should accept a cache object passed in to their constructor
and should additionally pass that cache object to all Resource constructors.
The top-level cloud should create the cache object, then pass it to each of
the ResourceManagers when it creates them.
Client connection objects should exist and be managed at the Cloud level. A
backreference to the OpenStack cloud should be passed to every resource manager
so that ResourceManagers can get hold of the ones they need. For instance,
an Image ResourceManager would potentially need access to both the glance_client
and the swift_client.
::
class ResourceManager
def __init__(self, cache, cloud)
class ServerManager(ResourceManager)
class OpenStackCloud
def __init__(self):
self.cache = dogpile.cache()
self.server = ServerManager(self.cache, self)
self.servers = self.server
Any resources that have an association action - such as servers and
floating_ips, should carry reciprocal methods on each resource with absolutely
no difference in behavior.
::
class Server(Resource):
def connect_floating_ip:
class FloatingIp(Resource):
def connect_server:
Resource objects should have all of the accessor methods you'd expect, as well
as any other interesting rollup methods or actions. For instance, since
a keystone User can be enabled or disabled, one should expect that there
would be an enable() and a disable() method, and that those methods will
immediately operate the necessary REST apis. However, if you need to make 80
changes to a Resource, 80 REST calls may or may not be silly, so there should
also be a generic update() method which can be used to request the minimal
amount of REST calls needed to update the attributes to the requested values.
Resource objects should all have a to_dict method which will return a plain
flat dictionary of their attributes.
::
class Resource:
def update(**new_values) -> Resource
def delete -> None, throws on error
Readiness
---------
`create`, `get`, and `attach` can return resources that are not yet ready. Each
method should take a `wait` and a `timeout` parameter, that will cause the
request for the resource to block until it is ready. However, the user may
want to poll themselves. Each resource should have an `is_ready` method which
will return True when the resource is ready. The `wait` method then can
actually be implemented in the base Resource class as an iterate timeout
loop around calls to `is_ready`. Every Resource should also have an
`is_failed` and an `is_deleted` method.
Optional Behavior
-----------------
Not all clouds expose all features. For instance, some clouds do not have
floating ips. Additionally, some clouds may have the feature but the user
account does not, which is effectively the same thing.
This should be handled in several ways:
If the user explicitly requests a resource that they do not have access to,
an error should be raised. For instance, if a user tries to create a floating
ip on a cloud that does not expose that feature to them, shade should throw
a "Your cloud does not let you do that" error.
If the resource concept can be can be serviced by multiple possible services,
shade should transparently try all of them. The discovery method should use
the dogpile.cache mechanism so that it can be avoided on subsequent tries. For
instance, if the user says "please upload this image", shade should figure
out which sequence of actions need to be performed and should get the job done.
If the resource isn't present on some clouds, but the overall concept the
resource represents is, a different resource should present the concept. For
instance, while some clouds do not have floating ips, if what the user wants
is "a server with an IP" - then the fact that one needs to request a floating
ip on some clouds is a detail, and the right thing for that to be is a quality
of a server and managed by the server resource. A floating ip resource should
really only be directly manipulated by the user if they were doing something
very floating-ip specific, such as moving a floating ip from one server to
another.
In short, it should be considered a MASSIVE bug in shade if the shade user
ever has to have in their own code "if cloud.has_capability("X") do_thing
else do_other_thing" - since that construct conveys some resource that shade
should really be able to model.
Functional Interface
--------------------
shade should also provide a functional mapping to the object interface that
does not expose the object interface at all. For instance, fora resource type
`server`, one could expect the following.
::
class OpenStackCloud:
def create_server
return self.server.create().to_dict()
def get_server
return self.server.get().to_dict()
def update_server
return self.server.get().update().to_dict()

176
doc/source/future.rst Normal file
View File

@ -0,0 +1,176 @@
************************
Future Design Discussion
************************
This document discusses a new approach to the Shade library and how
we might wish for it to operate in a future, not-yet-developed version.
It presents a more object oriented approach, and design decisions that
we have learned and decided on while working on the current version.
Object Design
=============
Shade is a library for managing resources, not for operating APIs. As such,
it is the resource in question that is the primary object and not the service
that may or may not provide that resource, much as we may feel warm and fuzzy
to one of the services.
Every resource at minimum has CRUD functions. Additionally, every resource
action should have a "do this task blocking" or "request that the cloud start
this action and give me a way to check its status" The creation and deletion
of Resources will be handled by a ResourceManager that is attached to the Cloud
::
class Cloud:
ResourceManager<Server> server
servers = server
ResourceManager<FloatingIp> floating_ip
floating_ips = floating_ip
ResourceManager<Image> image
images = image
ResourceManager<Role> role
roles = role
ResourceManager<Volume> volume
volumes = volume
getting, listing and searching
------------------------------
In addition to creating a resource, there are different ways of getting your
hands on a resource. A `get`, a `list` and a `search`.
`list` has the simplest semantics - it takes no parameters and simply returns
a list of all of the resources that exist.
`search` takes a set of parameters to match against and returns a list of
resources that match the parameters given. If no resources match, it returns
an empty list.
`get` takes the same set of parameters that `search` takes, but will only ever
return a single matching resource or None. If multiple resources are matched,
an exception will be raised.
::
class ResourceManager<Resource>:
def get -> Resource
def list -> List<Resource>
def search -> List<Resource>
def create -> Resource
Cloud and ResourceManager interface
===================================
All ResourceManagers should accept a cache object passed in to their constructor
and should additionally pass that cache object to all Resource constructors.
The top-level cloud should create the cache object, then pass it to each of
the ResourceManagers when it creates them.
Client connection objects should exist and be managed at the Cloud level. A
backreference to the OpenStack cloud should be passed to every resource manager
so that ResourceManagers can get hold of the ones they need. For instance,
an Image ResourceManager would potentially need access to both the glance_client
and the swift_client.
::
class ResourceManager
def __init__(self, cache, cloud)
class ServerManager(ResourceManager)
class OpenStackCloud
def __init__(self):
self.cache = dogpile.cache()
self.server = ServerManager(self.cache, self)
self.servers = self.server
Any resources that have an association action - such as servers and
floating_ips, should carry reciprocal methods on each resource with absolutely
no difference in behavior.
::
class Server(Resource):
def connect_floating_ip:
class FloatingIp(Resource):
def connect_server:
Resource objects should have all of the accessor methods you'd expect, as well
as any other interesting rollup methods or actions. For instance, since
a keystone User can be enabled or disabled, one should expect that there
would be an enable() and a disable() method, and that those methods will
immediately operate the necessary REST apis. However, if you need to make 80
changes to a Resource, 80 REST calls may or may not be silly, so there should
also be a generic update() method which can be used to request the minimal
amount of REST calls needed to update the attributes to the requested values.
Resource objects should all have a to_dict method which will return a plain
flat dictionary of their attributes.
::
class Resource:
def update(**new_values) -> Resource
def delete -> None, throws on error
Readiness
---------
`create`, `get`, and `attach` can return resources that are not yet ready. Each
method should take a `wait` and a `timeout` parameter, that will cause the
request for the resource to block until it is ready. However, the user may
want to poll themselves. Each resource should have an `is_ready` method which
will return True when the resource is ready. The `wait` method then can
actually be implemented in the base Resource class as an iterate timeout
loop around calls to `is_ready`. Every Resource should also have an
`is_failed` and an `is_deleted` method.
Optional Behavior
-----------------
Not all clouds expose all features. For instance, some clouds do not have
floating ips. Additionally, some clouds may have the feature but the user
account does not, which is effectively the same thing.
This should be handled in several ways:
If the user explicitly requests a resource that they do not have access to,
an error should be raised. For instance, if a user tries to create a floating
ip on a cloud that does not expose that feature to them, shade should throw
a "Your cloud does not let you do that" error.
If the resource concept can be can be serviced by multiple possible services,
shade should transparently try all of them. The discovery method should use
the dogpile.cache mechanism so that it can be avoided on subsequent tries. For
instance, if the user says "please upload this image", shade should figure
out which sequence of actions need to be performed and should get the job done.
If the resource isn't present on some clouds, but the overall concept the
resource represents is, a different resource should present the concept. For
instance, while some clouds do not have floating ips, if what the user wants
is "a server with an IP" - then the fact that one needs to request a floating
ip on some clouds is a detail, and the right thing for that to be is a quality
of a server and managed by the server resource. A floating ip resource should
really only be directly manipulated by the user if they were doing something
very floating-ip specific, such as moving a floating ip from one server to
another.
In short, it should be considered a MASSIVE bug in shade if the shade user
ever has to have in their own code "if cloud.has_capability("X") do_thing
else do_other_thing" - since that construct conveys some resource that shade
should really be able to model.
Functional Interface
====================
shade should also provide a functional mapping to the object interface that
does not expose the object interface at all. For instance, for a resource type
`server`, one could expect the following.
::
class OpenStackCloud:
def create_server
return self.server.create().to_dict()
def get_server
return self.server.get().to_dict()
def update_server
return self.server.get().update().to_dict()

View File

@ -4,7 +4,7 @@
contain the root `toctree` directive.
Welcome to shade's documentation!
========================================================
=================================
Contents:
@ -14,6 +14,7 @@ Contents:
installation
usage
contributing
future
.. include:: ../../README.rst

View File

@ -3969,10 +3969,10 @@ class OperatorCloud(OpenStackCloud):
:returns: a dict containing the services description, i.e. the
following attributes::
- id: <service id>
- name: <service name>
- service_type: <service type>
- description: <service description>
- id: <service id>
- name: <service name>
- service_type: <service type>
- description: <service description>
:raises: ``OpenStackCloudException`` if something goes wrong during the
openstack API call.
@ -4028,10 +4028,10 @@ class OperatorCloud(OpenStackCloud):
:returns: a dict containing the services description, i.e. the
following attributes::
- id: <service id>
- name: <service name>
- service_type: <service type>
- description: <service description>
- id: <service id>
- name: <service name>
- service_type: <service type>
- description: <service description>
:raises: ``OpenStackCloudException`` if something goes wrong during the
openstack API call or if multiple matches are found.
@ -4074,7 +4074,7 @@ class OperatorCloud(OpenStackCloud):
:returns: a dict containing the endpoint description.
:raise OpenStackCloudException: if the service cannot be found or if
:raises: OpenStackCloudException if the service cannot be found or if
something goes wrong during the openstack API call.
"""
# ToDo: support v3 api (dguerri)
@ -4122,11 +4122,11 @@ class OperatorCloud(OpenStackCloud):
:returns: a list of dict containing the endpoint description. Each dict
contains the following attributes::
- id: <endpoint id>
- region: <endpoint region>
- public_url: <endpoint public url>
- internal_url: <endpoint internal url> (optional)
- admin_url: <endpoint admin url> (optional)
- id: <endpoint id>
- region: <endpoint region>
- public_url: <endpoint public url>
- internal_url: <endpoint internal url> (optional)
- admin_url: <endpoint admin url> (optional)
:raises: ``OpenStackCloudException``: if something goes wrong during
the openstack API call.
@ -4143,11 +4143,11 @@ class OperatorCloud(OpenStackCloud):
:returns: a dict containing the endpoint description. i.e. a dict
containing the following attributes::
- id: <endpoint id>
- region: <endpoint region>
- public_url: <endpoint public url>
- internal_url: <endpoint internal url> (optional)
- admin_url: <endpoint admin url> (optional)
- id: <endpoint id>
- region: <endpoint region>
- public_url: <endpoint public url>
- internal_url: <endpoint internal url> (optional)
- admin_url: <endpoint admin url> (optional)
"""
return _utils._get_entity(self.search_endpoints, id, filters)
@ -4258,9 +4258,9 @@ class OperatorCloud(OpenStackCloud):
:returns: a list of dicts containing the domain description. Each dict
contains the following attributes::
- id: <domain id>
- name: <domain name>
- description: <domain description>
- id: <domain id>
- name: <domain name>
- description: <domain description>
:raises: ``OpenStackCloudException``: if something goes wrong during
the openstack API call.
@ -4280,10 +4280,9 @@ class OperatorCloud(OpenStackCloud):
:returns: a dict containing the domain description, or None if not
found. Each dict contains the following attributes::
- id: <domain id>
- name: <domain name>
- description: <domain description>
- id: <domain id>
- name: <domain name>
- description: <domain description>
:raises: ``OpenStackCloudException``: if something goes wrong during
the openstack API call.