[ops-guide] Cleanup customizing chapter
Change-Id: Idef2e0b6cef26266e6ca5ed0773a3fd78f387e6a Implements: blueprint ops-guide-rst
This commit is contained in:
parent
349a9810f4
commit
c04c8075e3
@ -2,14 +2,23 @@
|
|||||||
Customization
|
Customization
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
ops_customize_development.rst
|
||||||
|
ops_customize_objectstorage.rst
|
||||||
|
ops_customize_compute.rst
|
||||||
|
ops_customize_dashboard.rst
|
||||||
|
ops_customize_conclusion.rst
|
||||||
|
|
||||||
OpenStack might not do everything you need it to do out of the box. To
|
OpenStack might not do everything you need it to do out of the box. To
|
||||||
add a new feature, you can follow different paths.
|
add a new feature, you can follow different paths.
|
||||||
|
|
||||||
To take the first path, you can modify the OpenStack code directly.
|
To take the first path, you can modify the OpenStack code directly.
|
||||||
Learn `how to
|
Learn `how to contribute
|
||||||
contribute <https://wiki.openstack.org/wiki/How_To_Contribute>`_,
|
<https://wiki.openstack.org/wiki/How_To_Contribute>`_,
|
||||||
follow the `code review
|
follow the `Developer's Guide
|
||||||
workflow <https://wiki.openstack.org/wiki/GerritWorkflow>`_, make your
|
<http://docs.openstack.org/infra/manual/developers.html>`_, make your
|
||||||
changes, and contribute them back to the upstream OpenStack project.
|
changes, and contribute them back to the upstream OpenStack project.
|
||||||
This path is recommended if the feature you need requires deep
|
This path is recommended if the feature you need requires deep
|
||||||
integration with an existing project. The community is always open to
|
integration with an existing project. The community is always open to
|
||||||
@ -26,825 +35,10 @@ be specific ways of customizing a project, such as creating a new
|
|||||||
scheduler driver for Compute or a custom tab for the dashboard.
|
scheduler driver for Compute or a custom tab for the dashboard.
|
||||||
|
|
||||||
This chapter focuses on the second path for customizing OpenStack by
|
This chapter focuses on the second path for customizing OpenStack by
|
||||||
providing two examples for writing new features. The first example shows
|
providing two examples for writing new features.
|
||||||
how to modify Object Storage (swift) middleware to add a new feature,
|
The first example shows how to modify Object Storage service (swift)
|
||||||
and the second example provides a new scheduler feature for OpenStack
|
middleware to add a new feature, and the second example provides a new
|
||||||
Compute (nova). To customize OpenStack this way you need a development
|
scheduler feature for Compute service (nova).
|
||||||
environment. The best way to get an environment up and running quickly
|
To customize OpenStack this way you need a development environment.
|
||||||
is to run DevStack within your cloud.
|
The best way to get an environment up and running quickly is to run
|
||||||
|
DevStack within your cloud.
|
||||||
Create an OpenStack Development Environment
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
To create a development environment, you can use DevStack. DevStack is
|
|
||||||
essentially a collection of shell scripts and configuration files that
|
|
||||||
builds an OpenStack development environment for you. You use it to
|
|
||||||
create such an environment for developing a new feature.
|
|
||||||
|
|
||||||
You can find all of the documentation at the
|
|
||||||
`DevStack <http://docs.openstack.org/developer/devstack/>`_ website.
|
|
||||||
|
|
||||||
**To run DevStack on an instance in your OpenStack cloud:**
|
|
||||||
|
|
||||||
#. Boot an instance from the dashboard or the nova command-line interface
|
|
||||||
(CLI) with the following parameters:
|
|
||||||
|
|
||||||
- Name: devstack
|
|
||||||
|
|
||||||
- Image: Ubuntu 14.04 LTS
|
|
||||||
|
|
||||||
- Memory Size: 4 GB RAM
|
|
||||||
|
|
||||||
- Disk Size: minimum 5 GB
|
|
||||||
|
|
||||||
If you are using the ``nova`` client, specify :option:`--flavor 3` for the
|
|
||||||
:command:`nova boot` command to get adequate memory and disk sizes.
|
|
||||||
|
|
||||||
#. Log in and set up DevStack. Here's an example of the commands you can
|
|
||||||
use to set up DevStack on a virtual machine:
|
|
||||||
|
|
||||||
#. Log in to the instance:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ ssh username@my.instance.ip.address
|
|
||||||
|
|
||||||
#. Update the virtual machine's operating system:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# apt-get update
|
|
||||||
|
|
||||||
#. Install git:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# apt-get install git
|
|
||||||
|
|
||||||
#. Clone the ``devstack`` repository:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ git clone https://git.openstack.org/openstack-dev/devstack
|
|
||||||
|
|
||||||
#. Change to the ``devstack`` repository:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ cd devstack
|
|
||||||
|
|
||||||
#. (Optional) If you've logged in to your instance as the root user, you
|
|
||||||
must create a "stack" user; otherwise you'll run into permission issues.
|
|
||||||
If you've logged in as a user other than root, you can skip these steps:
|
|
||||||
|
|
||||||
#. Run the DevStack script to create the stack user:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# tools/create-stack-user.sh
|
|
||||||
|
|
||||||
#. Give ownership of the ``devstack`` directory to the stack user:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# chown -R stack:stack /root/devstack
|
|
||||||
|
|
||||||
#. Set some permissions you can use to view the DevStack screen later:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# chmod o+rwx /dev/pts/0
|
|
||||||
|
|
||||||
#. Switch to the stack user:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ su stack
|
|
||||||
|
|
||||||
#. Edit the ``local.conf`` configuration file that controls what DevStack
|
|
||||||
will deploy. Copy the example ``local.conf`` file at the end of this
|
|
||||||
section (:ref:`local.conf`):
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ vim local.conf
|
|
||||||
|
|
||||||
#. Run the stack script that will install OpenStack:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ ./stack.sh
|
|
||||||
|
|
||||||
#. When the stack script is done, you can open the screen session it
|
|
||||||
started to view all of the running OpenStack services:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ screen -r stack
|
|
||||||
|
|
||||||
#. Press ``Ctrl+A`` followed by 0 to go to the first ``screen`` window.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
- The ``stack.sh`` script takes a while to run. Perhaps you can
|
|
||||||
take this opportunity to `join the OpenStack
|
|
||||||
Foundation <https://www.openstack.org/join/>`__.
|
|
||||||
|
|
||||||
- ``Screen`` is a useful program for viewing many related services
|
|
||||||
at once. For more information, see the `GNU screen quick
|
|
||||||
reference <http://aperiodic.net/screen/quick_reference>`__.
|
|
||||||
|
|
||||||
Now that you have an OpenStack development environment, you're free to
|
|
||||||
hack around without worrying about damaging your production deployment.
|
|
||||||
:ref:`local.conf` provides a working environment for
|
|
||||||
running OpenStack Identity, Compute, Block Storage, Image service, the
|
|
||||||
OpenStack dashboard, and Object Storage as the starting point.
|
|
||||||
|
|
||||||
.. _local.conf:
|
|
||||||
|
|
||||||
local.conf
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
[[local|localrc]]
|
|
||||||
FLOATING_RANGE=192.168.1.224/27
|
|
||||||
FIXED_RANGE=10.11.12.0/24
|
|
||||||
FIXED_NETWORK_SIZE=256
|
|
||||||
FLAT_INTERFACE=eth0
|
|
||||||
ADMIN_PASSWORD=supersecret
|
|
||||||
DATABASE_PASSWORD=iheartdatabases
|
|
||||||
RABBIT_PASSWORD=flopsymopsy
|
|
||||||
SERVICE_PASSWORD=iheartksl
|
|
||||||
SERVICE_TOKEN=xyzpdqlazydog
|
|
||||||
|
|
||||||
Customizing Object Storage (Swift) Middleware
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
OpenStack Object Storage, known as swift when reading the code, is based
|
|
||||||
on the Python `Paste <http://pythonpaste.org/>`_ framework. The best
|
|
||||||
introduction to its architecture is `A Do-It-Yourself
|
|
||||||
Framework <http://pythonpaste.org/do-it-yourself-framework.html>`_.
|
|
||||||
Because of the swift project's use of this framework, you are able to
|
|
||||||
add features to a project by placing some custom code in a project's
|
|
||||||
pipeline without having to change any of the core code.
|
|
||||||
|
|
||||||
Imagine a scenario where you have public access to one of your
|
|
||||||
containers, but what you really want is to restrict access to that to a
|
|
||||||
set of IPs based on a whitelist. In this example, we'll create a piece
|
|
||||||
of middleware for swift that allows access to a container from only a
|
|
||||||
set of IP addresses, as determined by the container's metadata items.
|
|
||||||
Only those IP addresses that you explicitly whitelist using the
|
|
||||||
container's metadata will be able to access the container.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This example is for illustrative purposes only. It should not be
|
|
||||||
used as a container IP whitelist solution without further
|
|
||||||
development and extensive security testing.
|
|
||||||
|
|
||||||
When you join the screen session that ``stack.sh`` starts with
|
|
||||||
``screen -r stack``, you see a screen for each service running, which
|
|
||||||
can be a few or several, depending on how many services you configured
|
|
||||||
DevStack to run.
|
|
||||||
|
|
||||||
The asterisk * indicates which screen window you are viewing. This
|
|
||||||
example shows we are viewing the key (for keystone) screen window:
|
|
||||||
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
0$ shell 1$ key* 2$ horizon 3$ s-proxy 4$ s-object 5$ s-container 6$ s-account
|
|
||||||
|
|
||||||
The purpose of the screen windows are as follows:
|
|
||||||
|
|
||||||
|
|
||||||
``shell``
|
|
||||||
A shell where you can get some work done
|
|
||||||
|
|
||||||
``key*``
|
|
||||||
The keystone service
|
|
||||||
|
|
||||||
``horizon``
|
|
||||||
The horizon dashboard web application
|
|
||||||
|
|
||||||
``s-{name}``
|
|
||||||
The swift services
|
|
||||||
|
|
||||||
**To create the middleware and plug it in through Paste configuration:**
|
|
||||||
|
|
||||||
All of the code for OpenStack lives in ``/opt/stack``. Go to the swift
|
|
||||||
directory in the ``shell`` screen and edit your middleware module.
|
|
||||||
|
|
||||||
#. Change to the directory where Object Storage is installed:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ cd /opt/stack/swift
|
|
||||||
|
|
||||||
#. Create the ``ip_whitelist.py`` Python source code file:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ vim swift/common/middleware/ip_whitelist.py
|
|
||||||
|
|
||||||
#. Copy the code as shown below into ``ip_whitelist.py``.
|
|
||||||
The following code is a middleware example that
|
|
||||||
restricts access to a container based on IP address as explained at the
|
|
||||||
beginning of the section. Middleware passes the request on to another
|
|
||||||
application. This example uses the swift "swob" library to wrap Web
|
|
||||||
Server Gateway Interface (WSGI) requests and responses into objects for
|
|
||||||
swift to interact with. When you're done, save and close the file.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
# Copyright (c) 2014 OpenStack Foundation
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import socket
|
|
||||||
|
|
||||||
from swift.common.utils import get_logger
|
|
||||||
from swift.proxy.controllers.base import get_container_info
|
|
||||||
from swift.common.swob import Request, Response
|
|
||||||
|
|
||||||
class IPWhitelistMiddleware(object):
|
|
||||||
"""
|
|
||||||
IP Whitelist Middleware
|
|
||||||
|
|
||||||
Middleware that allows access to a container from only a set of IP
|
|
||||||
addresses as determined by the container's metadata items that start
|
|
||||||
with the prefix 'allow'. E.G. allow-dev=192.168.0.20
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app, conf, logger=None):
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
if logger:
|
|
||||||
self.logger = logger
|
|
||||||
else:
|
|
||||||
self.logger = get_logger(conf, log_route='ip_whitelist')
|
|
||||||
|
|
||||||
self.deny_message = conf.get('deny_message', "IP Denied")
|
|
||||||
self.local_ip = socket.gethostbyname(socket.gethostname())
|
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
|
||||||
"""
|
|
||||||
WSGI entry point.
|
|
||||||
Wraps env in swob.Request object and passes it down.
|
|
||||||
|
|
||||||
:param env: WSGI environment dictionary
|
|
||||||
:param start_response: WSGI callable
|
|
||||||
"""
|
|
||||||
req = Request(env)
|
|
||||||
|
|
||||||
try:
|
|
||||||
version, account, container, obj = req.split_path(1, 4, True)
|
|
||||||
except ValueError:
|
|
||||||
return self.app(env, start_response)
|
|
||||||
|
|
||||||
container_info = get_container_info(
|
|
||||||
req.environ, self.app, swift_source='IPWhitelistMiddleware')
|
|
||||||
|
|
||||||
remote_ip = env['REMOTE_ADDR']
|
|
||||||
self.logger.debug("Remote IP: %(remote_ip)s",
|
|
||||||
{'remote_ip': remote_ip})
|
|
||||||
|
|
||||||
meta = container_info['meta']
|
|
||||||
allow = {k:v for k,v in meta.iteritems() if k.startswith('allow')}
|
|
||||||
allow_ips = set(allow.values())
|
|
||||||
allow_ips.add(self.local_ip)
|
|
||||||
self.logger.debug("Allow IPs: %(allow_ips)s",
|
|
||||||
{'allow_ips': allow_ips})
|
|
||||||
|
|
||||||
if remote_ip in allow_ips:
|
|
||||||
return self.app(env, start_response)
|
|
||||||
else:
|
|
||||||
self.logger.debug(
|
|
||||||
"IP %(remote_ip)s denied access to Account=%(account)s "
|
|
||||||
"Container=%(container)s. Not in %(allow_ips)s", locals())
|
|
||||||
return Response(
|
|
||||||
status=403,
|
|
||||||
body=self.deny_message,
|
|
||||||
request=req)(env, start_response)
|
|
||||||
|
|
||||||
|
|
||||||
def filter_factory(global_conf, **local_conf):
|
|
||||||
"""
|
|
||||||
paste.deploy app factory for creating WSGI proxy apps.
|
|
||||||
"""
|
|
||||||
conf = global_conf.copy()
|
|
||||||
conf.update(local_conf)
|
|
||||||
|
|
||||||
def ip_whitelist(app):
|
|
||||||
return IPWhitelistMiddleware(app, conf)
|
|
||||||
return ip_whitelist
|
|
||||||
|
|
||||||
|
|
||||||
There is a lot of useful information in ``env`` and ``conf`` that you
|
|
||||||
can use to decide what to do with the request. To find out more about
|
|
||||||
what properties are available, you can insert the following log
|
|
||||||
statement into the ``__init__`` method:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
self.logger.debug("conf = %(conf)s", locals())
|
|
||||||
|
|
||||||
|
|
||||||
and the following log statement into the ``__call__`` method:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
self.logger.debug("env = %(env)s", locals())
|
|
||||||
|
|
||||||
#. To plug this middleware into the swift Paste pipeline, you edit one
|
|
||||||
configuration file, ``/etc/swift/proxy-server.conf``:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ vim /etc/swift/proxy-server.conf
|
|
||||||
|
|
||||||
#. Find the ``[filter:ratelimit]`` section in
|
|
||||||
``/etc/swift/proxy-server.conf``, and copy in the following
|
|
||||||
configuration section after it:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[filter:ip_whitelist]
|
|
||||||
paste.filter_factory = swift.common.middleware.ip_whitelist:filter_factory
|
|
||||||
# You can override the default log routing for this filter here:
|
|
||||||
# set log_name = ratelimit
|
|
||||||
# set log_facility = LOG_LOCAL0
|
|
||||||
# set log_level = INFO
|
|
||||||
# set log_headers = False
|
|
||||||
# set log_address = /dev/log
|
|
||||||
deny_message = You shall not pass!
|
|
||||||
|
|
||||||
#. Find the ``[pipeline:main]`` section in
|
|
||||||
``/etc/swift/proxy-server.conf``, and add ``ip_whitelist`` after
|
|
||||||
ratelimit to the list like so. When you're done, save and close the
|
|
||||||
file:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = catch_errors gatekeeper healthcheck proxy-logging cache bulk tempurl ratelimit ip_whitelist ...
|
|
||||||
|
|
||||||
#. Restart the ``swift proxy`` service to make swift use your middleware.
|
|
||||||
Start by switching to the ``swift-proxy`` screen:
|
|
||||||
|
|
||||||
#. Press **Ctrl+A** followed by 3.
|
|
||||||
|
|
||||||
#. Press **Ctrl+C** to kill the service.
|
|
||||||
|
|
||||||
#. Press Up Arrow to bring up the last command.
|
|
||||||
|
|
||||||
#. Press Enter to run it.
|
|
||||||
|
|
||||||
#. Test your middleware with the ``swift`` CLI. Start by switching to the
|
|
||||||
shell screen and finish by switching back to the ``swift-proxy`` screen
|
|
||||||
to check the log output:
|
|
||||||
|
|
||||||
#. Press **Ctrl+A** followed by 0.
|
|
||||||
|
|
||||||
#. Make sure you're in the ``devstack`` directory:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ cd /root/devstack
|
|
||||||
|
|
||||||
#. Source openrc to set up your environment variables for the CLI:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ . openrc
|
|
||||||
|
|
||||||
#. Create a container called ``middleware-test``:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ swift post middleware-test
|
|
||||||
|
|
||||||
#. Press **Ctrl+A** followed by 3 to check the log output.
|
|
||||||
|
|
||||||
#. Among the log statements you'll see the lines:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
proxy-server Remote IP: my.instance.ip.address (txn: ...)
|
|
||||||
proxy-server Allow IPs: set(['my.instance.ip.address']) (txn: ...)
|
|
||||||
|
|
||||||
These two statements are produced by our middleware and show that the
|
|
||||||
request was sent from our DevStack instance and was allowed.
|
|
||||||
|
|
||||||
#. Test the middleware from outside DevStack on a remote machine that has
|
|
||||||
access to your DevStack instance:
|
|
||||||
|
|
||||||
#. Install the ``keystone`` and ``swift`` clients on your local machine:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# pip install python-keystoneclient python-swiftclient
|
|
||||||
|
|
||||||
#. Attempt to list the objects in the ``middleware-test`` container:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ swift --os-auth-url=http://my.instance.ip.address:5000/v2.0/ \
|
|
||||||
--os-region-name=RegionOne --os-username=demo:demo \
|
|
||||||
--os-password=devstack list middleware-test
|
|
||||||
Container GET failed: http://my.instance.ip.address:8080/v1/AUTH_.../
|
|
||||||
middleware-test?format=json 403 Forbidden You shall not pass!
|
|
||||||
|
|
||||||
#. Press **Ctrl+A** followed by 3 to check the log output. Look at the
|
|
||||||
swift log statements again, and among the log statements, you'll see the
|
|
||||||
lines:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
proxy-server Authorizing from an overriding middleware (i.e: tempurl) (txn: ...)
|
|
||||||
proxy-server ... IPWhitelistMiddleware
|
|
||||||
proxy-server Remote IP: my.local.ip.address (txn: ...)
|
|
||||||
proxy-server Allow IPs: set(['my.instance.ip.address']) (txn: ...)
|
|
||||||
proxy-server IP my.local.ip.address denied access to Account=AUTH_... \
|
|
||||||
Container=None. Not in set(['my.instance.ip.address']) (txn: ...)
|
|
||||||
|
|
||||||
Here we can see that the request was denied because the remote IP
|
|
||||||
address wasn't in the set of allowed IPs.
|
|
||||||
|
|
||||||
#. Back in your DevStack instance on the shell screen, add some metadata to
|
|
||||||
your container to allow the request from the remote machine:
|
|
||||||
|
|
||||||
#. Press **Ctrl+A** followed by 0.
|
|
||||||
|
|
||||||
#. Add metadata to the container to allow the IP:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ swift post --meta allow-dev:my.local.ip.address middleware-test
|
|
||||||
|
|
||||||
#. Now try the command from Step 10 again and it succeeds. There are no
|
|
||||||
objects in the container, so there is nothing to list; however, there is
|
|
||||||
also no error to report.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Functional testing like this is not a replacement for proper unit
|
|
||||||
and integration testing, but it serves to get you started.
|
|
||||||
|
|
||||||
You can follow a similar pattern in other projects that use the Python
|
|
||||||
Paste framework. Simply create a middleware module and plug it in
|
|
||||||
through configuration. The middleware runs in sequence as part of that
|
|
||||||
project's pipeline and can call out to other services as necessary. No
|
|
||||||
project core code is touched. Look for a ``pipeline`` value in the
|
|
||||||
project's ``conf`` or ``ini`` configuration files in ``/etc/<project>``
|
|
||||||
to identify projects that use Paste.
|
|
||||||
|
|
||||||
When your middleware is done, we encourage you to open source it and let
|
|
||||||
the community know on the OpenStack mailing list. Perhaps others need
|
|
||||||
the same functionality. They can use your code, provide feedback, and
|
|
||||||
possibly contribute. If enough support exists for it, perhaps you can
|
|
||||||
propose that it be added to the official swift
|
|
||||||
`middleware <https://git.openstack.org/cgit/openstack/swift/tree/swift/common/middleware>`_.
|
|
||||||
|
|
||||||
Customizing the OpenStack Compute (nova) Scheduler
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Many OpenStack projects allow for customization of specific features
|
|
||||||
using a driver architecture. You can write a driver that conforms to a
|
|
||||||
particular interface and plug it in through configuration. For example,
|
|
||||||
you can easily plug in a new scheduler for Compute. The existing
|
|
||||||
schedulers for Compute are feature full and well documented at
|
|
||||||
`Scheduling <http://docs.openstack.org/liberty/config-reference/content/section_compute-scheduler.html>`_.
|
|
||||||
However, depending on your user's use cases, the existing schedulers
|
|
||||||
might not meet your requirements. You might need to create a new
|
|
||||||
scheduler.
|
|
||||||
|
|
||||||
To create a scheduler, you must inherit from the class
|
|
||||||
``nova.scheduler.driver.Scheduler``. Of the five methods that you can
|
|
||||||
override, you *must* override the two methods marked with an asterisk
|
|
||||||
(\*) below:
|
|
||||||
|
|
||||||
- ``update_service_capabilities``
|
|
||||||
|
|
||||||
- ``hosts_up``
|
|
||||||
|
|
||||||
- ``group_hosts``
|
|
||||||
|
|
||||||
- \* ``schedule_run_instance``
|
|
||||||
|
|
||||||
- \* ``select_destinations``
|
|
||||||
|
|
||||||
To demonstrate customizing OpenStack, we'll create an example of a
|
|
||||||
Compute scheduler that randomly places an instance on a subset of hosts,
|
|
||||||
depending on the originating IP address of the request and the prefix of
|
|
||||||
the hostname. Such an example could be useful when you have a group of
|
|
||||||
users on a subnet and you want all of their instances to start within
|
|
||||||
some subset of your hosts.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This example is for illustrative purposes only. It should not be
|
|
||||||
used as a scheduler for Compute without further development and
|
|
||||||
testing.
|
|
||||||
|
|
||||||
When you join the screen session that ``stack.sh`` starts with
|
|
||||||
``screen -r stack``, you are greeted with many screen windows:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
0$ shell* 1$ key 2$ horizon ... 9$ n-api ... 14$ n-sch ...
|
|
||||||
|
|
||||||
|
|
||||||
``shell``
|
|
||||||
A shell where you can get some work done
|
|
||||||
|
|
||||||
``key``
|
|
||||||
The keystone service
|
|
||||||
|
|
||||||
``horizon``
|
|
||||||
The horizon dashboard web application
|
|
||||||
|
|
||||||
``n-{name}``
|
|
||||||
The nova services
|
|
||||||
|
|
||||||
``n-sch``
|
|
||||||
The nova scheduler service
|
|
||||||
|
|
||||||
**To create the scheduler and plug it in through configuration**
|
|
||||||
|
|
||||||
#. The code for OpenStack lives in ``/opt/stack``, so go to the ``nova``
|
|
||||||
directory and edit your scheduler module. Change to the directory where
|
|
||||||
``nova`` is installed:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ cd /opt/stack/nova
|
|
||||||
|
|
||||||
#. Create the ``ip_scheduler.py`` Python source code file:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ vim nova/scheduler/ip_scheduler.py
|
|
||||||
|
|
||||||
#. The code shown below is a driver that will
|
|
||||||
schedule servers to hosts based on IP address as explained at the
|
|
||||||
beginning of the section. Copy the code into ``ip_scheduler.py``. When
|
|
||||||
you're done, save and close the file.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
# Copyright (c) 2014 OpenStack Foundation
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
IP Scheduler implementation
|
|
||||||
"""
|
|
||||||
|
|
||||||
import random
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from nova.compute import rpcapi as compute_rpcapi
|
|
||||||
from nova import exception
|
|
||||||
from nova.openstack.common import log as logging
|
|
||||||
from nova.openstack.common.gettextutils import _
|
|
||||||
from nova.scheduler import driver
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CONF.import_opt('compute_topic', 'nova.compute.rpcapi')
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class IPScheduler(driver.Scheduler):
|
|
||||||
"""
|
|
||||||
Implements Scheduler as a random node selector based on
|
|
||||||
IP address and hostname prefix.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(IPScheduler, self).__init__(*args, **kwargs)
|
|
||||||
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
|
|
||||||
|
|
||||||
def _filter_hosts(self, request_spec, hosts, filter_properties,
|
|
||||||
hostname_prefix):
|
|
||||||
"""Filter a list of hosts based on hostname prefix."""
|
|
||||||
|
|
||||||
hosts = [host for host in hosts if host.startswith(hostname_prefix)]
|
|
||||||
return hosts
|
|
||||||
|
|
||||||
def _schedule(self, context, topic, request_spec, filter_properties):
|
|
||||||
"""Picks a host that is up at random."""
|
|
||||||
|
|
||||||
elevated = context.elevated()
|
|
||||||
hosts = self.hosts_up(elevated, topic)
|
|
||||||
if not hosts:
|
|
||||||
msg = _("Is the appropriate service running?")
|
|
||||||
raise exception.NoValidHost(reason=msg)
|
|
||||||
|
|
||||||
remote_ip = context.remote_address
|
|
||||||
|
|
||||||
if remote_ip.startswith('10.1'):
|
|
||||||
hostname_prefix = 'doc'
|
|
||||||
elif remote_ip.startswith('10.2'):
|
|
||||||
hostname_prefix = 'ops'
|
|
||||||
else:
|
|
||||||
hostname_prefix = 'dev'
|
|
||||||
|
|
||||||
hosts = self._filter_hosts(request_spec, hosts, filter_properties,
|
|
||||||
hostname_prefix)
|
|
||||||
if not hosts:
|
|
||||||
msg = _("Could not find another compute")
|
|
||||||
raise exception.NoValidHost(reason=msg)
|
|
||||||
|
|
||||||
host = random.choice(hosts)
|
|
||||||
LOG.debug("Request from %(remote_ip)s scheduled to %(host)s" % locals())
|
|
||||||
|
|
||||||
return host
|
|
||||||
|
|
||||||
def select_destinations(self, context, request_spec, filter_properties):
|
|
||||||
"""Selects random destinations."""
|
|
||||||
num_instances = request_spec['num_instances']
|
|
||||||
# NOTE(timello): Returns a list of dicts with 'host', 'nodename' and
|
|
||||||
# 'limits' as keys for compatibility with filter_scheduler.
|
|
||||||
dests = []
|
|
||||||
for i in range(num_instances):
|
|
||||||
host = self._schedule(context, CONF.compute_topic,
|
|
||||||
request_spec, filter_properties)
|
|
||||||
host_state = dict(host=host, nodename=None, limits=None)
|
|
||||||
dests.append(host_state)
|
|
||||||
|
|
||||||
if len(dests) < num_instances:
|
|
||||||
raise exception.NoValidHost(reason='')
|
|
||||||
return dests
|
|
||||||
|
|
||||||
def schedule_run_instance(self, context, request_spec,
|
|
||||||
admin_password, injected_files,
|
|
||||||
requested_networks, is_first_time,
|
|
||||||
filter_properties, legacy_bdm_in_spec):
|
|
||||||
"""Create and run an instance or instances."""
|
|
||||||
instance_uuids = request_spec.get('instance_uuids')
|
|
||||||
for num, instance_uuid in enumerate(instance_uuids):
|
|
||||||
request_spec['instance_properties']['launch_index'] = num
|
|
||||||
try:
|
|
||||||
host = self._schedule(context, CONF.compute_topic,
|
|
||||||
request_spec, filter_properties)
|
|
||||||
updated_instance = driver.instance_update_db(context,
|
|
||||||
instance_uuid)
|
|
||||||
self.compute_rpcapi.run_instance(context,
|
|
||||||
instance=updated_instance, host=host,
|
|
||||||
requested_networks=requested_networks,
|
|
||||||
injected_files=injected_files,
|
|
||||||
admin_password=admin_password,
|
|
||||||
is_first_time=is_first_time,
|
|
||||||
request_spec=request_spec,
|
|
||||||
filter_properties=filter_properties,
|
|
||||||
legacy_bdm_in_spec=legacy_bdm_in_spec)
|
|
||||||
except Exception as ex:
|
|
||||||
# NOTE(vish): we don't reraise the exception here to make sure
|
|
||||||
# that all instances in the request get set to
|
|
||||||
# error properly
|
|
||||||
driver.handle_schedule_error(context, ex, instance_uuid,
|
|
||||||
request_spec)
|
|
||||||
|
|
||||||
|
|
||||||
There is a lot of useful information in ``context``, ``request_spec``,
|
|
||||||
and ``filter_properties`` that you can use to decide where to schedule
|
|
||||||
the instance. To find out more about what properties are available, you
|
|
||||||
can insert the following log statements into the
|
|
||||||
``schedule_run_instance`` method of the scheduler above:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
LOG.debug("context = %(context)s" % {'context': context.__dict__})
|
|
||||||
LOG.debug("request_spec = %(request_spec)s" % locals())
|
|
||||||
LOG.debug("filter_properties = %(filter_properties)s" % locals())
|
|
||||||
|
|
||||||
#. To plug this scheduler into nova, edit one configuration file,
|
|
||||||
``/etc/nova/nova.conf``:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ vim /etc/nova/nova.conf
|
|
||||||
|
|
||||||
#. Find the ``scheduler_driver`` config and change it like so:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
scheduler_driver=nova.scheduler.ip_scheduler.IPScheduler
|
|
||||||
|
|
||||||
#. Restart the nova scheduler service to make nova use your scheduler.
|
|
||||||
Start by switching to the ``n-sch`` screen:
|
|
||||||
|
|
||||||
#. Press **Ctrl+A** followed by 9.
|
|
||||||
|
|
||||||
#. Press **Ctrl+A** followed by N until you reach the ``n-sch`` screen.
|
|
||||||
|
|
||||||
#. Press **Ctrl+C** to kill the service.
|
|
||||||
|
|
||||||
#. Press Up Arrow to bring up the last command.
|
|
||||||
|
|
||||||
#. Press Enter to run it.
|
|
||||||
|
|
||||||
#. Test your scheduler with the nova CLI. Start by switching to the
|
|
||||||
``shell`` screen and finish by switching back to the ``n-sch`` screen to
|
|
||||||
check the log output:
|
|
||||||
|
|
||||||
#. Press **Ctrl+A** followed by 0.
|
|
||||||
|
|
||||||
#. Make sure you're in the ``devstack`` directory:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ cd /root/devstack
|
|
||||||
|
|
||||||
#. Source ``openrc`` to set up your environment variables for the CLI:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ . openrc
|
|
||||||
|
|
||||||
#. Put the image ID for the only installed image into an environment
|
|
||||||
variable:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ IMAGE_ID=`nova image-list | egrep cirros | egrep -v "kernel|ramdisk" | awk '{print $2}'`
|
|
||||||
|
|
||||||
#. Boot a test server:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ nova boot --flavor 1 --image $IMAGE_ID scheduler-test
|
|
||||||
|
|
||||||
#. Switch back to the ``n-sch`` screen. Among the log statements, you'll
|
|
||||||
see the line:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
2014-01-23 19:57:47.262 DEBUG nova.scheduler.ip_scheduler \
|
|
||||||
[req-... demo demo] Request from 162.242.221.84 \
|
|
||||||
scheduled to devstack-havana \
|
|
||||||
_schedule /opt/stack/nova/nova/scheduler/ip_scheduler.py:76
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Functional testing like this is not a replacement for proper unit
|
|
||||||
and integration testing, but it serves to get you started.
|
|
||||||
|
|
||||||
A similar pattern can be followed in other projects that use the driver
|
|
||||||
architecture. Simply create a module and class that conform to the
|
|
||||||
driver interface and plug it in through configuration. Your code runs
|
|
||||||
when that feature is used and can call out to other services as
|
|
||||||
necessary. No project core code is touched. Look for a "driver" value in
|
|
||||||
the project's ``.conf`` configuration files in ``/etc/<project>`` to
|
|
||||||
identify projects that use a driver architecture.
|
|
||||||
|
|
||||||
When your scheduler is done, we encourage you to open source it and let
|
|
||||||
the community know on the OpenStack mailing list. Perhaps others need
|
|
||||||
the same functionality. They can use your code, provide feedback, and
|
|
||||||
possibly contribute. If enough support exists for it, perhaps you can
|
|
||||||
propose that it be added to the official Compute
|
|
||||||
`schedulers <https://git.openstack.org/cgit/openstack/nova/tree/nova/scheduler>`_.
|
|
||||||
|
|
||||||
Customizing the Dashboard (Horizon)
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The dashboard is based on the Python
|
|
||||||
`Django <https://www.djangoproject.com/>`_ web application framework.
|
|
||||||
The best guide to customizing it has already been written and can be
|
|
||||||
found at `Building on
|
|
||||||
Horizon <http://docs.openstack.org/developer/horizon/topics/tutorial.html>`_.
|
|
||||||
|
|
||||||
Conclusion
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
When operating an OpenStack cloud, you may discover that your users can
|
|
||||||
be quite demanding. If OpenStack doesn't do what your users need, it may
|
|
||||||
be up to you to fulfill those requirements. This chapter provided you
|
|
||||||
with some options for customization and gave you the tools you need to
|
|
||||||
get started.
|
|
||||||
|
309
doc/ops-guide/source/ops_customize_compute.rst
Normal file
309
doc/ops-guide/source/ops_customize_compute.rst
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
==================================================
|
||||||
|
Customizing the OpenStack Compute (nova) Scheduler
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
Many OpenStack projects allow for customization of specific features
|
||||||
|
using a driver architecture. You can write a driver that conforms to a
|
||||||
|
particular interface and plug it in through configuration. For example,
|
||||||
|
you can easily plug in a new scheduler for Compute. The existing
|
||||||
|
schedulers for Compute are feature full and well documented at `Scheduling
|
||||||
|
<http://docs.openstack.org/mitaka/config-reference/compute/scheduler.html>`_.
|
||||||
|
However, depending on your user's use cases, the existing schedulers
|
||||||
|
might not meet your requirements. You might need to create a new scheduler.
|
||||||
|
|
||||||
|
To create a scheduler, you must inherit from the class
|
||||||
|
``nova.scheduler.driver.Scheduler``. Of the five methods that you can
|
||||||
|
override, you *must* override the two methods marked with an asterisk
|
||||||
|
(\*) below:
|
||||||
|
|
||||||
|
- ``update_service_capabilities``
|
||||||
|
|
||||||
|
- ``hosts_up``
|
||||||
|
|
||||||
|
- ``group_hosts``
|
||||||
|
|
||||||
|
- \* ``schedule_run_instance``
|
||||||
|
|
||||||
|
- \* ``select_destinations``
|
||||||
|
|
||||||
|
To demonstrate customizing OpenStack, we'll create an example of a
|
||||||
|
Compute scheduler that randomly places an instance on a subset of hosts,
|
||||||
|
depending on the originating IP address of the request and the prefix of
|
||||||
|
the hostname. Such an example could be useful when you have a group of
|
||||||
|
users on a subnet and you want all of their instances to start within
|
||||||
|
some subset of your hosts.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This example is for illustrative purposes only. It should not be
|
||||||
|
used as a scheduler for Compute without further development and
|
||||||
|
testing.
|
||||||
|
|
||||||
|
When you join the screen session that ``stack.sh`` starts with
|
||||||
|
``screen -r stack``, you are greeted with many screen windows:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
0$ shell* 1$ key 2$ horizon ... 9$ n-api ... 14$ n-sch ...
|
||||||
|
|
||||||
|
|
||||||
|
``shell``
|
||||||
|
A shell where you can get some work done
|
||||||
|
|
||||||
|
``key``
|
||||||
|
The keystone service
|
||||||
|
|
||||||
|
``horizon``
|
||||||
|
The horizon dashboard web application
|
||||||
|
|
||||||
|
``n-{name}``
|
||||||
|
The nova services
|
||||||
|
|
||||||
|
``n-sch``
|
||||||
|
The nova scheduler service
|
||||||
|
|
||||||
|
**To create the scheduler and plug it in through configuration**
|
||||||
|
|
||||||
|
#. The code for OpenStack lives in ``/opt/stack``, so go to the ``nova``
|
||||||
|
directory and edit your scheduler module. Change to the directory where
|
||||||
|
``nova`` is installed:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ cd /opt/stack/nova
|
||||||
|
|
||||||
|
#. Create the ``ip_scheduler.py`` Python source code file:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ vim nova/scheduler/ip_scheduler.py
|
||||||
|
|
||||||
|
#. The code shown below is a driver that will
|
||||||
|
schedule servers to hosts based on IP address as explained at the
|
||||||
|
beginning of the section. Copy the code into ``ip_scheduler.py``. When
|
||||||
|
you are done, save and close the file.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright (c) 2014 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
IP Scheduler implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from nova.compute import rpcapi as compute_rpcapi
|
||||||
|
from nova import exception
|
||||||
|
from nova.openstack.common import log as logging
|
||||||
|
from nova.openstack.common.gettextutils import _
|
||||||
|
from nova.scheduler import driver
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.import_opt('compute_topic', 'nova.compute.rpcapi')
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class IPScheduler(driver.Scheduler):
|
||||||
|
"""
|
||||||
|
Implements Scheduler as a random node selector based on
|
||||||
|
IP address and hostname prefix.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(IPScheduler, self).__init__(*args, **kwargs)
|
||||||
|
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
|
||||||
|
|
||||||
|
def _filter_hosts(self, request_spec, hosts, filter_properties,
|
||||||
|
hostname_prefix):
|
||||||
|
"""Filter a list of hosts based on hostname prefix."""
|
||||||
|
|
||||||
|
hosts = [host for host in hosts if host.startswith(hostname_prefix)]
|
||||||
|
return hosts
|
||||||
|
|
||||||
|
def _schedule(self, context, topic, request_spec, filter_properties):
|
||||||
|
"""Picks a host that is up at random."""
|
||||||
|
|
||||||
|
elevated = context.elevated()
|
||||||
|
hosts = self.hosts_up(elevated, topic)
|
||||||
|
if not hosts:
|
||||||
|
msg = _("Is the appropriate service running?")
|
||||||
|
raise exception.NoValidHost(reason=msg)
|
||||||
|
|
||||||
|
remote_ip = context.remote_address
|
||||||
|
|
||||||
|
if remote_ip.startswith('10.1'):
|
||||||
|
hostname_prefix = 'doc'
|
||||||
|
elif remote_ip.startswith('10.2'):
|
||||||
|
hostname_prefix = 'ops'
|
||||||
|
else:
|
||||||
|
hostname_prefix = 'dev'
|
||||||
|
|
||||||
|
hosts = self._filter_hosts(request_spec, hosts, filter_properties,
|
||||||
|
hostname_prefix)
|
||||||
|
if not hosts:
|
||||||
|
msg = _("Could not find another compute")
|
||||||
|
raise exception.NoValidHost(reason=msg)
|
||||||
|
|
||||||
|
host = random.choice(hosts)
|
||||||
|
LOG.debug("Request from %(remote_ip)s scheduled to %(host)s" % locals())
|
||||||
|
|
||||||
|
return host
|
||||||
|
|
||||||
|
def select_destinations(self, context, request_spec, filter_properties):
|
||||||
|
"""Selects random destinations."""
|
||||||
|
num_instances = request_spec['num_instances']
|
||||||
|
# NOTE(timello): Returns a list of dicts with 'host', 'nodename' and
|
||||||
|
# 'limits' as keys for compatibility with filter_scheduler.
|
||||||
|
dests = []
|
||||||
|
for i in range(num_instances):
|
||||||
|
host = self._schedule(context, CONF.compute_topic,
|
||||||
|
request_spec, filter_properties)
|
||||||
|
host_state = dict(host=host, nodename=None, limits=None)
|
||||||
|
dests.append(host_state)
|
||||||
|
|
||||||
|
if len(dests) < num_instances:
|
||||||
|
raise exception.NoValidHost(reason='')
|
||||||
|
return dests
|
||||||
|
|
||||||
|
def schedule_run_instance(self, context, request_spec,
|
||||||
|
admin_password, injected_files,
|
||||||
|
requested_networks, is_first_time,
|
||||||
|
filter_properties, legacy_bdm_in_spec):
|
||||||
|
"""Create and run an instance or instances."""
|
||||||
|
instance_uuids = request_spec.get('instance_uuids')
|
||||||
|
for num, instance_uuid in enumerate(instance_uuids):
|
||||||
|
request_spec['instance_properties']['launch_index'] = num
|
||||||
|
try:
|
||||||
|
host = self._schedule(context, CONF.compute_topic,
|
||||||
|
request_spec, filter_properties)
|
||||||
|
updated_instance = driver.instance_update_db(context,
|
||||||
|
instance_uuid)
|
||||||
|
self.compute_rpcapi.run_instance(context,
|
||||||
|
instance=updated_instance, host=host,
|
||||||
|
requested_networks=requested_networks,
|
||||||
|
injected_files=injected_files,
|
||||||
|
admin_password=admin_password,
|
||||||
|
is_first_time=is_first_time,
|
||||||
|
request_spec=request_spec,
|
||||||
|
filter_properties=filter_properties,
|
||||||
|
legacy_bdm_in_spec=legacy_bdm_in_spec)
|
||||||
|
except Exception as ex:
|
||||||
|
# NOTE(vish): we don't reraise the exception here to make sure
|
||||||
|
# that all instances in the request get set to
|
||||||
|
# error properly
|
||||||
|
driver.handle_schedule_error(context, ex, instance_uuid,
|
||||||
|
request_spec)
|
||||||
|
|
||||||
|
There is a lot of useful information in ``context``, ``request_spec``,
|
||||||
|
and ``filter_properties`` that you can use to decide where to schedule
|
||||||
|
the instance. To find out more about what properties are available, you
|
||||||
|
can insert the following log statements into the
|
||||||
|
``schedule_run_instance`` method of the scheduler above:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
LOG.debug("context = %(context)s" % {'context': context.__dict__})
|
||||||
|
LOG.debug("request_spec = %(request_spec)s" % locals())
|
||||||
|
LOG.debug("filter_properties = %(filter_properties)s" % locals())
|
||||||
|
|
||||||
|
#. To plug this scheduler into nova, edit one configuration file,
|
||||||
|
``/etc/nova/nova.conf``:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ vim /etc/nova/nova.conf
|
||||||
|
|
||||||
|
#. Find the ``scheduler_driver`` config and change it like so:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
scheduler_driver=nova.scheduler.ip_scheduler.IPScheduler
|
||||||
|
|
||||||
|
#. Restart the nova scheduler service to make nova use your scheduler.
|
||||||
|
Start by switching to the ``n-sch`` screen:
|
||||||
|
|
||||||
|
#. Press **Ctrl+A** followed by **9**.
|
||||||
|
|
||||||
|
#. Press **Ctrl+A** followed by **N** until you reach the ``n-sch`` screen.
|
||||||
|
|
||||||
|
#. Press **Ctrl+C** to kill the service.
|
||||||
|
|
||||||
|
#. Press **Up Arrow** to bring up the last command.
|
||||||
|
|
||||||
|
#. Press **Enter** to run it.
|
||||||
|
|
||||||
|
#. Test your scheduler with the nova CLI. Start by switching to the
|
||||||
|
``shell`` screen and finish by switching back to the ``n-sch`` screen to
|
||||||
|
check the log output:
|
||||||
|
|
||||||
|
#. Press **Ctrl+A** followed by **0**.
|
||||||
|
|
||||||
|
#. Make sure you are in the ``devstack`` directory:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ cd /root/devstack
|
||||||
|
|
||||||
|
#. Source ``openrc`` to set up your environment variables for the CLI:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ . openrc
|
||||||
|
|
||||||
|
#. Put the image ID for the only installed image into an environment
|
||||||
|
variable:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ IMAGE_ID=`nova image-list | egrep cirros | egrep -v "kernel|ramdisk" | awk '{print $2}'`
|
||||||
|
|
||||||
|
#. Boot a test server:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ nova boot --flavor 1 --image $IMAGE_ID scheduler-test
|
||||||
|
|
||||||
|
#. Switch back to the ``n-sch`` screen. Among the log statements, you'll
|
||||||
|
see the line:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
2014-01-23 19:57:47.262 DEBUG nova.scheduler.ip_scheduler
|
||||||
|
[req-... demo demo] Request from xx.xx.xx.xx scheduled to devstack-havana
|
||||||
|
_schedule /opt/stack/nova/nova/scheduler/ip_scheduler.py:76
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Functional testing like this is not a replacement for proper unit
|
||||||
|
and integration testing, but it serves to get you started.
|
||||||
|
|
||||||
|
A similar pattern can be followed in other projects that use the driver
|
||||||
|
architecture. Simply create a module and class that conform to the
|
||||||
|
driver interface and plug it in through configuration. Your code runs
|
||||||
|
when that feature is used and can call out to other services as
|
||||||
|
necessary. No project core code is touched. Look for a "driver" value in
|
||||||
|
the project's ``.conf`` configuration files in ``/etc/<project>`` to
|
||||||
|
identify projects that use a driver architecture.
|
||||||
|
|
||||||
|
When your scheduler is done, we encourage you to open source it and let
|
||||||
|
the community know on the OpenStack mailing list. Perhaps others need
|
||||||
|
the same functionality. They can use your code, provide feedback, and
|
||||||
|
possibly contribute. If enough support exists for it, perhaps you can
|
||||||
|
propose that it be added to the official Compute
|
||||||
|
`schedulers <https://git.openstack.org/cgit/openstack/nova/tree/nova/scheduler>`_.
|
9
doc/ops-guide/source/ops_customize_conclusion.rst
Normal file
9
doc/ops-guide/source/ops_customize_conclusion.rst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
==========
|
||||||
|
Conclusion
|
||||||
|
==========
|
||||||
|
|
||||||
|
When operating an OpenStack cloud, you may discover that your users can
|
||||||
|
be quite demanding. If OpenStack doesn't do what your users need, it may
|
||||||
|
be up to you to fulfill those requirements. This chapter provided you
|
||||||
|
with some options for customization and gave you the tools you need to
|
||||||
|
get started.
|
9
doc/ops-guide/source/ops_customize_dashboard.rst
Normal file
9
doc/ops-guide/source/ops_customize_dashboard.rst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
===================================
|
||||||
|
Customizing the Dashboard (Horizon)
|
||||||
|
===================================
|
||||||
|
|
||||||
|
The dashboard is based on the Python
|
||||||
|
`Django <https://www.djangoproject.com/>`_ web application framework.
|
||||||
|
The best guide to customizing it has already been written and can be
|
||||||
|
found at `Building a Dashboard using Horizon
|
||||||
|
<http://docs.openstack.org/developer/horizon/topics/tutorial.html>`_.
|
145
doc/ops-guide/source/ops_customize_development.rst
Normal file
145
doc/ops-guide/source/ops_customize_development.rst
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
===========================================
|
||||||
|
Create an OpenStack Development Environment
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
To create a development environment, you can use DevStack. DevStack is
|
||||||
|
essentially a collection of shell scripts and configuration files that
|
||||||
|
builds an OpenStack development environment for you. You use it to
|
||||||
|
create such an environment for developing a new feature.
|
||||||
|
|
||||||
|
You can find all of the documentation at the
|
||||||
|
`DevStack <http://docs.openstack.org/developer/devstack/>`_ website.
|
||||||
|
|
||||||
|
**To run DevStack on an instance in your OpenStack cloud:**
|
||||||
|
|
||||||
|
#. Boot an instance from the dashboard or the nova command-line interface
|
||||||
|
(CLI) with the following parameters:
|
||||||
|
|
||||||
|
- Name: devstack
|
||||||
|
|
||||||
|
- Image: Ubuntu 14.04 LTS
|
||||||
|
|
||||||
|
- Memory Size: 4 GB RAM
|
||||||
|
|
||||||
|
- Disk Size: minimum 5 GB
|
||||||
|
|
||||||
|
If you are using the ``nova`` client, specify :option:`--flavor 3` for the
|
||||||
|
:command:`nova boot` command to get adequate memory and disk sizes.
|
||||||
|
|
||||||
|
#. Log in and set up DevStack. Here's an example of the commands you can
|
||||||
|
use to set up DevStack on a virtual machine:
|
||||||
|
|
||||||
|
#. Log in to the instance:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ ssh username@my.instance.ip.address
|
||||||
|
|
||||||
|
#. Update the virtual machine's operating system:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# apt-get update
|
||||||
|
|
||||||
|
#. Install git:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# apt-get install git
|
||||||
|
|
||||||
|
#. Clone the ``devstack`` repository:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ git clone https://git.openstack.org/openstack-dev/devstack
|
||||||
|
|
||||||
|
#. Change to the ``devstack`` repository:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ cd devstack
|
||||||
|
|
||||||
|
#. (Optional) If you've logged in to your instance as the root user, you
|
||||||
|
must create a "stack" user; otherwise you'll run into permission issues.
|
||||||
|
If you've logged in as a user other than root, you can skip these steps:
|
||||||
|
|
||||||
|
#. Run the DevStack script to create the stack user:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# tools/create-stack-user.sh
|
||||||
|
|
||||||
|
#. Give ownership of the ``devstack`` directory to the stack user:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# chown -R stack:stack /root/devstack
|
||||||
|
|
||||||
|
#. Set some permissions you can use to view the DevStack screen later:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# chmod o+rwx /dev/pts/0
|
||||||
|
|
||||||
|
#. Switch to the stack user:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ su stack
|
||||||
|
|
||||||
|
#. Edit the ``local.conf`` configuration file that controls what DevStack
|
||||||
|
will deploy. Copy the example ``local.conf`` file at the end of this
|
||||||
|
section (:ref:`local.conf`):
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ vim local.conf
|
||||||
|
|
||||||
|
#. Run the stack script that will install OpenStack:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ ./stack.sh
|
||||||
|
|
||||||
|
#. When the stack script is done, you can open the screen session it
|
||||||
|
started to view all of the running OpenStack services:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ screen -r stack
|
||||||
|
|
||||||
|
#. Press ``Ctrl+A`` followed by 0 to go to the first ``screen`` window.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
- The ``stack.sh`` script takes a while to run. Perhaps you can
|
||||||
|
take this opportunity to `join the OpenStack
|
||||||
|
Foundation <https://www.openstack.org/join/>`__.
|
||||||
|
|
||||||
|
- ``Screen`` is a useful program for viewing many related services
|
||||||
|
at once. For more information, see the `GNU screen quick
|
||||||
|
reference <http://aperiodic.net/screen/quick_reference>`__.
|
||||||
|
|
||||||
|
Now that you have an OpenStack development environment, you're free to
|
||||||
|
hack around without worrying about damaging your production deployment.
|
||||||
|
:ref:`local.conf` provides a working environment for running
|
||||||
|
Identity service, Compute service, Block Storage service, Image service,
|
||||||
|
Dashboard, and Object Storage service as the starting point.
|
||||||
|
|
||||||
|
.. _local.conf:
|
||||||
|
|
||||||
|
local.conf
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
[[local|localrc]]
|
||||||
|
FLOATING_RANGE=192.168.1.224/27
|
||||||
|
FIXED_RANGE=10.11.12.0/24
|
||||||
|
FIXED_NETWORK_SIZE=256
|
||||||
|
FLAT_INTERFACE=eth0
|
||||||
|
ADMIN_PASSWORD=supersecret
|
||||||
|
DATABASE_PASSWORD=iheartdatabases
|
||||||
|
RABBIT_PASSWORD=flopsymopsy
|
||||||
|
SERVICE_PASSWORD=iheartksl
|
||||||
|
SERVICE_TOKEN=xyzpdqlazydog
|
341
doc/ops-guide/source/ops_customize_objectstorage.rst
Normal file
341
doc/ops-guide/source/ops_customize_objectstorage.rst
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
=============================================
|
||||||
|
Customizing Object Storage (Swift) Middleware
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
OpenStack Object Storage, known as swift when reading the code, is based
|
||||||
|
on the Python `Paste <http://pythonpaste.org/>`_ framework. The best
|
||||||
|
introduction to its architecture is `A Do-It-Yourself
|
||||||
|
Framework <http://pythonpaste.org/do-it-yourself-framework.html>`_.
|
||||||
|
Because of the swift project's use of this framework, you are able to
|
||||||
|
add features to a project by placing some custom code in a project's
|
||||||
|
pipeline without having to change any of the core code.
|
||||||
|
|
||||||
|
Imagine a scenario where you have public access to one of your
|
||||||
|
containers, but what you really want is to restrict access to that to a
|
||||||
|
set of IPs based on a whitelist. In this example, we'll create a piece
|
||||||
|
of middleware for swift that allows access to a container from only a
|
||||||
|
set of IP addresses, as determined by the container's metadata items.
|
||||||
|
Only those IP addresses that you explicitly whitelist using the
|
||||||
|
container's metadata will be able to access the container.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This example is for illustrative purposes only. It should not be
|
||||||
|
used as a container IP whitelist solution without further
|
||||||
|
development and extensive security testing.
|
||||||
|
|
||||||
|
When you join the screen session that ``stack.sh`` starts with
|
||||||
|
``screen -r stack``, you see a screen for each service running, which
|
||||||
|
can be a few or several, depending on how many services you configured
|
||||||
|
DevStack to run.
|
||||||
|
|
||||||
|
The asterisk * indicates which screen window you are viewing. This
|
||||||
|
example shows we are viewing the key (for keystone) screen window:
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
0$ shell 1$ key* 2$ horizon 3$ s-proxy 4$ s-object 5$ s-container 6$ s-account
|
||||||
|
|
||||||
|
The purpose of the screen windows are as follows:
|
||||||
|
|
||||||
|
|
||||||
|
``shell``
|
||||||
|
A shell where you can get some work done
|
||||||
|
|
||||||
|
``key*``
|
||||||
|
The keystone service
|
||||||
|
|
||||||
|
``horizon``
|
||||||
|
The horizon dashboard web application
|
||||||
|
|
||||||
|
``s-{name}``
|
||||||
|
The swift services
|
||||||
|
|
||||||
|
**To create the middleware and plug it in through Paste configuration:**
|
||||||
|
|
||||||
|
All of the code for OpenStack lives in ``/opt/stack``. Go to the swift
|
||||||
|
directory in the ``shell`` screen and edit your middleware module.
|
||||||
|
|
||||||
|
#. Change to the directory where Object Storage is installed:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ cd /opt/stack/swift
|
||||||
|
|
||||||
|
#. Create the ``ip_whitelist.py`` Python source code file:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ vim swift/common/middleware/ip_whitelist.py
|
||||||
|
|
||||||
|
#. Copy the code as shown below into ``ip_whitelist.py``.
|
||||||
|
The following code is a middleware example that
|
||||||
|
restricts access to a container based on IP address as explained at the
|
||||||
|
beginning of the section. Middleware passes the request on to another
|
||||||
|
application. This example uses the swift "swob" library to wrap Web
|
||||||
|
Server Gateway Interface (WSGI) requests and responses into objects for
|
||||||
|
swift to interact with. When you're done, save and close the file.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright (c) 2014 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from swift.common.utils import get_logger
|
||||||
|
from swift.proxy.controllers.base import get_container_info
|
||||||
|
from swift.common.swob import Request, Response
|
||||||
|
|
||||||
|
class IPWhitelistMiddleware(object):
|
||||||
|
"""
|
||||||
|
IP Whitelist Middleware
|
||||||
|
|
||||||
|
Middleware that allows access to a container from only a set of IP
|
||||||
|
addresses as determined by the container's metadata items that start
|
||||||
|
with the prefix 'allow'. E.G. allow-dev=192.168.0.20
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app, conf, logger=None):
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
self.logger = logger
|
||||||
|
else:
|
||||||
|
self.logger = get_logger(conf, log_route='ip_whitelist')
|
||||||
|
|
||||||
|
self.deny_message = conf.get('deny_message', "IP Denied")
|
||||||
|
self.local_ip = socket.gethostbyname(socket.gethostname())
|
||||||
|
|
||||||
|
def __call__(self, env, start_response):
|
||||||
|
"""
|
||||||
|
WSGI entry point.
|
||||||
|
Wraps env in swob.Request object and passes it down.
|
||||||
|
|
||||||
|
:param env: WSGI environment dictionary
|
||||||
|
:param start_response: WSGI callable
|
||||||
|
"""
|
||||||
|
req = Request(env)
|
||||||
|
|
||||||
|
try:
|
||||||
|
version, account, container, obj = req.split_path(1, 4, True)
|
||||||
|
except ValueError:
|
||||||
|
return self.app(env, start_response)
|
||||||
|
|
||||||
|
container_info = get_container_info(
|
||||||
|
req.environ, self.app, swift_source='IPWhitelistMiddleware')
|
||||||
|
|
||||||
|
remote_ip = env['REMOTE_ADDR']
|
||||||
|
self.logger.debug("Remote IP: %(remote_ip)s",
|
||||||
|
{'remote_ip': remote_ip})
|
||||||
|
|
||||||
|
meta = container_info['meta']
|
||||||
|
allow = {k:v for k,v in meta.iteritems() if k.startswith('allow')}
|
||||||
|
allow_ips = set(allow.values())
|
||||||
|
allow_ips.add(self.local_ip)
|
||||||
|
self.logger.debug("Allow IPs: %(allow_ips)s",
|
||||||
|
{'allow_ips': allow_ips})
|
||||||
|
|
||||||
|
if remote_ip in allow_ips:
|
||||||
|
return self.app(env, start_response)
|
||||||
|
else:
|
||||||
|
self.logger.debug(
|
||||||
|
"IP %(remote_ip)s denied access to Account=%(account)s "
|
||||||
|
"Container=%(container)s. Not in %(allow_ips)s", locals())
|
||||||
|
return Response(
|
||||||
|
status=403,
|
||||||
|
body=self.deny_message,
|
||||||
|
request=req)(env, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_factory(global_conf, **local_conf):
|
||||||
|
"""
|
||||||
|
paste.deploy app factory for creating WSGI proxy apps.
|
||||||
|
"""
|
||||||
|
conf = global_conf.copy()
|
||||||
|
conf.update(local_conf)
|
||||||
|
|
||||||
|
def ip_whitelist(app):
|
||||||
|
return IPWhitelistMiddleware(app, conf)
|
||||||
|
return ip_whitelist
|
||||||
|
|
||||||
|
|
||||||
|
There is a lot of useful information in ``env`` and ``conf`` that you
|
||||||
|
can use to decide what to do with the request. To find out more about
|
||||||
|
what properties are available, you can insert the following log
|
||||||
|
statement into the ``__init__`` method:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
self.logger.debug("conf = %(conf)s", locals())
|
||||||
|
|
||||||
|
|
||||||
|
and the following log statement into the ``__call__`` method:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
self.logger.debug("env = %(env)s", locals())
|
||||||
|
|
||||||
|
#. To plug this middleware into the swift Paste pipeline, you edit one
|
||||||
|
configuration file, ``/etc/swift/proxy-server.conf``:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ vim /etc/swift/proxy-server.conf
|
||||||
|
|
||||||
|
#. Find the ``[filter:ratelimit]`` section in
|
||||||
|
``/etc/swift/proxy-server.conf``, and copy in the following
|
||||||
|
configuration section after it:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[filter:ip_whitelist]
|
||||||
|
paste.filter_factory = swift.common.middleware.ip_whitelist:filter_factory
|
||||||
|
# You can override the default log routing for this filter here:
|
||||||
|
# set log_name = ratelimit
|
||||||
|
# set log_facility = LOG_LOCAL0
|
||||||
|
# set log_level = INFO
|
||||||
|
# set log_headers = False
|
||||||
|
# set log_address = /dev/log
|
||||||
|
deny_message = You shall not pass!
|
||||||
|
|
||||||
|
#. Find the ``[pipeline:main]`` section in
|
||||||
|
``/etc/swift/proxy-server.conf``, and add ``ip_whitelist`` after
|
||||||
|
ratelimit to the list like so. When you're done, save and close the
|
||||||
|
file:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pipeline:main]
|
||||||
|
pipeline = catch_errors gatekeeper healthcheck proxy-logging cache bulk tempurl ratelimit ip_whitelist ...
|
||||||
|
|
||||||
|
#. Restart the ``swift proxy`` service to make swift use your middleware.
|
||||||
|
Start by switching to the ``swift-proxy`` screen:
|
||||||
|
|
||||||
|
#. Press **Ctrl+A** followed by **3**.
|
||||||
|
|
||||||
|
#. Press **Ctrl+C** to kill the service.
|
||||||
|
|
||||||
|
#. Press **Up Arrow** to bring up the last command.
|
||||||
|
|
||||||
|
#. Press Enter to run it.
|
||||||
|
|
||||||
|
#. Test your middleware with the ``swift`` CLI. Start by switching to the
|
||||||
|
shell screen and finish by switching back to the ``swift-proxy`` screen
|
||||||
|
to check the log output:
|
||||||
|
|
||||||
|
#. Press **Ctrl+A** followed by **0**.
|
||||||
|
|
||||||
|
#. Make sure you're in the ``devstack`` directory:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ cd /root/devstack
|
||||||
|
|
||||||
|
#. Source openrc to set up your environment variables for the CLI:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ . openrc
|
||||||
|
|
||||||
|
#. Create a container called ``middleware-test``:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ swift post middleware-test
|
||||||
|
|
||||||
|
#. Press **Ctrl+A** followed by **3** to check the log output.
|
||||||
|
|
||||||
|
#. Among the log statements you'll see the lines:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
proxy-server Remote IP: my.instance.ip.address (txn: ...)
|
||||||
|
proxy-server Allow IPs: set(['my.instance.ip.address']) (txn: ...)
|
||||||
|
|
||||||
|
These two statements are produced by our middleware and show that the
|
||||||
|
request was sent from our DevStack instance and was allowed.
|
||||||
|
|
||||||
|
#. Test the middleware from outside DevStack on a remote machine that has
|
||||||
|
access to your DevStack instance:
|
||||||
|
|
||||||
|
#. Install the ``keystone`` and ``swift`` clients on your local machine:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# pip install python-keystoneclient python-swiftclient
|
||||||
|
|
||||||
|
#. Attempt to list the objects in the ``middleware-test`` container:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ swift --os-auth-url=http://my.instance.ip.address:5000/v2.0/ \
|
||||||
|
--os-region-name=RegionOne --os-username=demo:demo \
|
||||||
|
--os-password=devstack list middleware-test
|
||||||
|
Container GET failed: http://my.instance.ip.address:8080/v1/AUTH_.../
|
||||||
|
middleware-test?format=json 403 Forbidden You shall not pass!
|
||||||
|
|
||||||
|
#. Press **Ctrl+A** followed by **3** to check the log output. Look at the
|
||||||
|
swift log statements again, and among the log statements, you'll see the
|
||||||
|
lines:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
proxy-server Authorizing from an overriding middleware (i.e: tempurl) (txn: ...)
|
||||||
|
proxy-server ... IPWhitelistMiddleware
|
||||||
|
proxy-server Remote IP: my.local.ip.address (txn: ...)
|
||||||
|
proxy-server Allow IPs: set(['my.instance.ip.address']) (txn: ...)
|
||||||
|
proxy-server IP my.local.ip.address denied access to Account=AUTH_... \
|
||||||
|
Container=None. Not in set(['my.instance.ip.address']) (txn: ...)
|
||||||
|
|
||||||
|
Here we can see that the request was denied because the remote IP
|
||||||
|
address wasn't in the set of allowed IPs.
|
||||||
|
|
||||||
|
#. Back in your DevStack instance on the shell screen, add some metadata to
|
||||||
|
your container to allow the request from the remote machine:
|
||||||
|
|
||||||
|
#. Press **Ctrl+A** followed by **0**.
|
||||||
|
|
||||||
|
#. Add metadata to the container to allow the IP:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ swift post --meta allow-dev:my.local.ip.address middleware-test
|
||||||
|
|
||||||
|
#. Now try the command from Step 10 again and it succeeds. There are no
|
||||||
|
objects in the container, so there is nothing to list; however, there is
|
||||||
|
also no error to report.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Functional testing like this is not a replacement for proper unit
|
||||||
|
and integration testing, but it serves to get you started.
|
||||||
|
|
||||||
|
You can follow a similar pattern in other projects that use the Python
|
||||||
|
Paste framework. Simply create a middleware module and plug it in
|
||||||
|
through configuration. The middleware runs in sequence as part of that
|
||||||
|
project's pipeline and can call out to other services as necessary. No
|
||||||
|
project core code is touched. Look for a ``pipeline`` value in the
|
||||||
|
project's ``conf`` or ``ini`` configuration files in ``/etc/<project>``
|
||||||
|
to identify projects that use Paste.
|
||||||
|
|
||||||
|
When your middleware is done, we encourage you to open source it and let
|
||||||
|
the community know on the OpenStack mailing list. Perhaps others need
|
||||||
|
the same functionality. They can use your code, provide feedback, and
|
||||||
|
possibly contribute. If enough support exists for it, perhaps you can
|
||||||
|
propose that it be added to the official swift
|
||||||
|
`middleware <https://git.openstack.org/cgit/openstack/swift/tree/swift/common/middleware>`_.
|
Loading…
Reference in New Issue
Block a user