[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
|
||||
=============
|
||||
|
||||
.. 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
|
||||
add a new feature, you can follow different paths.
|
||||
|
||||
To take the first path, you can modify the OpenStack code directly.
|
||||
Learn `how to
|
||||
contribute <https://wiki.openstack.org/wiki/How_To_Contribute>`_,
|
||||
follow the `code review
|
||||
workflow <https://wiki.openstack.org/wiki/GerritWorkflow>`_, make your
|
||||
Learn `how to contribute
|
||||
<https://wiki.openstack.org/wiki/How_To_Contribute>`_,
|
||||
follow the `Developer's Guide
|
||||
<http://docs.openstack.org/infra/manual/developers.html>`_, make your
|
||||
changes, and contribute them back to the upstream OpenStack project.
|
||||
This path is recommended if the feature you need requires deep
|
||||
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.
|
||||
|
||||
This chapter focuses on the second path for customizing OpenStack by
|
||||
providing two examples for writing new features. The first example shows
|
||||
how to modify Object Storage (swift) middleware to add a new feature,
|
||||
and the second example provides a new scheduler feature for OpenStack
|
||||
Compute (nova). To customize OpenStack this way you need a development
|
||||
environment. 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.
|
||||
providing two examples for writing new features.
|
||||
The first example shows how to modify Object Storage service (swift)
|
||||
middleware to add a new feature, and the second example provides a new
|
||||
scheduler feature for Compute service (nova).
|
||||
To customize OpenStack this way you need a development environment.
|
||||
The best way to get an environment up and running quickly is to run
|
||||
DevStack within your cloud.
|
||||
|
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