From 0af29bda2fe3ed9ac6ff7e2237c52471ba413cd3 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Tue, 4 Aug 2015 09:08:32 -0400 Subject: [PATCH] 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 --- README.rst | 180 ++---------------------------------------- doc/source/future.rst | 176 +++++++++++++++++++++++++++++++++++++++++ doc/source/index.rst | 3 +- shade/__init__.py | 51 ++++++------ 4 files changed, 209 insertions(+), 201 deletions(-) create mode 100644 doc/source/future.rst diff --git a/README.rst b/README.rst index b4dc82f04..9b1d254ca 100644 --- a/README.rst +++ b/README.rst @@ -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 - servers = server - ResourceManager floating_ip - floating_ips = floating_ip - ResourceManager image - images = image - ResourceManager role - roles = role - ResourceManager 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: - def get -> Resource - def list -> List - def search -> List - 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() diff --git a/doc/source/future.rst b/doc/source/future.rst new file mode 100644 index 000000000..61c1a4af8 --- /dev/null +++ b/doc/source/future.rst @@ -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 + servers = server + ResourceManager floating_ip + floating_ips = floating_ip + ResourceManager image + images = image + ResourceManager role + roles = role + ResourceManager 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: + def get -> Resource + def list -> List + def search -> List + 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() diff --git a/doc/source/index.rst b/doc/source/index.rst index 15a603f40..97455f584 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -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 diff --git a/shade/__init__.py b/shade/__init__.py index a7b8f4629..81f613ada 100644 --- a/shade/__init__.py +++ b/shade/__init__.py @@ -3969,10 +3969,10 @@ class OperatorCloud(OpenStackCloud): :returns: a dict containing the services description, i.e. the following attributes:: - - id: - - name: - - service_type: - - description: + - id: + - name: + - service_type: + - 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: - - name: - - service_type: - - description: + - id: + - name: + - service_type: + - 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: - - region: - - public_url: - - internal_url: (optional) - - admin_url: (optional) + - id: + - region: + - public_url: + - internal_url: (optional) + - 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: - - region: - - public_url: - - internal_url: (optional) - - admin_url: (optional) + - id: + - region: + - public_url: + - internal_url: (optional) + - 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: - - name: - - description: + - id: + - name: + - 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: - - name: - - description: - + - id: + - name: + - description: :raises: ``OpenStackCloudException``: if something goes wrong during the openstack API call.