Merge trunk.

This commit is contained in:
Muneyuki Noguchi
2011-04-22 09:57:19 +09:00
155 changed files with 30962 additions and 20630 deletions

View File

@@ -5,13 +5,7 @@ _trial_temp
keys
networks
nova.sqlite
CA/cacert.pem
CA/crl.pem
CA/index.txt*
CA/openssl.cnf
CA/serial*
CA/newcerts/*.pem
CA/private/cakey.pem
CA
nova/vcsversion.py
*.DS_Store
.project

View File

@@ -4,6 +4,7 @@
<anotherjesse@gmail.com> <jesse@dancelamb>
<anotherjesse@gmail.com> <jesse@gigantor.local>
<anotherjesse@gmail.com> <jesse@ubuntu>
<anotherjesse@gmail.com> <jesse@aire.local>
<ant@openstack.org> <amesserl@rackspace.com>
<Armando.Migliaccio@eu.citrix.com> <armando.migliaccio@citrix.com>
<brian.lamar@rackspace.com> <brian.lamar@gmail.com>

View File

@@ -27,11 +27,15 @@ Gabe Westmaas <gabe.westmaas@rackspace.com>
Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>
Hisaki Ohara <hisaki.ohara@intel.com>
Ilya Alekseyev <ialekseev@griddynamics.com>
Jason Koelker <jason@koelker.net>
Jay Pipes <jaypipes@gmail.com>
Jesse Andrews <anotherjesse@gmail.com>
Jimmy Bergman <jimmy@sigint.se>
Joe Heck <heckj@mac.com>
Joel Moore <joelbm24@gmail.com>
Johannes Erdfelt <johannes.erdfelt@rackspace.com>
John Dewey <john@dewey.ws>
John Tran <jtran@attinteractive.com>
Jonathan Bryce <jbryce@jbryce.com>
Jordan Rinke <jordan@openstack.org>
Josh Durgin <joshd@hq.newdream.net>
@@ -72,5 +76,6 @@ Trey Morris <trey.morris@rackspace.com>
Tushar Patil <tushar.vitthal.patil@gmail.com>
Vasiliy Shlykov <vash@vasiliyshlykov.org>
Vishvananda Ishaya <vishvananda@gmail.com>
Yoshiaki Tamura <yoshi@midokura.jp>
Youcef Laribi <Youcef.Laribi@eu.citrix.com>
Zhixue Wu <Zhixue.Wu@citrix.com>

17
HACKING
View File

@@ -50,17 +50,24 @@ Human Alphabetical Order Examples
Docstrings
----------
"""Summary of the function, class or method, less than 80 characters.
"""A one line docstring looks like this and ends in a period."""
New paragraph after newline that explains in more detail any general
information about the function, class or method. After this, if defining
parameters and return types use the Sphinx format. After that an extra
newline then close the quotations.
"""A multiline docstring has a one-line summary, less than 80 characters.
Then a new paragraph after a newline that explains in more detail any
general information about the function, class or method. Example usages
are also great to have here if it is a complex class for function. After
you have finished your descriptions add an extra newline and close the
quotations.
When writing the docstring for a class, an extra line should be placed
after the closing quotations. For more in-depth explanations for these
decisions see http://www.python.org/dev/peps/pep-0257/
If you are going to describe parameters and return values, use Sphinx, the
appropriate syntax is as follows.
:param foo: the foo parameter
:param bar: the bar parameter
:returns: description of the return value

View File

@@ -1,7 +1,7 @@
include HACKING LICENSE run_tests.py run_tests.sh
include README builddeb.sh exercise_rsapi.py
include ChangeLog MANIFEST.in pylintrc Authors
graft CA
graft nova/CA
graft doc
graft smoketests
graft tools

View File

@@ -28,11 +28,11 @@ import sys
# If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
sys.path.insert(0, POSSIBLE_TOPDIR)
gettext.install('nova', unicode=1)

View File

@@ -58,7 +58,6 @@ import gettext
import glob
import json
import os
import re
import sys
import time
@@ -66,11 +65,11 @@ import IPy
# If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
sys.path.insert(0, POSSIBLE_TOPDIR)
gettext.install('nova', unicode=1)
@@ -449,7 +448,7 @@ class FixedIpCommands(object):
ctxt = context.get_admin_context()
try:
if host == None:
if host is None:
fixed_ips = db.fixed_ip_get_all(ctxt)
else:
fixed_ips = db.fixed_ip_get_all_by_host(ctxt, host)
@@ -499,7 +498,7 @@ class FloatingIpCommands(object):
"""Lists all floating ips (optionally by host)
arguments: [host]"""
ctxt = context.get_admin_context()
if host == None:
if host is None:
floating_ips = db.floating_ip_get_all(ctxt)
else:
floating_ips = db.floating_ip_get_all_by_host(ctxt, host)
@@ -570,6 +569,49 @@ class NetworkCommands(object):
class VmCommands(object):
"""Class for mangaging VM instances."""
def list(self, host=None):
"""Show a list of all instances
:param host: show all instance on specified host.
:param instance: show specificed instance.
"""
print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
" %-10s %-10s %-10s %-5s" % (
_('instance'),
_('node'),
_('type'),
_('state'),
_('launched'),
_('image'),
_('kernel'),
_('ramdisk'),
_('project'),
_('user'),
_('zone'),
_('index'))
if host is None:
instances = db.instance_get_all(context.get_admin_context())
else:
instances = db.instance_get_all_by_host(
context.get_admin_context(), host)
for instance in instances:
print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
" %-10s %-10s %-10s %-5d" % (
instance['hostname'],
instance['host'],
instance['instance_type'],
instance['state_description'],
instance['launched_at'],
instance['image_id'],
instance['kernel_id'],
instance['ramdisk_id'],
instance['project_id'],
instance['user_id'],
instance['availability_zone'],
instance['launch_index'])
def live_migration(self, ec2_id, dest):
"""Migrates a running instance to a new machine.
@@ -701,15 +743,6 @@ class ServiceCommands(object):
{"method": "update_available_resource"})
class LogCommands(object):
def request(self, request_id, logfile='/var/log/nova.log'):
"""Show all fields in the log for the given request. Assumes you
haven't changed the log format too much.
ARGS: request_id [logfile]"""
lines = utils.execute("cat %s | grep '\[%s '" % (logfile, request_id))
print re.sub('#012', "\n", "\n".join(lines))
class DbCommands(object):
"""Class for managing the database."""
@@ -725,49 +758,6 @@ class DbCommands(object):
print migration.db_version()
class InstanceCommands(object):
"""Class for managing instances."""
def list(self, host=None, instance=None):
"""Show a list of all instances"""
print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
" %-10s %-10s %-10s %-5s" % (
_('instance'),
_('node'),
_('type'),
_('state'),
_('launched'),
_('image'),
_('kernel'),
_('ramdisk'),
_('project'),
_('user'),
_('zone'),
_('index'))
if host == None:
instances = db.instance_get_all(context.get_admin_context())
else:
instances = db.instance_get_all_by_host(
context.get_admin_context(), host)
for instance in instances:
print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
" %-10s %-10s %-10s %-5d" % (
instance['hostname'],
instance['host'],
instance['instance_type'],
instance['state_description'],
instance['launched_at'],
instance['image_id'],
instance['kernel_id'],
instance['ramdisk_id'],
instance['project_id'],
instance['user_id'],
instance['availability_zone'],
instance['launch_index'])
class VolumeCommands(object):
"""Methods for dealing with a cloud in an odd state"""
@@ -818,11 +808,11 @@ class VolumeCommands(object):
class InstanceTypeCommands(object):
"""Class for managing instance types / flavors."""
def _print_instance_types(self, n, val):
def _print_instance_types(self, name, val):
deleted = ('', ', inactive')[val["deleted"] == 1]
print ("%s: Memory: %sMB, VCPUS: %s, Storage: %sGB, FlavorID: %s, "
"Swap: %sGB, RXTX Quota: %sGB, RXTX Cap: %sMB%s") % (
n, val["memory_mb"], val["vcpus"], val["local_gb"],
name, val["memory_mb"], val["vcpus"], val["local_gb"],
val["flavorid"], val["swap"], val["rxtx_quota"],
val["rxtx_cap"], deleted)
@@ -873,12 +863,12 @@ class InstanceTypeCommands(object):
"""Lists all active or specific instance types / flavors
arguments: [name]"""
try:
if name == None:
if name is None:
inst_types = instance_types.get_all_types()
elif name == "--all":
inst_types = instance_types.get_all_types(True)
else:
inst_types = instance_types.get_instance_type(name)
inst_types = instance_types.get_instance_type_by_name(name)
except exception.DBError, e:
_db_error(e)
if isinstance(inst_types.values()[0], dict):
@@ -894,20 +884,17 @@ class ImageCommands(object):
def __init__(self, *args, **kwargs):
self.image_service = utils.import_object(FLAGS.image_service)
def _register(self, image_type, disk_format, container_format,
def _register(self, container_format, disk_format,
path, owner, name=None, is_public='T',
architecture='x86_64', kernel_id=None, ramdisk_id=None):
meta = {'is_public': True,
meta = {'is_public': (is_public == 'T'),
'name': name,
'disk_format': disk_format,
'container_format': container_format,
'disk_format': disk_format,
'properties': {'image_state': 'available',
'owner_id': owner,
'type': image_type,
'project_id': owner,
'architecture': architecture,
'image_location': 'local',
'is_public': (is_public == 'T')}}
print image_type, meta
'image_location': 'local'}}
if kernel_id:
meta['properties']['kernel_id'] = int(kernel_id)
if ramdisk_id:
@@ -932,16 +919,18 @@ class ImageCommands(object):
ramdisk_id = self.ramdisk_register(ramdisk, owner, None,
is_public, architecture)
self.image_register(image, owner, name, is_public,
architecture, kernel_id, ramdisk_id)
architecture, 'ami', 'ami',
kernel_id, ramdisk_id)
def image_register(self, path, owner, name=None, is_public='T',
architecture='x86_64', kernel_id=None, ramdisk_id=None,
disk_format='ami', container_format='ami'):
architecture='x86_64', container_format='bare',
disk_format='raw', kernel_id=None, ramdisk_id=None):
"""Uploads an image into the image_service
arguments: path owner [name] [is_public='T'] [architecture='x86_64']
[container_format='bare'] [disk_format='raw']
[kernel_id=None] [ramdisk_id=None]
[disk_format='ami'] [container_format='ami']"""
return self._register('machine', disk_format, container_format, path,
"""
return self._register(container_format, disk_format, path,
owner, name, is_public, architecture,
kernel_id, ramdisk_id)
@@ -950,7 +939,7 @@ class ImageCommands(object):
"""Uploads a kernel into the image_service
arguments: path owner [name] [is_public='T'] [architecture='x86_64']
"""
return self._register('kernel', 'aki', 'aki', path, owner, name,
return self._register('aki', 'aki', path, owner, name,
is_public, architecture)
def ramdisk_register(self, path, owner, name=None, is_public='T',
@@ -958,7 +947,7 @@ class ImageCommands(object):
"""Uploads a ramdisk into the image_service
arguments: path owner [name] [is_public='T'] [architecture='x86_64']
"""
return self._register('ramdisk', 'ari', 'ari', path, owner, name,
return self._register('ari', 'ari', path, owner, name,
is_public, architecture)
def _lookup(self, old_image_id):
@@ -975,16 +964,17 @@ class ImageCommands(object):
'ramdisk': 'ari'}
container_format = mapping[old['type']]
disk_format = container_format
if container_format == 'ami' and not old.get('kernelId'):
container_format = 'bare'
disk_format = 'raw'
new = {'disk_format': disk_format,
'container_format': container_format,
'is_public': True,
'is_public': old['isPublic'],
'name': old['imageId'],
'properties': {'image_state': old['imageState'],
'owner_id': old['imageOwnerId'],
'project_id': old['imageOwnerId'],
'architecture': old['architecture'],
'type': old['type'],
'image_location': old['imageLocation'],
'is_public': old['isPublic']}}
'image_location': old['imageLocation']}}
if old.get('kernelId'):
new['properties']['kernel_id'] = self._lookup(old['kernelId'])
if old.get('ramdiskId'):
@@ -1018,7 +1008,7 @@ class ImageCommands(object):
if (FLAGS.image_service == 'nova.image.local.LocalImageService'
and directory == os.path.abspath(FLAGS.images_path)):
new_dir = "%s_bak" % directory
os.move(directory, new_dir)
os.rename(directory, new_dir)
os.mkdir(directory)
directory = new_dir
for fn in glob.glob("%s/*/info.json" % directory):
@@ -1030,7 +1020,7 @@ class ImageCommands(object):
machine_images[image_path] = image_metadata
else:
other_images[image_path] = image_metadata
except Exception as exc:
except Exception:
print _("Failed to load %(fn)s.") % locals()
# NOTE(vish): do kernels and ramdisks first so images
self._convert_images(other_images)
@@ -1049,13 +1039,11 @@ CATEGORIES = [
('network', NetworkCommands),
('vm', VmCommands),
('service', ServiceCommands),
('log', LogCommands),
('db', DbCommands),
('volume', VolumeCommands),
('instance_type', InstanceTypeCommands),
('image', ImageCommands),
('flavor', InstanceTypeCommands),
('instance', InstanceCommands)]
('flavor', InstanceTypeCommands)]
def lazy_match(name, key_value_tuples):

View File

@@ -38,6 +38,46 @@ The cloudpipe image is basically just a linux instance with openvpn installed.
It is also useful to have a cron script that will periodically redownload the metadata and copy the new crl. This will keep revoked users from connecting and will disconnect any users that are connected with revoked certificates when their connection is renegotiated (every hour).
Creating a Cloudpipe Image
--------------------------
Making a cloudpipe image is relatively easy.
# install openvpn on a base ubuntu image.
# set up a server.conf.template in /etc/openvpn/
.. literalinclude:: server.conf.template
:language: bash
:linenos:
# set up.sh in /etc/openvpn/
.. literalinclude:: up.sh
:language: bash
:linenos:
# set down.sh in /etc/openvpn/
.. literalinclude:: down.sh
:language: bash
:linenos:
# download and run the payload on boot from /etc/rc.local.
.. literalinclude:: rc.local
:language: bash
:linenos:
# register the image and set the image id in your flagfile::
--vpn_image_id=ami-xxxxxxxx
# you should set a few other flags to make vpns work properly::
--use_project_ca
--cnt_vpn_clients=5
Cloudpipe Launch
----------------
@@ -63,6 +103,31 @@ Certificates and Revocation
If the use_project_ca flag is set (required to for cloudpipes to work securely), then each project has its own ca. This ca is used to sign the certificate for the vpn, and is also passed to the user for bundling images. When a certificate is revoked using nova-manage, a new Certificate Revocation List (crl) is generated. As long as cloudpipe has an updated crl, it will block revoked users from connecting to the vpn.
The userdata for cloudpipe isn't currently updated when certs are revoked, so it is necessary to restart the cloudpipe instance if a user's credentials are revoked.
Restarting Cloudpipe VPN
------------------------
You can reboot a cloudpipe vpn through the api if something goes wrong (using euca-reboot-instances for example), but if you generate a new crl, you will have to terminate it and start it again using nova-manage vpn run. The cloudpipe instance always gets the first ip in the subnet and it can take up to 10 minutes for the ip to be recovered. If you try to start the new vpn instance too soon, the instance will fail to start because of a NoMoreAddresses error. If you can't wait 10 minutes, you can manually update the ip with something like the following (use the right ip for the project)::
euca-terminate-instances <instance_id>
mysql nova -e "update fixed_ips set allocated=0, leased=0, instance_id=NULL where fixed_ip='10.0.0.2'"
You also will need to terminate the dnsmasq running for the user (make sure you use the right pid file)::
sudo kill `cat /var/lib/nova/br100.pid`
Now you should be able to re-run the vpn::
nova-manage vpn run <project_id>
Logging into Cloudpipe VPN
--------------------------
The keypair that was used to launch the cloudpipe instance should be in the keys/<project_id> folder. You can use this key to log into the cloudpipe instance for debugging purposes.
The :mod:`nova.cloudpipe.pipelib` Module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -0,0 +1,36 @@
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
####### These lines go at the end of /etc/rc.local #######
. /lib/lsb/init-functions
echo Downloading payload from userdata
wget http://169.254.169.254/latest/user-data -O /tmp/payload.b64
echo Decrypting base64 payload
openssl enc -d -base64 -in /tmp/payload.b64 -out /tmp/payload.zip
mkdir -p /tmp/payload
echo Unzipping payload file
unzip -o /tmp/payload.zip -d /tmp/payload/
# if the autorun.sh script exists, run it
if [ -e /tmp/payload/autorun.sh ]; then
echo Running autorun.sh
cd /tmp/payload
sh /tmp/payload/autorun.sh
else
echo rc.local : No autorun script to run
fi
exit 0

View File

@@ -0,0 +1,34 @@
port 1194
proto udp
dev tap0
up "/etc/openvpn/up.sh br0"
down "/etc/openvpn/down.sh br0"
persist-key
persist-tun
ca ca.crt
cert server.crt
key server.key # This file should be kept secret
dh dh1024.pem
ifconfig-pool-persist ipp.txt
server-bridge VPN_IP DHCP_SUBNET DHCP_LOWER DHCP_UPPER
client-to-client
keepalive 10 120
comp-lzo
max-clients 1
user nobody
group nogroup
persist-key
persist-tun
status openvpn-status.log
verb 3
mute 20

127
doc/source/devref/zone.rst Normal file
View File

@@ -0,0 +1,127 @@
..
Copyright 2010-2011 OpenStack LLC
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.
Zones
=====
A Nova deployment is called a Zone. At the very least a Zone requires an API node, a Scheduler node, a database and RabbitMQ. Pushed further a Zone may contain many API nodes, many Scheduler, Volume, Network and Compute nodes as well as a cluster of databases and RabbitMQ servers. A Zone allows you to partition your deployments into logical groups for load balancing and instance distribution.
The idea behind Zones is, if a particular deployment is not capable of servicing a particular request, the request may be forwarded to (child) Zones for possible processing. Zones may be nested in a tree fashion.
Zones only know about their immediate children, they do not know about their parent Zones and may in fact have more than one parent. Likewise, a Zone's children may themselves have child Zones.
Zones share nothing. They communicate via the public OpenStack API only. No database, queue, user or project definition is shared between Zones.
Capabilities
------------
Routing between Zones is based on the Capabilities of that Zone. Capabilities are nothing more than key/value pairs. Values are multi-value, with each value separated with a semicolon (`;`). When expressed as a string they take the form:
::
key=value;value;value, key=value;value;value
Zones have Capabilities which are general to the Zone and are set via `--zone-capabilities` flag. Zones also have dynamic per-service Capabilities. Services derived from `nova.manager.SchedulerDependentManager` (such as Compute, Volume and Network) can set these capabilities by calling the `update_service_capabilities()` method on their `Manager` base class. These capabilities will be periodically sent to the Scheduler service automatically. The rate at which these updates are sent is controlled by the `--periodic_interval` flag.
Flow within a Zone
------------------
The brunt of the work within a Zone is done in the Scheduler Service. The Scheduler is responsible for:
- collecting capability messages from the Compute, Volume and Network nodes,
- polling the child Zones for their status and
- providing data to the Distributed Scheduler for performing load balancing calculations
Inter-service communication within a Zone is done with RabbitMQ. Each class of Service (Compute, Volume and Network) has both a named message exchange (particular to that host) and a general message exchange (particular to that class of service). Messages sent to these exchanges are picked off in round-robin fashion. Zones introduce a new fan-out exchange per service. Messages sent to the fan-out exchange are picked up by all services of a particular class. This fan-out exchange is used by the Scheduler services to receive capability messages from the Compute, Volume and Network nodes.
These capability messages are received by the Scheduler services and stored in the `ZoneManager` object. The SchedulerManager object has a reference to the `ZoneManager` it can use for load balancing.
The `ZoneManager` also polls the child Zones periodically to gather their capabilities to aid in decision making. This is done via the OpenStack API `/v1.0/zones/info` REST call. This also captures the name of each child Zone. The Zone name is set via the `--zone-name` flag (and defaults to "nova").
Zone administrative functions
-----------------------------
Zone administrative operations are usually done using python-novaclient_
.. _python-novaclient: https://github.com/rackspace/python-novaclient
In order to use the Zone operations, be sure to enable administrator operations in OpenStack API by setting the `--allow_admin_api=true` flag.
Finally you need to enable Zone Forwarding. This will be used by the Distributed Scheduler initiative currently underway. Set `--enable_zone_routing=true` to enable this feature.
Find out about this Zone
------------------------
In any Zone you can find the Zone's name and capabilities with the ``nova zone-info`` command.
::
alice@novadev:~$ nova zone-info
+-----------------+---------------+
| Property | Value |
+-----------------+---------------+
| compute_cpu | 0.7,0.7 |
| compute_disk | 123000,123000 |
| compute_network | 800,800 |
| hypervisor | xenserver |
| name | nova |
| network_cpu | 0.7,0.7 |
| network_disk | 123000,123000 |
| network_network | 800,800 |
| os | linux |
+-----------------+---------------+
This equates to a GET operation on `.../zones/info`. If you have no child Zones defined you'll usually only get back the default `name`, `hypervisor` and `os` capabilities. Otherwise you'll get back a tuple of min, max values for each capabilities of all the hosts of all the services running in the child zone. These take the `<service>_<capability> = <min>,<max>` format.
Adding a child Zone
-------------------
Any Zone can be a parent Zone. Children are associated to a Zone. The Zone where this command originates from is known as the Parent Zone. Routing is only ever conducted from a Zone to its children, never the other direction. From a parent zone you can add a child zone with the following command:
::
nova zone-add <child zone api url> <username> <nova api key>
You can get the `child zone api url`, `nova api key` and `username` from the `novarc` file in the child zone. For example:
::
export NOVA_API_KEY="3bd1af06-6435-4e23-a827-413b2eb86934"
export NOVA_USERNAME="alice"
export NOVA_URL="http://192.168.2.120:8774/v1.0/"
This equates to a POST operation to `.../zones/` to add a new zone. No connection attempt to the child zone is done when this command. It only puts an entry in the db at this point. After about 30 seconds the `ZoneManager` in the Scheduler services will attempt to talk to the child zone and get its information.
Getting a list of child Zones
-----------------------------
::
nova zone-list
alice@novadev:~$ nova zone-list
+----+-------+-----------+--------------------------------------------+---------------------------------+
| ID | Name | Is Active | Capabilities | API URL |
+----+-------+-----------+--------------------------------------------+---------------------------------+
| 2 | zone1 | True | hypervisor=xenserver;kvm, os=linux;windows | http://192.168.2.108:8774/v1.0/ |
| 3 | zone2 | True | hypervisor=xenserver;kvm, os=linux;windows | http://192.168.2.115:8774/v1.0/ |
+----+-------+-----------+--------------------------------------------+---------------------------------+
This equates to a GET operation to `.../zones`.
Removing a child Zone
---------------------
::
nova zone-delete <N>
This equates to a DELETE call to `.../zones/N`. The Zone with ID=N will be removed. This will only remove the zone entry from the current (parent) Zone, no child Zones are affected. Removing a Child Zone doesn't affect any other part of the hierarchy.

7
doc/source/down.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
BR=$1
DEV=$2
/usr/sbin/brctl delif $BR $DEV
/sbin/ifconfig $DEV down

View File

@@ -240,6 +240,16 @@ Nova Images
Converts all images in directory from the old (Bexar) format to the new format.
Nova VM
~~~~~~~~~~~
``nova-manage vm list [host]``
Show a list of all instances. Accepts optional hostname (to show only instances on specific host).
``nova-manage live-migration <ec2_id> <destination host name>``
Live migrate instance from current host to destination host. Requires instance id (which comes from euca-describe-instance) and destination host name (which can be found from nova-manage service list).
FILES
========

View File

@@ -20,174 +20,4 @@ Flags and Flagfiles
Nova uses a configuration file containing flags located in /etc/nova/nova.conf. You can get the most recent listing of avaialble flags by running nova-(servicename) --help, for example, nova-api --help.
Here's a list of available flags and their default settings.
--ajax_console_proxy_port: port that ajax_console_proxy binds
(default: '8000')
--ajax_console_proxy_topic: the topic ajax proxy nodes listen on
(default: 'ajax_proxy')
--ajax_console_proxy_url: location of ajax console proxy, in the form
"http://127.0.0.1:8000"
(default: 'http://127.0.0.1:8000')
--auth_token_ttl: Seconds for auth tokens to linger
(default: '3600')
(an integer)
--aws_access_key_id: AWS Access ID
(default: 'admin')
--aws_secret_access_key: AWS Access Key
(default: 'admin')
--compute_manager: Manager for compute
(default: 'nova.compute.manager.ComputeManager')
--compute_topic: the topic compute nodes listen on
(default: 'compute')
--connection_type: libvirt, xenapi or fake
(default: 'libvirt')
--console_manager: Manager for console proxy
(default: 'nova.console.manager.ConsoleProxyManager')
--console_topic: the topic console proxy nodes listen on
(default: 'console')
--control_exchange: the main exchange to connect to
(default: 'nova')
--db_backend: The backend to use for db
(default: 'sqlalchemy')
--default_image: default image to use, testing only
(default: 'ami-11111')
--default_instance_type: default instance type to use, testing only
(default: 'm1.small')
--default_log_levels: list of logger=LEVEL pairs
(default: 'amqplib=WARN,sqlalchemy=WARN,eventlet.wsgi.server=WARN')
(a comma separated list)
--default_project: default project for openstack
(default: 'openstack')
--ec2_dmz_host: internal ip of api server
(default: '$my_ip')
--ec2_host: ip of api server
(default: '$my_ip')
--ec2_path: suffix for ec2
(default: '/services/Cloud')
--ec2_port: cloud controller port
(default: '8773')
(an integer)
--ec2_scheme: prefix for ec2
(default: 'http')
--[no]enable_new_services: Services to be added to the available pool on
create
(default: 'true')
--[no]fake_network: should we use fake network devices and addresses
(default: 'false')
--[no]fake_rabbit: use a fake rabbit
(default: 'false')
--glance_host: glance host
(default: '$my_ip')
--glance_port: glance port
(default: '9292')
(an integer)
-?,--[no]help: show this help
--[no]helpshort: show usage only for this module
--[no]helpxml: like --help, but generates XML output
--host: name of this node
(default: 'osdemo03')
--image_service: The service to use for retrieving and searching for images.
(default: 'nova.image.s3.S3ImageService')
--instance_name_template: Template string to be used to generate instance
names
(default: 'instance-%08x')
--logfile: output to named file
--logging_context_format_string: format string to use for log messages with
context
(default: '%(asctime)s %(levelname)s %(name)s [%(request_id)s %(user)s
%(project)s] %(message)s')
--logging_debug_format_suffix: data to append to log format when level is
DEBUG
(default: 'from %(processName)s (pid=%(process)d) %(funcName)s
%(pathname)s:%(lineno)d')
--logging_default_format_string: format string to use for log messages without
context
(default: '%(asctime)s %(levelname)s %(name)s [-] %(message)s')
--logging_exception_prefix: prefix each line of exception output with this
format
(default: '(%(name)s): TRACE: ')
--my_ip: host ip address
(default: '184.106.73.68')
--network_manager: Manager for network
(default: 'nova.network.manager.VlanManager')
--network_topic: the topic network nodes listen on
(default: 'network')
--node_availability_zone: availability zone of this node
(default: 'nova')
--null_kernel: kernel image that indicates not to use a kernel, but to use a
raw disk image instead
(default: 'nokernel')
--osapi_host: ip of api server
(default: '$my_ip')
--osapi_path: suffix for openstack
(default: '/v1.0/')
--osapi_port: OpenStack API port
(default: '8774')
(an integer)
--osapi_scheme: prefix for openstack
(default: 'http')
--periodic_interval: seconds between running periodic tasks
(default: '60')
(a positive integer)
--pidfile: pidfile to use for this service
--rabbit_host: rabbit host
(default: 'localhost')
--rabbit_max_retries: rabbit connection attempts
(default: '12')
(an integer)
--rabbit_password: rabbit password
(default: 'guest')
--rabbit_port: rabbit port
(default: '5672')
(an integer)
--rabbit_retry_interval: rabbit connection retry interval
(default: '10')
(an integer)
--rabbit_userid: rabbit userid
(default: 'guest')
--rabbit_virtual_host: rabbit virtual host
(default: '/')
--region_list: list of region=fqdn pairs separated by commas
(default: '')
(a comma separated list)
--report_interval: seconds between nodes reporting state to datastore
(default: '10')
(a positive integer)
--s3_dmz: s3 dmz ip (for instances)
(default: '$my_ip')
--s3_host: s3 host (for infrastructure)
(default: '$my_ip')
--s3_port: s3 port
(default: '3333')
(an integer)
--scheduler_manager: Manager for scheduler
(default: 'nova.scheduler.manager.SchedulerManager')
--scheduler_topic: the topic scheduler nodes listen on
(default: 'scheduler')
--sql_connection: connection string for sql database
(default: 'sqlite:///$state_path/nova.sqlite')
--sql_idle_timeout: timeout for idle sql database connections
(default: '3600')
--sql_max_retries: sql connection attempts
(default: '12')
(an integer)
--sql_retry_interval: sql connection retry interval
(default: '10')
(an integer)
--state_path: Top-level directory for maintaining nova's state
(default: '/usr/lib/pymodules/python2.6/nova/../')
--[no]use_syslog: output to syslog
(default: 'false')
--[no]verbose: show debug output
(default: 'false')
--volume_manager: Manager for volume
(default: 'nova.volume.manager.VolumeManager')
--volume_name_template: Template string to be used to generate instance names
(default: 'volume-%08x')
--volume_topic: the topic volume nodes listen on
(default: 'volume')
--vpn_image_id: AMI for cloudpipe vpn server
(default: 'ami-cloudpipe')
--vpn_key_suffix: Suffix to add to project name for vpn key and secgroups
(default: '-vpn')
The OpenStack wiki has a page with the flags listed by their purpose and use at http://wiki.openstack.org/FlagsGrouping.

View File

@@ -18,7 +18,7 @@
Running Nova
============
This guide describes the basics of running and managing Nova. For more administrator's documentation, refer to `docs.openstack.org <http://docs.openstack.org>`_.
This guide describes the basics of running and managing Nova. This site is intended to provide developer documentation. For more administrator's documentation, refer to `docs.openstack.org <http://docs.openstack.org>`_.
Running the Cloud
-----------------
@@ -60,7 +60,7 @@ For background on the core objects referenced in this section, see :doc:`../obje
Deployment
----------
For a starting multi-node architecture, you would start with two nodes - a cloud controller node and a compute node. The cloud controller node contains the nova- services plus the Nova database. The compute node installs all the nova-services but then refers to the database installation, which is hosted by the cloud controller node. Ensure that the nova.conf file is identical on each node. If you find performance issues not related to database reads or writes, but due to the messaging queue backing up, you could add additional messaging services (rabbitmq). For instructions on multi-server installations, refer to `Installing and Configuring OpenStack Compute <http://docs.openstack.org/openstack-compute/admin/content/ch03.html>`_.
For a starting multi-node architecture, you would start with two nodes - a cloud controller node and a compute node. The cloud controller node contains the nova- services plus the Nova database. The compute node installs all the nova-services but then refers to the database installation, which is hosted by the cloud controller node. Ensure that the nova.conf file is identical on each node. If you find performance issues not related to database reads or writes, but due to the messaging queue backing up, you could add additional messaging services (rabbitmq). For instructions on multi-server installations, refer to `Installing and Configuring OpenStack Compute <http://docs.openstack.org/>`_.
.. toctree::

View File

@@ -18,4 +18,9 @@
Managing Images
===============
.. todo:: Put info on managing images here!
With Nova, you can manage images either using the built-in object store or using Glance, a related OpenStack project. Glance is a server that provides the following services:
* Ability to store and retrieve virtual machine images
* Ability to store and retrieve metadata about these virtual machine images
Refer to http://glance.openstack.org for additional details.

View File

@@ -16,6 +16,8 @@
Managing Instance Types and Flavors
===================================
You can manage instance types and instance flavors using the nova-manage command-line interface coupled with the instance_type subcommand for nova-manage.
What are Instance Types or Flavors ?
------------------------------------

View File

@@ -18,8 +18,6 @@
Security Considerations
=======================
.. todo:: This doc is vague and just high-level right now. Describe architecture that enables security.
The goal of securing a cloud computing system involves both protecting the instances, data on the instances, and
ensuring users are authenticated for actions and that borders are understood by the users and the system.
Protecting the system from intrusion or attack involves authentication, network protections, and

View File

@@ -36,9 +36,7 @@ In this mode, each project gets its own VLAN, Linux networking bridge, and subne
While network traffic between VM instances belonging to the same VLAN is always open, Nova can enforce isolation of network traffic between different projects by enforcing one VLAN per project.
In addition, the network administrator can specify a pool of public IP addresses that users may allocate and then assign to VMs, either at boot or dynamically at run-time. This capability is similar to Amazon's 'elastic IPs'. A public IP address may be associated with a running instances, allowing the VM instance to be accessed from the public network. The public IP addresses are accessible from the network host and NATed to the private IP address of the project.
.. todo:: Describe how a public IP address could be associated with a project (a VLAN)
In addition, the network administrator can specify a pool of public IP addresses that users may allocate and then assign to VMs, either at boot or dynamically at run-time. This capability is similar to Amazon's 'elastic IPs'. A public IP address may be associated with a running instances, allowing the VM instance to be accessed from the public network. The public IP addresses are accessible from the network host and NATed to the private IP address of the project. A public IP address could be associated with a project using the euca-allocate-address commands.
This is the default networking mode and supports the most features. For multiple machine installation, it requires a switch that supports host-managed vlan tagging. In this mode, nova will create a vlan and bridge for each project. The project gets a range of private ips that are only accessible from inside the vlan. In order for a user to access the instances in their project, a special vpn instance (code named :ref:`cloudpipe <cloudpipe>`) needs to be created. Nova generates a certificate and key for the user to access the vpn and starts the vpn automatically. More information on cloudpipe can be found :ref:`here <cloudpipe>`.
@@ -176,4 +174,3 @@ Setup
* project network size
* DMZ network
.. todo:: need specific Nova configuration added

View File

@@ -83,13 +83,13 @@ Nova User
Nova Project
~~~~~~~~~~~~
``nova-manage project add <projectname>``
``nova-manage project add <projectname> <username>``
Add a nova project with the name <projectname> to the database.
Add a nova project with the name <projectname> to the database that will be administered by the named user.
``nova-manage project create <projectname>``
``nova-manage project create <projectname> <projectmanager>``
Create a new nova project with the name <projectname> (you still need to do nova-manage project add <projectname> to add it to the database).
Create a new nova project with the name <projectname> (you still need to do nova-manage project add <projectname> to add it to the database). The <projectmanager> username is the administrator of the project.
``nova-manage project delete <projectname>``
@@ -111,9 +111,9 @@ Nova Project
Deletes the project with the name <projectname>.
``nova-manage project zipfile``
``nova-manage project zipfile <projectname> <username> <directory for credentials>``
Compresses all related files for a created project into a zip file nova.zip.
Compresses all related files for a created project into a named zip file such as nova.zip.
Nova Role
~~~~~~~~~
@@ -226,7 +226,7 @@ Concept: Plugins
Concept: IPC/RPC
----------------
Rabbit!
Rabbit is the main messaging queue, used for all communication between Nova components and it also does the remote procedure calls and inter-process communication.
Concept: Fakes

7
doc/source/up.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
BR=$1
DEV=$2
MTU=$3
/sbin/ifconfig $DEV mtu $MTU promisc up
/usr/sbin/brctl addif $BR $DEV

View File

View File

@@ -23,7 +23,7 @@ mkdir -p projects/$NAME
cd projects/$NAME
cp ../../openssl.cnf.tmpl openssl.cnf
sed -i -e s/%USERNAME%/$NAME/g openssl.cnf
mkdir certs crl newcerts private
mkdir -p certs crl newcerts private
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
echo "10" > serial
touch index.txt

View File

@@ -20,8 +20,9 @@ if [ -f "cacert.pem" ];
then
echo "Not installing, it's already done."
else
cp openssl.cnf.tmpl openssl.cnf
cp "$(dirname $0)/openssl.cnf.tmpl" openssl.cnf
sed -i -e s/%USERNAME%/ROOT/g openssl.cnf
mkdir -p certs crl newcerts private
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
touch index.txt
echo "10" > serial

View File

@@ -41,9 +41,13 @@ nameopt = default_ca
certopt = default_ca
policy = policy_match
# NOTE(dprince): stateOrProvinceName must be 'supplied' or 'optional' to
# work around a stateOrProvince printable string UTF8 mismatch on
# RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or
# openssl-1.0.0d-1.fc14.x86_64)
[ policy_match ]
countryName = match
stateOrProvinceName = match
stateOrProvinceName = supplied
organizationName = optional
organizationalUnitName = optional
commonName = supplied

View File

@@ -1,473 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
"""
Nova User API client library.
"""
import base64
import boto
import boto.exception
import httplib
import re
import string
from boto.ec2.regioninfo import RegionInfo
DEFAULT_CLC_URL = 'http://127.0.0.1:8773'
DEFAULT_REGION = 'nova'
class UserInfo(object):
"""
Information about a Nova user, as parsed through SAX.
**Fields Include**
* username
* accesskey
* secretkey
* file (optional) containing zip of X509 cert & rc file
"""
def __init__(self, connection=None, username=None, endpoint=None):
self.connection = connection
self.username = username
self.endpoint = endpoint
def __repr__(self):
return 'UserInfo:%s' % self.username
def startElement(self, name, attrs, connection):
return None
def endElement(self, name, value, connection):
if name == 'username':
self.username = str(value)
elif name == 'file':
self.file = base64.b64decode(str(value))
elif name == 'accesskey':
self.accesskey = str(value)
elif name == 'secretkey':
self.secretkey = str(value)
class UserRole(object):
"""
Information about a Nova user's role, as parsed through SAX.
**Fields include**
* role
"""
def __init__(self, connection=None):
self.connection = connection
self.role = None
def __repr__(self):
return 'UserRole:%s' % self.role
def startElement(self, name, attrs, connection):
return None
def endElement(self, name, value, connection):
if name == 'role':
self.role = value
else:
setattr(self, name, str(value))
class ProjectInfo(object):
"""
Information about a Nova project, as parsed through SAX.
**Fields include**
* projectname
* description
* projectManagerId
* memberIds
"""
def __init__(self, connection=None):
self.connection = connection
self.projectname = None
self.description = None
self.projectManagerId = None
self.memberIds = []
def __repr__(self):
return 'ProjectInfo:%s' % self.projectname
def startElement(self, name, attrs, connection):
return None
def endElement(self, name, value, connection):
if name == 'projectname':
self.projectname = value
elif name == 'description':
self.description = value
elif name == 'projectManagerId':
self.projectManagerId = value
elif name == 'memberId':
self.memberIds.append(value)
else:
setattr(self, name, str(value))
class ProjectMember(object):
"""
Information about a Nova project member, as parsed through SAX.
**Fields include**
* memberId
"""
def __init__(self, connection=None):
self.connection = connection
self.memberId = None
def __repr__(self):
return 'ProjectMember:%s' % self.memberId
def startElement(self, name, attrs, connection):
return None
def endElement(self, name, value, connection):
if name == 'member':
self.memberId = value
else:
setattr(self, name, str(value))
class HostInfo(object):
"""
Information about a Nova Host, as parsed through SAX.
**Fields Include**
* Hostname
* Compute service status
* Volume service status
* Instance count
* Volume count
"""
def __init__(self, connection=None):
self.connection = connection
self.hostname = None
self.compute = None
self.volume = None
self.instance_count = 0
self.volume_count = 0
def __repr__(self):
return 'Host:%s' % self.hostname
# this is needed by the sax parser, so ignore the ugly name
def startElement(self, name, attrs, connection):
return None
# this is needed by the sax parser, so ignore the ugly name
def endElement(self, name, value, connection):
fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name))
setattr(self, fixed_name, value)
class Vpn(object):
"""
Information about a Vpn, as parsed through SAX
**Fields Include**
* instance_id
* project_id
* public_ip
* public_port
* created_at
* internal_ip
* state
"""
def __init__(self, connection=None):
self.connection = connection
self.instance_id = None
self.project_id = None
def __repr__(self):
return 'Vpn:%s:%s' % (self.project_id, self.instance_id)
def startElement(self, name, attrs, connection):
return None
def endElement(self, name, value, connection):
fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name))
setattr(self, fixed_name, value)
class InstanceType(object):
"""
Information about a Nova instance type, as parsed through SAX.
**Fields include**
* name
* vcpus
* disk_gb
* memory_mb
* flavor_id
"""
def __init__(self, connection=None):
self.connection = connection
self.name = None
self.vcpus = None
self.disk_gb = None
self.memory_mb = None
self.flavor_id = None
def __repr__(self):
return 'InstanceType:%s' % self.name
def startElement(self, name, attrs, connection):
return None
def endElement(self, name, value, connection):
if name == "memoryMb":
self.memory_mb = str(value)
elif name == "flavorId":
self.flavor_id = str(value)
elif name == "diskGb":
self.disk_gb = str(value)
else:
setattr(self, name, str(value))
class NovaAdminClient(object):
def __init__(
self,
clc_url=DEFAULT_CLC_URL,
region=DEFAULT_REGION,
access_key=None,
secret_key=None,
**kwargs):
parts = self.split_clc_url(clc_url)
self.clc_url = clc_url
self.region = region
self.access = access_key
self.secret = secret_key
self.apiconn = boto.connect_ec2(aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
is_secure=parts['is_secure'],
region=RegionInfo(None,
region,
parts['ip']),
port=parts['port'],
path='/services/Admin',
**kwargs)
self.apiconn.APIVersion = 'nova'
def connection_for(self, username, project, clc_url=None, region=None,
**kwargs):
"""Returns a boto ec2 connection for the given username."""
if not clc_url:
clc_url = self.clc_url
if not region:
region = self.region
parts = self.split_clc_url(clc_url)
user = self.get_user(username)
access_key = '%s:%s' % (user.accesskey, project)
return boto.connect_ec2(aws_access_key_id=access_key,
aws_secret_access_key=user.secretkey,
is_secure=parts['is_secure'],
region=RegionInfo(None,
self.region,
parts['ip']),
port=parts['port'],
path='/services/Cloud',
**kwargs)
def split_clc_url(self, clc_url):
"""Splits a cloud controller endpoint url."""
parts = httplib.urlsplit(clc_url)
is_secure = parts.scheme == 'https'
ip, port = parts.netloc.split(':')
return {'ip': ip, 'port': int(port), 'is_secure': is_secure}
def get_users(self):
"""Grabs the list of all users."""
return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)])
def get_user(self, name):
"""Grab a single user by name."""
user = self.apiconn.get_object('DescribeUser',
{'Name': name},
UserInfo)
if user.username != None:
return user
def has_user(self, username):
"""Determine if user exists."""
return self.get_user(username) != None
def create_user(self, username):
"""Creates a new user, returning the userinfo object with
access/secret."""
return self.apiconn.get_object('RegisterUser', {'Name': username},
UserInfo)
def delete_user(self, username):
"""Deletes a user."""
return self.apiconn.get_object('DeregisterUser', {'Name': username},
UserInfo)
def get_roles(self, project_roles=True):
"""Returns a list of available roles."""
return self.apiconn.get_list('DescribeRoles',
{'ProjectRoles': project_roles},
[('item', UserRole)])
def get_user_roles(self, user, project=None):
"""Returns a list of roles for the given user.
Omitting project will return any global roles that the user has.
Specifying project will return only project specific roles.
"""
params = {'User': user}
if project:
params['Project'] = project
return self.apiconn.get_list('DescribeUserRoles',
params,
[('item', UserRole)])
def add_user_role(self, user, role, project=None):
"""Add a role to a user either globally or for a specific project."""
return self.modify_user_role(user, role, project=project,
operation='add')
def remove_user_role(self, user, role, project=None):
"""Remove a role from a user either globally or for a specific
project."""
return self.modify_user_role(user, role, project=project,
operation='remove')
def modify_user_role(self, user, role, project=None, operation='add',
**kwargs):
"""Add or remove a role for a user and project."""
params = {'User': user,
'Role': role,
'Project': project,
'Operation': operation}
return self.apiconn.get_status('ModifyUserRole', params)
def get_projects(self, user=None):
"""Returns a list of all projects."""
if user:
params = {'User': user}
else:
params = {}
return self.apiconn.get_list('DescribeProjects',
params,
[('item', ProjectInfo)])
def get_project(self, name):
"""Returns a single project with the specified name."""
project = self.apiconn.get_object('DescribeProject',
{'Name': name},
ProjectInfo)
if project.projectname != None:
return project
def create_project(self, projectname, manager_user, description=None,
member_users=None):
"""Creates a new project."""
params = {'Name': projectname,
'ManagerUser': manager_user,
'Description': description,
'MemberUsers': member_users}
return self.apiconn.get_object('RegisterProject', params, ProjectInfo)
def modify_project(self, projectname, manager_user=None, description=None):
"""Modifies an existing project."""
params = {'Name': projectname,
'ManagerUser': manager_user,
'Description': description}
return self.apiconn.get_status('ModifyProject', params)
def delete_project(self, projectname):
"""Permanently deletes the specified project."""
return self.apiconn.get_object('DeregisterProject',
{'Name': projectname},
ProjectInfo)
def get_project_members(self, name):
"""Returns a list of members of a project."""
return self.apiconn.get_list('DescribeProjectMembers',
{'Name': name},
[('item', ProjectMember)])
def add_project_member(self, user, project):
"""Adds a user to a project."""
return self.modify_project_member(user, project, operation='add')
def remove_project_member(self, user, project):
"""Removes a user from a project."""
return self.modify_project_member(user, project, operation='remove')
def modify_project_member(self, user, project, operation='add'):
"""Adds or removes a user from a project."""
params = {'User': user,
'Project': project,
'Operation': operation}
return self.apiconn.get_status('ModifyProjectMember', params)
def get_zip(self, user, project):
"""Returns the content of a zip file containing novarc and access
credentials."""
params = {'Name': user, 'Project': project}
zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo)
return zip.file
def start_vpn(self, project):
"""
Starts the vpn for a user
"""
return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn)
def get_vpns(self):
"""Return a list of vpn with project name"""
return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)])
def get_hosts(self):
return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)])
def get_instance_types(self):
"""Grabs the list of all users."""
return self.apiconn.get_list('DescribeInstanceTypes', {},
[('item', InstanceType)])

View File

@@ -15,5 +15,3 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""No-op __init__ for directory full of api goodies."""

View File

@@ -44,14 +44,33 @@ from nova import utils
from nova import wsgi
# Global storage for registering modules.
ROUTES = {}
def register_service(path, handle):
"""Register a service handle at a given path.
Services registered in this way will be made available to any instances of
nova.api.direct.Router.
:param path: `routes` path, can be a basic string like "/path"
:param handle: an object whose methods will be made available via the api
"""
ROUTES[path] = handle
class Router(wsgi.Router):
"""A simple WSGI router configured via `register_service`.
This is a quick way to attach multiple services to a given endpoint.
It will automatically load the routes registered in the `ROUTES` global.
TODO(termie): provide a paste-deploy version of this.
"""
def __init__(self, mapper=None):
if mapper is None:
mapper = routes.Mapper()
@@ -66,6 +85,24 @@ class Router(wsgi.Router):
class DelegatedAuthMiddleware(wsgi.Middleware):
"""A simple and naive authentication middleware.
Designed mostly to provide basic support for alternative authentication
schemes, this middleware only desires the identity of the user and will
generate the appropriate nova.context.RequestContext for the rest of the
application. This allows any middleware above it in the stack to
authenticate however it would like while only needing to conform to a
minimal interface.
Expects two headers to determine identity:
- X-OpenStack-User
- X-OpenStack-Project
This middleware is tied to identity management and will need to be kept
in sync with any changes to the way identity is dealt with internally.
"""
def process_request(self, request):
os_user = request.headers['X-OpenStack-User']
os_project = request.headers['X-OpenStack-Project']
@@ -74,6 +111,20 @@ class DelegatedAuthMiddleware(wsgi.Middleware):
class JsonParamsMiddleware(wsgi.Middleware):
"""Middleware to allow method arguments to be passed as serialized JSON.
Accepting arguments as JSON is useful for accepting data that may be more
complex than simple primitives.
In this case we accept it as urlencoded data under the key 'json' as in
json=<urlencoded_json> but this could be extended to accept raw JSON
in the POST body.
Filters out the parameters `self`, `context` and anything beginning with
an underscore.
"""
def process_request(self, request):
if 'json' not in request.params:
return
@@ -92,6 +143,13 @@ class JsonParamsMiddleware(wsgi.Middleware):
class PostParamsMiddleware(wsgi.Middleware):
"""Middleware to allow method arguments to be passed as POST parameters.
Filters out the parameters `self`, `context` and anything beginning with
an underscore.
"""
def process_request(self, request):
params_parsed = request.params
params = {}
@@ -106,12 +164,21 @@ class PostParamsMiddleware(wsgi.Middleware):
class Reflection(object):
"""Reflection methods to list available methods."""
"""Reflection methods to list available methods.
This is an object that expects to be registered via register_service.
These methods allow the endpoint to be self-describing. They introspect
the exposed methods and provide call signatures and documentation for
them allowing quick experimentation.
"""
def __init__(self):
self._methods = {}
self._controllers = {}
def _gather_methods(self):
"""Introspect available methods and generate documentation for them."""
methods = {}
controllers = {}
for route, handler in ROUTES.iteritems():
@@ -185,6 +252,16 @@ class Reflection(object):
class ServiceWrapper(wsgi.Controller):
"""Wrapper to dynamically povide a WSGI controller for arbitrary objects.
With lightweight introspection allows public methods on the object to
be accesed via simple WSGI routing and parameters and serializes the
return values.
Automatically used be nova.api.direct.Router to wrap registered instances.
"""
def __init__(self, service_handle):
self.service_handle = service_handle
@@ -206,10 +283,14 @@ class ServiceWrapper(wsgi.Controller):
# NOTE(vish): make sure we have no unicode keys for py2.6.
params = dict([(str(k), v) for (k, v) in params.iteritems()])
result = method(context, **params)
if result is None or type(result) is str or type(result) is unicode:
return result
try:
return self._serialize(result, req.best_match_content_type())
content_type = req.best_match_content_type()
default_xmlns = self.get_default_xmlns(req)
return self._serialize(result, content_type, default_xmlns)
except:
raise exception.Error("returned non-serializable type: %s"
% result)
@@ -256,7 +337,16 @@ class Limited(object):
class Proxy(object):
"""Pretend a Direct API endpoint is an object."""
"""Pretend a Direct API endpoint is an object.
This is mostly useful in testing at the moment though it should be easily
extendable to provide a basic API library functionality.
In testing we use this to stub out internal objects to verify that results
from the API are serializable.
"""
def __init__(self, app, prefix=None):
self.app = app
self.prefix = prefix

View File

@@ -196,7 +196,7 @@ class APIRequest(object):
elif isinstance(data, datetime.datetime):
data_el.appendChild(
xml.createTextNode(_database_to_isoformat(data)))
elif data != None:
elif data is not None:
data_el.appendChild(xml.createTextNode(str(data)))
return data_el

View File

@@ -103,10 +103,18 @@ class CloudController(object):
# Gen root CA, if we don't have one
root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file)
if not os.path.exists(root_ca_path):
genrootca_sh_path = os.path.join(os.path.dirname(__file__),
os.path.pardir,
os.path.pardir,
'CA',
'genrootca.sh')
start = os.getcwd()
if not os.path.exists(FLAGS.ca_path):
os.makedirs(FLAGS.ca_path)
os.chdir(FLAGS.ca_path)
# TODO(vish): Do this with M2Crypto instead
utils.runthis(_("Generating root CA: %s"), "sh", "genrootca.sh")
utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path)
os.chdir(start)
def _get_mpi_data(self, context, project_id):
@@ -134,6 +142,11 @@ class CloudController(object):
instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)
if instance_ref is None:
return None
# This ensures that all attributes of the instance
# are populated.
instance_ref = db.instance_get(ctxt, instance_ref['id'])
mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])
if instance_ref['key_name']:
keys = {'0': {'_name': instance_ref['key_name'],
@@ -146,7 +159,7 @@ class CloudController(object):
floating_ip = db.instance_get_floating_address(ctxt,
instance_ref['id'])
ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine')
image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami')
data = {
'user-data': base64.b64decode(instance_ref['user_data']),
'meta-data': {
@@ -174,9 +187,9 @@ class CloudController(object):
'mpi': mpi}}
for image_type in ['kernel', 'ramdisk']:
if '%s_id' % image_type in instance_ref:
if instance_ref.get('%s_id' % image_type):
ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type],
image_type)
self._image_type(image_type))
data['meta-data']['%s-id' % image_type] = ec2_id
if False: # TODO(vish): store ancestor ids
@@ -429,7 +442,7 @@ class CloudController(object):
group_name)
criteria = self._revoke_rule_args_to_dict(context, **kwargs)
if criteria == None:
if criteria is None:
raise exception.ApiError(_("Not enough parameters to build a "
"valid rule."))
@@ -600,7 +613,7 @@ class CloudController(object):
# TODO(vish): Instance should be None at db layer instead of
# trying to lazy load, but for now we turn it into
# a dict to avoid an error.
return {'volumeSet': [self._format_volume(context, dict(volume))]}
return self._format_volume(context, dict(volume))
def delete_volume(self, context, volume_id, **kwargs):
volume_id = ec2utils.ec2_id_to_id(volume_id)
@@ -651,7 +664,7 @@ class CloudController(object):
'volumeId': ec2utils.id_to_ec2_id(volume_id, 'vol-%08x')}
def _convert_to_set(self, lst, label):
if lst == None or lst == []:
if lst is None or lst == []:
return None
if not isinstance(lst, list):
lst = [lst]
@@ -713,7 +726,9 @@ class CloudController(object):
instance['mac_address'])
i['privateDnsName'] = fixed_addr
i['privateIpAddress'] = fixed_addr
i['publicDnsName'] = floating_addr
i['ipAddress'] = floating_addr or fixed_addr
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
i['keyName'] = instance['key_name']
@@ -722,7 +737,10 @@ class CloudController(object):
instance['project_id'],
instance['host'])
i['productCodesSet'] = self._convert_to_set([], 'product_codes')
i['instanceType'] = instance['instance_type']
if instance['instance_type']:
i['instanceType'] = instance['instance_type'].get('name')
else:
i['instanceType'] = None
i['launchTime'] = instance['created_at']
i['amiLaunchIndex'] = instance['launch_index']
i['displayName'] = instance['display_name']
@@ -757,6 +775,8 @@ class CloudController(object):
iterator = db.floating_ip_get_all_by_project(context,
context.project_id)
for floating_ip_ref in iterator:
if floating_ip_ref['project_id'] is None:
continue
address = floating_ip_ref['address']
ec2_id = None
if (floating_ip_ref['fixed_ip']
@@ -775,7 +795,7 @@ class CloudController(object):
def allocate_address(self, context, **kwargs):
LOG.audit(_("Allocate address"), context=context)
public_ip = self.network_api.allocate_floating_ip(context)
return {'addressSet': [{'publicIp': public_ip}]}
return {'publicIp': public_ip}
def release_address(self, context, public_ip, **kwargs):
LOG.audit(_("Release address %s"), public_ip, context=context)
@@ -805,7 +825,7 @@ class CloudController(object):
ramdisk = self._get_image(context, kwargs['ramdisk_id'])
kwargs['ramdisk_id'] = ramdisk['id']
instances = self.compute_api.create(context,
instance_type=instance_types.get_by_type(
instance_type=instance_types.get_instance_type_by_name(
kwargs.get('instance_type', None)),
image_id=self._get_image(context, kwargs['image_id'])['id'],
min_count=int(kwargs.get('min_count', max_count)),
@@ -862,13 +882,27 @@ class CloudController(object):
self.compute_api.update(context, instance_id=instance_id, **kwargs)
return True
_type_prefix_map = {'machine': 'ami',
'kernel': 'aki',
'ramdisk': 'ari'}
@staticmethod
def _image_type(image_type):
"""Converts to a three letter image type.
def _image_ec2_id(self, image_id, image_type='machine'):
prefix = self._type_prefix_map[image_type]
template = prefix + '-%08x'
aki, kernel => aki
ari, ramdisk => ari
anything else => ami
"""
if image_type == 'kernel':
return 'aki'
if image_type == 'ramdisk':
return 'ari'
if image_type not in ['aki', 'ari']:
return 'ami'
return image_type
@staticmethod
def _image_ec2_id(image_id, image_type='ami'):
"""Returns image ec2_id using id and three letter type."""
template = image_type + '-%08x'
return ec2utils.id_to_ec2_id(int(image_id), template=template)
def _get_image(self, context, ec2_id):
@@ -876,31 +910,42 @@ class CloudController(object):
internal_id = ec2utils.ec2_id_to_id(ec2_id)
return self.image_service.show(context, internal_id)
except exception.NotFound:
return self.image_service.show_by_name(context, ec2_id)
try:
return self.image_service.show_by_name(context, ec2_id)
except exception.NotFound:
raise exception.NotFound(_('Image %s not found') % ec2_id)
def _format_image(self, image):
"""Convert from format defined by BaseImageService to S3 format."""
i = {}
image_type = image['properties'].get('type')
image_type = self._image_type(image.get('container_format'))
ec2_id = self._image_ec2_id(image.get('id'), image_type)
name = image.get('name')
if name:
i['imageId'] = "%s (%s)" % (ec2_id, name)
else:
i['imageId'] = ec2_id
i['imageId'] = ec2_id
kernel_id = image['properties'].get('kernel_id')
if kernel_id:
i['kernelId'] = self._image_ec2_id(kernel_id, 'kernel')
i['kernelId'] = self._image_ec2_id(kernel_id, 'aki')
ramdisk_id = image['properties'].get('ramdisk_id')
if ramdisk_id:
i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ramdisk')
i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari')
i['imageOwnerId'] = image['properties'].get('owner_id')
i['imageLocation'] = image['properties'].get('image_location')
i['imageState'] = image['properties'].get('image_state')
i['displayName'] = image.get('name')
if name:
i['imageLocation'] = "%s (%s)" % (image['properties'].
get('image_location'), name)
else:
i['imageLocation'] = image['properties'].get('image_location')
# NOTE(vish): fallback status if image_state isn't set
state = image.get('status')
if state == 'active':
state = 'available'
i['imageState'] = image['properties'].get('image_state', state)
i['displayName'] = name
i['description'] = image.get('description')
i['type'] = image_type
i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True'
display_mapping = {'aki': 'kernel',
'ari': 'ramdisk',
'ami': 'machine'}
i['imageType'] = display_mapping.get(image_type)
i['isPublic'] = image.get('is_public') == True
i['architecture'] = image['properties'].get('architecture')
return i
@@ -932,8 +977,9 @@ class CloudController(object):
image_location = kwargs['name']
metadata = {'properties': {'image_location': image_location}}
image = self.image_service.create(context, metadata)
image_type = self._image_type(image.get('container_format'))
image_id = self._image_ec2_id(image['id'],
image['properties']['type'])
image_type)
msg = _("Registered image %(image_location)s with"
" id %(image_id)s") % locals()
LOG.audit(msg, context=context)
@@ -948,7 +994,7 @@ class CloudController(object):
except exception.NotFound:
raise exception.NotFound(_('Image %s not found') % image_id)
result = {'imageId': image_id, 'launchPermission': []}
if image['properties']['is_public']:
if image['is_public']:
result['launchPermission'].append({'group': 'all'})
return result
@@ -973,7 +1019,7 @@ class CloudController(object):
internal_id = image['id']
del(image['id'])
image['properties']['is_public'] = (operation_type == 'add')
image['is_public'] = (operation_type == 'add')
return self.image_service.update(context, internal_id, image)
def update_image(self, context, image_id, **kwargs):

View File

@@ -34,6 +34,7 @@ from nova.api.openstack import consoles
from nova.api.openstack import flavors
from nova.api.openstack import images
from nova.api.openstack import image_metadata
from nova.api.openstack import ips
from nova.api.openstack import limits
from nova.api.openstack import servers
from nova.api.openstack import server_metadata
@@ -144,6 +145,11 @@ class APIRouterV10(APIRouter):
parent_resource=dict(member_name='server',
collection_name='servers'))
mapper.resource("ip", "ips", controller=ips.Controller(),
collection=dict(public='GET', private='GET'),
parent_resource=dict(member_name='server',
collection_name='servers'))
class APIRouterV11(APIRouter):
"""Define routes specific to OpenStack API V1.1."""

View File

@@ -13,15 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import common
import webob.exc
from nova import exception
from nova import flags
from nova import log as logging
from nova import wsgi
from nova.auth import manager
from nova.api.openstack import common
from nova.api.openstack import faults
FLAGS = flags.FLAGS
@@ -35,7 +34,7 @@ def _translate_keys(account):
manager=account.project_manager_id)
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
_serialization_metadata = {
'application/xml': {

View File

@@ -55,6 +55,9 @@ class AuthMiddleware(wsgi.Middleware):
user = self.get_user_by_authentication(req)
accounts = self.auth.get_projects(user=user)
if not user:
token = req.headers["X-Auth-Token"]
msg = _("%(user)s could not be found with token '%(token)s'")
LOG.warn(msg % locals())
return faults.Fault(webob.exc.HTTPUnauthorized())
if accounts:
@@ -66,6 +69,8 @@ class AuthMiddleware(wsgi.Middleware):
if not self.auth.is_admin(user) and \
not self.auth.is_project_member(user, account):
msg = _("%(user)s must be an admin or a member of %(account)s")
LOG.warn(msg % locals())
return faults.Fault(webob.exc.HTTPUnauthorized())
req.environ['nova.context'] = context.RequestContext(user, account)
@@ -82,12 +87,16 @@ class AuthMiddleware(wsgi.Middleware):
# honor it
path_info = req.path_info
if len(path_info) > 1:
return faults.Fault(webob.exc.HTTPUnauthorized())
msg = _("Authentication requests must be made against a version "
"root (e.g. /v1.0 or /v1.1).")
LOG.warn(msg)
return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg))
try:
username = req.headers['X-Auth-User']
key = req.headers['X-Auth-Key']
except KeyError:
except KeyError as ex:
LOG.warn(_("Could not find %s in request.") % ex)
return faults.Fault(webob.exc.HTTPUnauthorized())
token, user = self._authorize_user(username, key, req)
@@ -100,6 +109,7 @@ class AuthMiddleware(wsgi.Middleware):
res.headers['X-CDN-Management-Url'] = token.cdn_management_url
res.content_type = 'text/plain'
res.status = '204'
LOG.debug(_("Successfully authenticated '%s'") % username)
return res
else:
return faults.Fault(webob.exc.HTTPUnauthorized())
@@ -139,6 +149,7 @@ class AuthMiddleware(wsgi.Middleware):
try:
user = self.auth.get_user_from_access_key(key)
except exception.NotFound:
LOG.warn(_("User not found with provided API key."))
user = None
if user and user.name == username:
@@ -153,4 +164,9 @@ class AuthMiddleware(wsgi.Middleware):
token_dict['user_id'] = user.id
token = self.db.auth_token_create(ctxt, token_dict)
return token, user
elif user and user.name != username:
msg = _("Provided API key is valid, but not for user "
"'%(username)s'") % locals()
LOG.warn(msg)
return None, None

View File

@@ -19,7 +19,7 @@ import time
from webob import exc
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
import nova.image.service
@@ -29,7 +29,7 @@ def _translate_keys(inst):
return dict(backupSchedule=inst)
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
""" The backup schedule API controller for the Openstack API """
_serialization_metadata = {

View File

@@ -22,14 +22,19 @@ import webob
from nova import exception
from nova import flags
from nova import log as logging
from nova import wsgi
LOG = logging.getLogger('common')
LOG = logging.getLogger('nova.api.openstack.common')
FLAGS = flags.FLAGS
XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
def limited(items, request, max_limit=FLAGS.osapi_max_limit):
"""
Return a slice of items according to requested offset and limit.
@@ -111,8 +116,14 @@ def get_image_id_from_image_hash(image_service, context, image_hash):
items = image_service.index(context)
for image in items:
image_id = image['id']
if abs(hash(image_id)) == int(image_hash):
return image_id
try:
if abs(hash(image_id)) == int(image_hash):
return image_id
except ValueError:
msg = _("Requested image_id has wrong format: %s,"
"should have numerical format") % image_id
LOG.error(msg)
raise Exception(msg)
raise exception.NotFound(image_hash)
@@ -128,3 +139,9 @@ def get_id_from_href(href):
except:
LOG.debug(_("Error extracting id from href: %s") % href)
raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
class OpenstackController(wsgi.Controller):
def get_default_xmlns(self, req):
# Use V10 by default
return XML_NS_V10

View File

@@ -19,7 +19,7 @@ from webob import exc
from nova import console
from nova import exception
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
@@ -43,7 +43,7 @@ def _translate_detail_keys(cons):
return dict(console=info)
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
"""The Consoles Controller for the Openstack API"""
_serialization_metadata = {

View File

@@ -322,8 +322,7 @@ class Volumes(extensions.ExtensionDescriptor):
# Does this matter?
res = extensions.ResourceExtension('volumes',
VolumeController(),
collection_actions={'detail': 'GET'}
)
collection_actions={'detail': 'GET'})
resources.append(res)
res = extensions.ResourceExtension('volume_attachments',

View File

@@ -28,6 +28,7 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
@@ -115,7 +116,7 @@ class ExtensionDescriptor(object):
return response_exts
class ActionExtensionController(wsgi.Controller):
class ActionExtensionController(common.OpenstackController):
def __init__(self, application):
@@ -136,7 +137,7 @@ class ActionExtensionController(wsgi.Controller):
return res
class ResponseExtensionController(wsgi.Controller):
class ResponseExtensionController(common.OpenstackController):
def __init__(self, application):
self.application = application
@@ -155,7 +156,8 @@ class ResponseExtensionController(wsgi.Controller):
body = res.body
headers = res.headers
except AttributeError:
body = self._serialize(res, content_type)
default_xmlns = None
body = self._serialize(res, content_type, default_xmlns)
headers = {"Content-Type": content_type}
res = webob.Response()
res.body = body
@@ -163,7 +165,7 @@ class ResponseExtensionController(wsgi.Controller):
return res
class ExtensionController(wsgi.Controller):
class ExtensionController(common.OpenstackController):
def __init__(self, extension_manager):
self.extension_manager = extension_manager

View File

@@ -20,10 +20,10 @@ import webob.dec
import webob.exc
from nova import wsgi
from nova.api.openstack import common
class Fault(webob.exc.HTTPException):
"""An RS API fault response."""
_fault_names = {
@@ -47,7 +47,7 @@ class Fault(webob.exc.HTTPException):
"""Generate a WSGI response based on the exception passed to ctor."""
# Replace the body with fault details.
code = self.wrapped_exc.status_int
fault_name = self._fault_names.get(code, "computeFault")
fault_name = self._fault_names.get(code, "cloudServersFault")
fault_data = {
fault_name: {
'code': code,
@@ -57,7 +57,8 @@ class Fault(webob.exc.HTTPException):
fault_data[fault_name]['retryAfter'] = retry
# 'code' is an attribute on the fault tag itself
metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
serializer = wsgi.Serializer(metadata)
default_xmlns = common.XML_NS_V10
serializer = wsgi.Serializer(metadata, default_xmlns)
content_type = req.best_match_content_type()
self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
self.wrapped_exc.content_type = content_type

View File

@@ -19,11 +19,11 @@ import webob
from nova import db
from nova import exception
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import views
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
"""Flavor controller for the OpenStack API."""
_serialization_metadata = {
@@ -76,3 +76,6 @@ class ControllerV11(Controller):
def _get_view_builder(self, req):
base_url = req.application_url
return views.flavors.ViewBuilderV11(base_url)
def get_default_xmlns(self, req):
return common.XML_NS_V11

View File

@@ -18,15 +18,17 @@
from webob import exc
from nova import flags
from nova import quota
from nova import utils
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
FLAGS = flags.FLAGS
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
"""The image metadata API controller for the Openstack API"""
def __init__(self):
@@ -39,6 +41,15 @@ class Controller(wsgi.Controller):
metadata = image.get('properties', {})
return metadata
def _check_quota_limit(self, context, metadata):
if metadata is None:
return
num_metadata = len(metadata)
quota_metadata = quota.allowed_metadata_items(context, num_metadata)
if quota_metadata < num_metadata:
expl = _("Image metadata limit exceeded")
raise exc.HTTPBadRequest(explanation=expl)
def index(self, req, image_id):
"""Returns the list of metadata for a given instance"""
context = req.environ['nova.context']
@@ -61,6 +72,7 @@ class Controller(wsgi.Controller):
if 'metadata' in body:
for key, value in body['metadata'].iteritems():
metadata[key] = value
self._check_quota_limit(context, metadata)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)
return dict(metadata=metadata)
@@ -77,6 +89,7 @@ class Controller(wsgi.Controller):
img = self.image_service.show(context, image_id)
metadata = self._get_metadata(context, image_id, img)
metadata[id] = body[id]
self._check_quota_limit(context, metadata)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)

View File

@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import webob.exc
from nova import compute
@@ -22,7 +20,6 @@ from nova import exception
from nova import flags
from nova import log
from nova import utils
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.api.openstack.views import images as images_view
@@ -32,7 +29,7 @@ LOG = log.getLogger('nova.api.openstack.images')
FLAGS = flags.FLAGS
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
"""Base `wsgi.Controller` for retrieving/displaying images."""
_serialization_metadata = {
@@ -153,3 +150,6 @@ class ControllerV11(Controller):
"""Property to get the ViewBuilder class we need to use."""
base_url = request.application_url
return images_view.ViewBuilderV11(base_url)
def get_default_xmlns(self, req):
return common.XML_NS_V11

72
nova/api/openstack/ips.py Normal file
View File

@@ -0,0 +1,72 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# 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 time
from webob import exc
import nova
import nova.api.openstack.views.addresses
from nova.api.openstack import common
from nova.api.openstack import faults
class Controller(common.OpenstackController):
"""The servers addresses API controller for the Openstack API."""
_serialization_metadata = {
'application/xml': {
'list_collections': {
'public': {'item_name': 'ip', 'item_key': 'addr'},
'private': {'item_name': 'ip', 'item_key': 'addr'},
},
},
}
def __init__(self):
self.compute_api = nova.compute.API()
self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
def index(self, req, server_id):
try:
instance = self.compute_api.get(req.environ['nova.context'], id)
except nova.exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
return {'addresses': self.builder.build(instance)}
def public(self, req, server_id):
try:
instance = self.compute_api.get(req.environ['nova.context'], id)
except nova.exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
return {'public': self.builder.build_public_parts(instance)}
def private(self, req, server_id):
try:
instance = self.compute_api.get(req.environ['nova.context'], id)
except nova.exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
return {'private': self.builder.build_private_parts(instance)}
def show(self, req, server_id, id):
return faults.Fault(exc.HTTPNotImplemented())
def create(self, req, server_id):
return faults.Fault(exc.HTTPNotImplemented())
def delete(self, req, server_id, id):
return faults.Fault(exc.HTTPNotImplemented())

View File

@@ -31,8 +31,8 @@ from collections import defaultdict
from webob.dec import wsgify
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.wsgi import Controller
from nova.wsgi import Middleware
@@ -43,7 +43,7 @@ PER_HOUR = 60 * 60
PER_DAY = 60 * 60 * 24
class LimitsController(Controller):
class LimitsController(common.OpenstackController):
"""
Controller for accessing limits in the OpenStack API.
"""

View File

@@ -18,11 +18,13 @@
from webob import exc
from nova import compute
from nova import quota
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
""" The server metadata API controller for the Openstack API """
def __init__(self):
@@ -43,10 +45,14 @@ class Controller(wsgi.Controller):
def create(self, req, server_id):
context = req.environ['nova.context']
body = self._deserialize(req.body, req.get_content_type())
self.compute_api.update_or_create_instance_metadata(context,
server_id,
body['metadata'])
data = self._deserialize(req.body, req.get_content_type())
metadata = data.get('metadata')
try:
self.compute_api.update_or_create_instance_metadata(context,
server_id,
metadata)
except quota.QuotaError as error:
self._handle_quota_error(error)
return req.body
def update(self, req, server_id, id):
@@ -58,9 +64,13 @@ class Controller(wsgi.Controller):
if len(body) > 1:
expl = _('Request body contains too many items')
raise exc.HTTPBadRequest(explanation=expl)
self.compute_api.update_or_create_instance_metadata(context,
server_id,
body)
try:
self.compute_api.update_or_create_instance_metadata(context,
server_id,
body)
except quota.QuotaError as error:
self._handle_quota_error(error)
return req.body
def show(self, req, server_id, id):
@@ -76,3 +86,9 @@ class Controller(wsgi.Controller):
""" Deletes an existing metadata """
context = req.environ['nova.context']
self.compute_api.delete_instance_metadata(context, server_id, id)
def _handle_quota_error(self, error):
"""Reraise quota errors as api-specific http exceptions."""
if error.code == "MetadataLimitExceeded":
raise exc.HTTPBadRequest(explanation=error.message)
raise error

View File

@@ -40,11 +40,11 @@ import nova.api.openstack
from nova.scheduler import api as scheduler_api
LOG = logging.getLogger('server')
LOG = logging.getLogger('nova.api.openstack.servers')
FLAGS = flags.FLAGS
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
""" The Server API controller for the OpenStack API """
_serialization_metadata = {
@@ -55,6 +55,13 @@ class Controller(wsgi.Controller):
"imageRef"],
"link": ["rel", "type", "href"],
},
"dict_collections": {
"metadata": {"item_name": "meta", "item_key": "key"},
},
"list_collections": {
"public": {"item_name": "ip", "item_key": "addr"},
"private": {"item_name": "ip", "item_key": "addr"},
},
},
}
@@ -63,15 +70,6 @@ class Controller(wsgi.Controller):
self._image_service = utils.import_object(FLAGS.image_service)
super(Controller, self).__init__()
def ips(self, req, id):
try:
instance = self.compute_api.get(req.environ['nova.context'], id)
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
builder = self._get_addresses_view_builder(req)
return builder.build(instance)
def index(self, req):
""" Returns a list of server names and ids for a given user """
return self._items(req, is_detail=False)
@@ -120,6 +118,8 @@ class Controller(wsgi.Controller):
context = req.environ['nova.context']
password = self._get_server_admin_password(env['server'])
key_name = None
key_data = None
key_pairs = auth_manager.AuthManager.get_key_pairs(context)
@@ -129,21 +129,16 @@ class Controller(wsgi.Controller):
key_data = key_pair['public_key']
requested_image_id = self._image_id_from_req_data(env)
image_id = common.get_image_id_from_image_hash(self._image_service,
context, requested_image_id)
try:
image_id = common.get_image_id_from_image_hash(self._image_service,
context, requested_image_id)
except:
msg = _("Can not find requested image")
return faults.Fault(exc.HTTPBadRequest(msg))
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
req, image_id)
# Metadata is a list, not a Dictionary, because we allow duplicate keys
# (even though JSON can't encode this)
# In future, we may not allow duplicate keys.
# However, the CloudServers API is not definitive on this front,
# and we want to be compatible.
metadata = []
if env['server'].get('metadata'):
for k, v in env['server']['metadata'].items():
metadata.append({'key': k, 'value': v})
personality = env['server'].get('personality')
injected_files = []
if personality:
@@ -160,9 +155,11 @@ class Controller(wsgi.Controller):
name = name.strip()
try:
inst_type = \
instance_types.get_instance_type_by_flavor_id(flavor_id)
(inst,) = self.compute_api.create(
context,
instance_types.get_by_flavor_id(flavor_id),
inst_type,
image_id,
kernel_id=kernel_id,
ramdisk_id=ramdisk_id,
@@ -170,18 +167,16 @@ class Controller(wsgi.Controller):
display_description=name,
key_name=key_name,
key_data=key_data,
metadata=metadata,
metadata=env['server'].get('metadata', {}),
injected_files=injected_files)
except quota.QuotaError as error:
self._handle_quota_error(error)
inst['instance_type'] = flavor_id
inst['instance_type'] = inst_type
inst['image_id'] = requested_image_id
builder = self._get_view_builder(req)
server = builder.build(inst, is_detail=True)
password = "%s%s" % (server['server']['name'][:4],
utils.generate_password(12))
server['server']['adminPass'] = password
self.compute_api.set_admin_password(context, server['server']['id'],
password)
@@ -243,6 +238,10 @@ class Controller(wsgi.Controller):
# if the original error is okay, just reraise it
raise error
def _get_server_admin_password(self, server):
""" Determine the admin password for a server on creation """
return utils.generate_password(16)
@scheduler_api.redirect_handler
def update(self, req, id):
""" Updates the server name or password """
@@ -288,11 +287,12 @@ class Controller(wsgi.Controller):
resize a server"""
actions = {
'reboot': self._action_reboot,
'resize': self._action_resize,
'changePassword': self._action_change_password,
'reboot': self._action_reboot,
'resize': self._action_resize,
'confirmResize': self._action_confirm_resize,
'revertResize': self._action_revert_resize,
'rebuild': self._action_rebuild,
'revertResize': self._action_revert_resize,
'rebuild': self._action_rebuild,
}
input_dict = self._deserialize(req.body, req.get_content_type())
@@ -301,6 +301,9 @@ class Controller(wsgi.Controller):
return actions[key](input_dict, req, id)
return faults.Fault(exc.HTTPNotImplemented())
def _action_change_password(self, input_dict, req, id):
return exc.HTTPNotImplemented()
def _action_confirm_resize(self, input_dict, req, id):
try:
self.compute_api.confirm_resize(req.environ['nova.context'], id)
@@ -318,6 +321,7 @@ class Controller(wsgi.Controller):
return exc.HTTPAccepted()
def _action_rebuild(self, input_dict, req, id):
LOG.debug(_("Rebuild server action is not implemented"))
return faults.Fault(exc.HTTPNotImplemented())
def _action_resize(self, input_dict, req, id):
@@ -333,18 +337,20 @@ class Controller(wsgi.Controller):
except Exception, e:
LOG.exception(_("Error in resize %s"), e)
return faults.Fault(exc.HTTPBadRequest())
return faults.Fault(exc.HTTPAccepted())
return exc.HTTPAccepted()
def _action_reboot(self, input_dict, req, id):
try:
if 'reboot' in input_dict and 'type' in input_dict['reboot']:
reboot_type = input_dict['reboot']['type']
except Exception:
raise faults.Fault(exc.HTTPNotImplemented())
else:
LOG.exception(_("Missing argument 'type' for reboot"))
return faults.Fault(exc.HTTPUnprocessableEntity())
try:
# TODO(gundlach): pass reboot_type, support soft reboot in
# virt driver
self.compute_api.reboot(req.environ['nova.context'], id)
except:
except Exception, e:
LOG.exception(_("Error in reboot %s"), e)
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
@@ -562,7 +568,7 @@ class Controller(wsgi.Controller):
_("Cannot build from image %(image_id)s, status not active") %
locals())
if image_meta['properties']['disk_format'] != 'ami':
if image_meta.get('container_format') != 'ami':
return None, None
try:
@@ -629,9 +635,35 @@ class ControllerV11(Controller):
def _get_addresses_view_builder(self, req):
return nova.api.openstack.views.addresses.ViewBuilderV11(req)
def _action_change_password(self, input_dict, req, id):
context = req.environ['nova.context']
if (not 'changePassword' in input_dict
or not 'adminPass' in input_dict['changePassword']):
msg = _("No adminPass was specified")
return exc.HTTPBadRequest(msg)
password = input_dict['changePassword']['adminPass']
if not isinstance(password, basestring) or password == '':
msg = _("Invalid adminPass")
return exc.HTTPBadRequest(msg)
self.compute_api.set_admin_password(context, id, password)
return exc.HTTPAccepted()
def _limit_items(self, items, req):
return common.limited_by_marker(items, req)
def _get_server_admin_password(self, server):
""" Determine the admin password for a server on creation """
password = server.get('adminPass')
if password is None:
return utils.generate_password(16)
if not isinstance(password, basestring) or password == '':
msg = _("Invalid adminPass")
raise exc.HTTPBadRequest(msg)
return password
def get_default_xmlns(self, req):
return common.XML_NS_V11
class ServerCreateRequestXMLDeserializer(object):
"""

View File

@@ -17,7 +17,7 @@
from webob import exc
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
@@ -32,7 +32,7 @@ def _translate_detail_keys(inst):
return dict(sharedIpGroups=inst)
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
""" The Shared IP Groups Controller for the Openstack API """
_serialization_metadata = {

View File

@@ -18,7 +18,6 @@ from webob import exc
from nova import exception
from nova import flags
from nova import log as logging
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.auth import manager
@@ -35,7 +34,7 @@ def _translate_keys(user):
admin=user.admin)
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
_serialization_metadata = {
'application/xml': {

View File

@@ -28,10 +28,16 @@ class ViewBuilder(object):
class ViewBuilderV10(ViewBuilder):
def build(self, inst):
private_ips = utils.get_from_path(inst, 'fixed_ip/address')
public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
private_ips = self.build_private_parts(inst)
public_ips = self.build_public_parts(inst)
return dict(public=public_ips, private=private_ips)
def build_public_parts(self, inst):
return utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
def build_private_parts(self, inst):
return utils.get_from_path(inst, 'fixed_ip/address')
class ViewBuilderV11(ViewBuilder):
def build(self, inst):

View File

@@ -34,11 +34,11 @@ class ViewBuilder(object):
def _format_status(self, image):
"""Update the status field to standardize format."""
status_mapping = {
'pending': 'queued',
'decrypting': 'preparing',
'untarring': 'saving',
'available': 'active',
'killed': 'failed',
'pending': 'QUEUED',
'decrypting': 'PREPARING',
'untarring': 'SAVING',
'available': 'ACTIVE',
'killed': 'FAILED',
}
try:
@@ -60,8 +60,8 @@ class ViewBuilder(object):
self._format_status(image_obj)
image = {
"id": image_obj["id"],
"name": image_obj["name"],
"id": image_obj.get("id"),
"name": image_obj.get("name"),
}
if "instance_id" in properties:
@@ -72,9 +72,9 @@ class ViewBuilder(object):
if detail:
image.update({
"created": image_obj["created_at"],
"updated": image_obj["updated_at"],
"status": image_obj["status"],
"created": image_obj.get("created_at"),
"updated": image_obj.get("updated_at"),
"status": image_obj.get("status"),
})
if image["status"] == "SAVING":

View File

@@ -57,16 +57,16 @@ class ViewBuilder(object):
def _build_detail(self, inst):
"""Returns a detailed model of a server."""
power_mapping = {
None: 'build',
power_state.NOSTATE: 'build',
power_state.RUNNING: 'active',
power_state.BLOCKED: 'active',
power_state.SUSPENDED: 'suspended',
power_state.PAUSED: 'paused',
power_state.SHUTDOWN: 'active',
power_state.SHUTOFF: 'active',
power_state.CRASHED: 'error',
power_state.FAILED: 'error'}
None: 'BUILD',
power_state.NOSTATE: 'BUILD',
power_state.RUNNING: 'ACTIVE',
power_state.BLOCKED: 'ACTIVE',
power_state.SUSPENDED: 'SUSPENDED',
power_state.PAUSED: 'PAUSED',
power_state.SHUTDOWN: 'ACTIVE',
power_state.SHUTOFF: 'ACTIVE',
power_state.CRASHED: 'ERROR',
power_state.FAILED: 'ERROR'}
inst_dict = {
'id': int(inst['id']),
@@ -77,12 +77,12 @@ class ViewBuilder(object):
ctxt = nova.context.get_admin_context()
compute_api = nova.compute.API()
if compute_api.has_finished_migration(ctxt, inst['id']):
inst_dict['status'] = 'resize-confirm'
inst_dict['status'] = 'RESIZE-CONFIRM'
# Return the metadata as a dictionary
metadata = {}
for item in inst.get('metadata', []):
metadata[item['key']] = item['value']
metadata[item['key']] = str(item['value'])
inst_dict['metadata'] = metadata
inst_dict['hostId'] = ''
@@ -115,7 +115,7 @@ class ViewBuilderV10(ViewBuilder):
def _build_flavor(self, response, inst):
if 'instance_type' in dict(inst):
response['flavorId'] = inst['instance_type']
response['flavorId'] = inst['instance_type']['flavorid']
class ViewBuilderV11(ViewBuilder):
@@ -134,7 +134,7 @@ class ViewBuilderV11(ViewBuilder):
def _build_flavor(self, response, inst):
if "instance_type" in dict(inst):
flavor_id = inst["instance_type"]
flavor_id = inst["instance_type"]['flavorid']
flavor_ref = self.flavor_builder.generate_href(flavor_id)
response["flavorRef"] = flavor_ref

View File

@@ -13,12 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import common
from nova import db
from nova import flags
from nova import log as logging
from nova import wsgi
from nova.api.openstack import common
from nova.scheduler import api
@@ -43,7 +41,7 @@ def _scrub_zone(zone):
'deleted', 'deleted_at', 'updated_at'))
class Controller(wsgi.Controller):
class Controller(common.OpenstackController):
_serialization_metadata = {
'application/xml': {

View File

@@ -115,7 +115,7 @@ class DbDriver(object):
# on to create the project. This way we won't have to destroy
# the project again because a user turns out to be invalid.
members = set([manager])
if member_uids != None:
if member_uids is not None:
for member_uid in member_uids:
member = db.user_get(context.get_admin_context(), member_uid)
if not member:

View File

@@ -268,7 +268,7 @@ class AuthManager(object):
LOG.debug(_('Looking up user: %r'), access_key)
user = self.get_user_from_access_key(access_key)
LOG.debug('user: %r', user)
if user == None:
if user is None:
LOG.audit(_("Failed authorization for access key %s"), access_key)
raise exception.NotFound(_('No user found for access key %s')
% access_key)
@@ -280,7 +280,7 @@ class AuthManager(object):
project_id = user.name
project = self.get_project(project_id)
if project == None:
if project is None:
pjid = project_id
uname = user.name
LOG.audit(_("failed authorization: no project named %(pjid)s"
@@ -646,9 +646,9 @@ class AuthManager(object):
@rtype: User
@return: The new user.
"""
if access == None:
if access is None:
access = str(uuid.uuid4())
if secret == None:
if secret is None:
secret = str(uuid.uuid4())
with self.driver() as drv:
user_dict = drv.create_user(name, access, secret, admin)

View File

@@ -16,9 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Handles all requests relating to instances (guest vms).
"""
"""Handles all requests relating to instances (guest vms)."""
import datetime
import re
@@ -37,10 +35,14 @@ from nova.compute import instance_types
from nova.scheduler import api as scheduler_api
from nova.db import base
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.compute.api')
FLAGS = flags.FLAGS
flags.DECLARE('vncproxy_topic', 'nova.vnc')
def generate_default_hostname(instance_id):
"""Default function to generate a hostname given an instance reference."""
return str(instance_id)
@@ -82,10 +84,10 @@ class API(base.Base):
{"method": "get_network_topic", "args": {'fake': 1}})
def _check_injected_file_quota(self, context, injected_files):
"""
Enforce quota limits on injected files
"""Enforce quota limits on injected files.
Raises a QuotaError if any limit is exceeded.
Raises a QuotaError if any limit is exceeded
"""
if injected_files is None:
return
@@ -100,18 +102,45 @@ class API(base.Base):
if len(content) > content_limit:
raise quota.QuotaError(code="OnsetFileContentLimitExceeded")
def _check_metadata_properties_quota(self, context, metadata={}):
"""Enforce quota limits on metadata properties."""
num_metadata = len(metadata)
quota_metadata = quota.allowed_metadata_items(context, num_metadata)
if quota_metadata < num_metadata:
pid = context.project_id
msg = _("Quota exceeeded for %(pid)s, tried to set "
"%(num_metadata)s metadata properties") % locals()
LOG.warn(msg)
raise quota.QuotaError(msg, "MetadataLimitExceeded")
# Because metadata is stored in the DB, we hard-code the size limits
# In future, we may support more variable length strings, so we act
# as if this is quota-controlled for forwards compatibility
for k, v in metadata.iteritems():
if len(k) > 255 or len(v) > 255:
pid = context.project_id
msg = _("Quota exceeeded for %(pid)s, metadata property "
"key or value too long") % locals()
LOG.warn(msg)
raise quota.QuotaError(msg, "MetadataLimitExceeded")
def create(self, context, instance_type,
image_id, kernel_id=None, ramdisk_id=None,
min_count=1, max_count=1,
display_name='', display_description='',
key_name=None, key_data=None, security_group='default',
availability_zone=None, user_data=None, metadata=[],
availability_zone=None, user_data=None, metadata={},
injected_files=None):
"""Create the number of instances requested if quota and
other arguments check out ok."""
"""Create the number and type of instances requested.
type_data = instance_types.get_instance_type(instance_type)
num_instances = quota.allowed_instances(context, max_count, type_data)
Verifies that quota and other arguments are valid.
"""
if not instance_type:
instance_type = instance_types.get_default_instance_type()
num_instances = quota.allowed_instances(context, max_count,
instance_type)
if num_instances < min_count:
pid = context.project_id
LOG.warn(_("Quota exceeeded for %(pid)s,"
@@ -120,30 +149,7 @@ class API(base.Base):
"run %s more instances of this type.") %
num_instances, "InstanceLimitExceeded")
num_metadata = len(metadata)
quota_metadata = quota.allowed_metadata_items(context, num_metadata)
if quota_metadata < num_metadata:
pid = context.project_id
msg = (_("Quota exceeeded for %(pid)s,"
" tried to set %(num_metadata)s metadata properties")
% locals())
LOG.warn(msg)
raise quota.QuotaError(msg, "MetadataLimitExceeded")
# Because metadata is stored in the DB, we hard-code the size limits
# In future, we may support more variable length strings, so we act
# as if this is quota-controlled for forwards compatibility
for metadata_item in metadata:
k = metadata_item['key']
v = metadata_item['value']
if len(k) > 255 or len(v) > 255:
pid = context.project_id
msg = (_("Quota exceeeded for %(pid)s,"
" metadata property key or value too long")
% locals())
LOG.warn(msg)
raise quota.QuotaError(msg, "MetadataLimitExceeded")
self._check_metadata_properties_quota(context, metadata)
self._check_injected_file_quota(context, injected_files)
image = self.image_service.show(context, image_id)
@@ -197,10 +203,10 @@ class API(base.Base):
'user_id': context.user_id,
'project_id': context.project_id,
'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
'instance_type': instance_type,
'memory_mb': type_data['memory_mb'],
'vcpus': type_data['vcpus'],
'local_gb': type_data['local_gb'],
'instance_type_id': instance_type['id'],
'memory_mb': instance_type['memory_mb'],
'vcpus': instance_type['vcpus'],
'local_gb': instance_type['local_gb'],
'display_name': display_name,
'display_description': display_description,
'user_data': user_data or '',
@@ -231,7 +237,7 @@ class API(base.Base):
# Set sane defaults if not specified
updates = dict(hostname=self.hostname_factory(instance_id))
if (not hasattr(instance, 'display_name') or
instance.display_name == None):
instance.display_name is None):
updates['display_name'] = "Server %s" % instance_id
instance = self.update(context, instance_id, **updates)
@@ -255,8 +261,7 @@ class API(base.Base):
return [dict(x.iteritems()) for x in instances]
def has_finished_migration(self, context, instance_id):
"""Retrieves whether or not a finished migration exists for
an instance"""
"""Returns true if an instance has a finished migration."""
try:
db.migration_get_by_instance_and_status(context, instance_id,
'finished')
@@ -265,8 +270,10 @@ class API(base.Base):
return False
def ensure_default_security_group(self, context):
""" Create security group for the security context if it
does not already exist
"""Ensure that a context has a security group.
Creates a security group for the security context if it does not
already exist.
:param context: the security context
@@ -282,7 +289,7 @@ class API(base.Base):
db.security_group_create(context, values)
def trigger_security_group_rules_refresh(self, context, security_group_id):
"""Called when a rule is added to or removed from a security_group"""
"""Called when a rule is added to or removed from a security_group."""
security_group = self.db.security_group_get(context, security_group_id)
@@ -298,11 +305,12 @@ class API(base.Base):
"args": {"security_group_id": security_group.id}})
def trigger_security_group_members_refresh(self, context, group_id):
"""Called when a security group gains a new or loses a member
"""Called when a security group gains a new or loses a member.
Sends an update request to each compute node for whom this is
relevant."""
relevant.
"""
# First, we get the security group rules that reference this group as
# the grantee..
security_group_rules = \
@@ -347,7 +355,7 @@ class API(base.Base):
as data fields of the instance to be
updated
:retval None
:returns: None
"""
rv = self.db.instance_update(context, instance_id, kwargs)
@@ -355,6 +363,7 @@ class API(base.Base):
@scheduler_api.reroute_compute("delete")
def delete(self, context, instance_id):
"""Terminate an instance."""
LOG.debug(_("Going to try to terminate %s"), instance_id)
try:
instance = self.get(context, instance_id)
@@ -363,11 +372,15 @@ class API(base.Base):
instance_id)
raise
if (instance['state_description'] == 'terminating'):
if instance['state_description'] == 'terminating':
LOG.warning(_("Instance %s is already being terminated"),
instance_id)
return
if instance['state_description'] == 'migrating':
LOG.warning(_("Instance %s is being migrated"), instance_id)
return
self.update(context,
instance['id'],
state_description='terminating',
@@ -382,22 +395,28 @@ class API(base.Base):
self.db.instance_destroy(context, instance_id)
def get(self, context, instance_id):
"""Get a single instance with the given ID."""
"""Get a single instance with the given instance_id."""
rv = self.db.instance_get(context, instance_id)
return dict(rv.iteritems())
@scheduler_api.reroute_compute("get")
def routing_get(self, context, instance_id):
"""Use this method instead of get() if this is the only
operation you intend to to. It will route to novaclient.get
if the instance is not found."""
"""A version of get with special routing characteristics.
Use this method instead of get() if this is the only operation you
intend to to. It will route to novaclient.get if the instance is not
found.
"""
return self.get(context, instance_id)
def get_all(self, context, project_id=None, reservation_id=None,
fixed_ip=None):
"""Get all instances, possibly filtered by one of the
given parameters. If there is no filter and the context is
an admin, it will retreive all instances in the system.
"""Get all instances filtered by one of the given parameters.
If there is no filter and the context is an admin, it will retreive
all instances in the system.
"""
if reservation_id is not None:
return self.db.instance_get_all_by_reservation(
@@ -426,7 +445,8 @@ class API(base.Base):
:param params: Optional dictionary of arguments to be passed to the
compute worker
:retval None
:returns: None
"""
if not params:
params = {}
@@ -445,7 +465,7 @@ class API(base.Base):
:param params: Optional dictionary of arguments to be passed to the
compute worker
:retval: Result returned by compute worker
:returns: Result returned by compute worker
"""
if not params:
params = {}
@@ -458,13 +478,14 @@ class API(base.Base):
return rpc.call(context, queue, kwargs)
def _cast_scheduler_message(self, context, args):
"""Generic handler for RPC calls to the scheduler"""
"""Generic handler for RPC calls to the scheduler."""
rpc.cast(context, FLAGS.scheduler_topic, args)
def snapshot(self, context, instance_id, name):
"""Snapshot the given instance.
:retval: A dict containing image metadata
:returns: A dict containing image metadata
"""
properties = {'instance_id': str(instance_id),
'user_id': str(context.user_id)}
@@ -481,7 +502,7 @@ class API(base.Base):
self._cast_compute_message('reboot_instance', context, instance_id)
def revert_resize(self, context, instance_id):
"""Reverts a resize, deleting the 'new' instance in the process"""
"""Reverts a resize, deleting the 'new' instance in the process."""
context = context.elevated()
migration_ref = self.db.migration_get_by_instance_and_status(context,
instance_id, 'finished')
@@ -496,8 +517,7 @@ class API(base.Base):
{'status': 'reverted'})
def confirm_resize(self, context, instance_id):
"""Confirms a migration/resize, deleting the 'old' instance in the
process."""
"""Confirms a migration/resize and deletes the 'old' instance."""
context = context.elevated()
migration_ref = self.db.migration_get_by_instance_and_status(context,
instance_id, 'finished')
@@ -517,8 +537,7 @@ class API(base.Base):
def resize(self, context, instance_id, flavor_id):
"""Resize a running instance."""
instance = self.db.instance_get(context, instance_id)
current_instance_type = self.db.instance_type_get_by_name(
context, instance['instance_type'])
current_instance_type = instance['instance_type']
new_instance_type = self.db.instance_type_get_by_flavor_id(
context, flavor_id)
@@ -558,10 +577,9 @@ class API(base.Base):
@scheduler_api.reroute_compute("diagnostics")
def get_diagnostics(self, context, instance_id):
"""Retrieve diagnostics for the given instance."""
return self._call_compute_message(
"get_diagnostics",
context,
instance_id)
return self._call_compute_message("get_diagnostics",
context,
instance_id)
def get_actions(self, context, instance_id):
"""Retrieve actions for the given instance."""
@@ -569,12 +587,12 @@ class API(base.Base):
@scheduler_api.reroute_compute("suspend")
def suspend(self, context, instance_id):
"""suspend the instance with instance_id"""
"""Suspend the given instance."""
self._cast_compute_message('suspend_instance', context, instance_id)
@scheduler_api.reroute_compute("resume")
def resume(self, context, instance_id):
"""resume the instance with instance_id"""
"""Resume the given instance."""
self._cast_compute_message('resume_instance', context, instance_id)
@scheduler_api.reroute_compute("rescue")
@@ -589,15 +607,15 @@ class API(base.Base):
def set_admin_password(self, context, instance_id, password=None):
"""Set the root/admin password for the given instance."""
self._cast_compute_message('set_admin_password', context, instance_id,
password)
self._cast_compute_message(
'set_admin_password', context, instance_id, password)
def inject_file(self, context, instance_id):
"""Write a file to the given instance."""
self._cast_compute_message('inject_file', context, instance_id)
def get_ajax_console(self, context, instance_id):
"""Get a url to an AJAX Console"""
"""Get a url to an AJAX Console."""
output = self._call_compute_message('get_ajax_console',
context,
instance_id)
@@ -606,7 +624,7 @@ class API(base.Base):
'args': {'token': output['token'], 'host': output['host'],
'port': output['port']}})
return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url,
output['token'])}
output['token'])}
def get_vnc_console(self, context, instance_id):
"""Get a url to a VNC Console."""
@@ -628,39 +646,34 @@ class API(base.Base):
'portignore')}
def get_console_output(self, context, instance_id):
"""Get console output for an an instance"""
"""Get console output for an an instance."""
return self._call_compute_message('get_console_output',
context,
instance_id)
def lock(self, context, instance_id):
"""lock the instance with instance_id"""
"""Lock the given instance."""
self._cast_compute_message('lock_instance', context, instance_id)
def unlock(self, context, instance_id):
"""unlock the instance with instance_id"""
"""Unlock the given instance."""
self._cast_compute_message('unlock_instance', context, instance_id)
def get_lock(self, context, instance_id):
"""return the boolean state of (instance with instance_id)'s lock"""
"""Return the boolean state of given instance's lock."""
instance = self.get(context, instance_id)
return instance['locked']
def reset_network(self, context, instance_id):
"""
Reset networking on the instance.
"""
"""Reset networking on the instance."""
self._cast_compute_message('reset_network', context, instance_id)
def inject_network_info(self, context, instance_id):
"""
Inject network info for the instance.
"""
"""Inject network info for the instance."""
self._cast_compute_message('inject_network_info', context, instance_id)
def attach_volume(self, context, instance_id, volume_id, device):
"""Attach an existing volume to an existing instance."""
if not re.match("^/dev/[a-z]d[a-z]+$", device):
raise exception.ApiError(_("Invalid device specified: %s. "
"Example device: /dev/vdb") % device)
@@ -675,6 +688,7 @@ class API(base.Base):
"mountpoint": device}})
def detach_volume(self, context, volume_id):
"""Detach a volume from an instance."""
instance = self.db.volume_get_instance(context.elevated(), volume_id)
if not instance:
raise exception.ApiError(_("Volume isn't attached to anything!"))
@@ -688,6 +702,7 @@ class API(base.Base):
return instance
def associate_floating_ip(self, context, instance_id, address):
"""Associate a floating ip with an instance."""
instance = self.get(context, instance_id)
self.network_api.associate_floating_ip(context,
floating_ip=address,
@@ -699,11 +714,14 @@ class API(base.Base):
return dict(rv.iteritems())
def delete_instance_metadata(self, context, instance_id, key):
"""Delete the given metadata item"""
"""Delete the given metadata item from an instance."""
self.db.instance_metadata_delete(context, instance_id, key)
def update_or_create_instance_metadata(self, context, instance_id,
metadata):
"""Updates or creates instance metadata"""
"""Updates or creates instance metadata."""
combined_metadata = self.get_instance_metadata(context, instance_id)
combined_metadata.update(metadata)
self._check_metadata_properties_quota(context, combined_metadata)
self.db.instance_metadata_update_or_create(context, instance_id,
metadata)

View File

@@ -18,9 +18,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
The built-in instance properties.
"""
"""Built-in instance properties."""
from nova import context
from nova import db
@@ -34,9 +32,7 @@ LOG = logging.getLogger('nova.instance_types')
def create(name, memory, vcpus, local_gb, flavorid, swap=0,
rxtx_quota=0, rxtx_cap=0):
"""Creates instance types / flavors
arguments: name memory vcpus local_gb flavorid swap rxtx_quota rxtx_cap
"""
"""Creates instance types."""
for option in [memory, vcpus, local_gb, flavorid]:
try:
int(option)
@@ -59,83 +55,86 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0,
rxtx_quota=rxtx_quota,
rxtx_cap=rxtx_cap))
except exception.DBError, e:
LOG.exception(_('DB error: %s' % e))
raise exception.ApiError(_("Cannot create instance type: %s" % name))
LOG.exception(_('DB error: %s') % e)
raise exception.ApiError(_("Cannot create instance type: %s") % name)
def destroy(name):
"""Marks instance types / flavors as deleted
arguments: name"""
if name == None:
"""Marks instance types as deleted."""
if name is None:
raise exception.InvalidInputException(_("No instance type specified"))
else:
try:
db.instance_type_destroy(context.get_admin_context(), name)
except exception.NotFound:
LOG.exception(_('Instance type %s not found for deletion' % name))
raise exception.ApiError(_("Unknown instance type: %s" % name))
LOG.exception(_('Instance type %s not found for deletion') % name)
raise exception.ApiError(_("Unknown instance type: %s") % name)
def purge(name):
"""Removes instance types / flavors from database
arguments: name"""
if name == None:
"""Removes instance types from database."""
if name is None:
raise exception.InvalidInputException(_("No instance type specified"))
else:
try:
db.instance_type_purge(context.get_admin_context(), name)
except exception.NotFound:
LOG.exception(_('Instance type %s not found for purge' % name))
raise exception.ApiError(_("Unknown instance type: %s" % name))
LOG.exception(_('Instance type %s not found for purge') % name)
raise exception.ApiError(_("Unknown instance type: %s") % name)
def get_all_types(inactive=0):
"""Retrieves non-deleted instance_types.
Pass true as argument if you want deleted instance types returned also."""
"""Get all non-deleted instance_types.
Pass true as argument if you want deleted instance types returned also.
"""
return db.instance_type_get_all(context.get_admin_context(), inactive)
def get_all_flavors():
"""retrieves non-deleted flavors. alias for instance_types.get_all_types().
Pass true as argument if you want deleted instance types returned also."""
return get_all_types(context.get_admin_context())
get_all_flavors = get_all_types
def get_instance_type(name):
"""Retrieves single instance type by name"""
if name is None:
return FLAGS.default_instance_type
def get_default_instance_type():
"""Get the default instance type."""
name = FLAGS.default_instance_type
try:
ctxt = context.get_admin_context()
inst_type = db.instance_type_get_by_name(ctxt, name)
return inst_type
return get_instance_type_by_name(name)
except exception.DBError:
raise exception.ApiError(_("Unknown instance type: %s" % name))
raise exception.ApiError(_("Unknown instance type: %s") % name)
def get_by_type(instance_type):
"""retrieve instance type name"""
if instance_type is None:
return FLAGS.default_instance_type
def get_instance_type(id):
"""Retrieves single instance type by id."""
if id is None:
return get_default_instance_type()
try:
ctxt = context.get_admin_context()
inst_type = db.instance_type_get_by_name(ctxt, instance_type)
return inst_type['name']
except exception.DBError, e:
LOG.exception(_('DB error: %s' % e))
raise exception.ApiError(_("Unknown instance type: %s" %\
instance_type))
return db.instance_type_get_by_id(ctxt, id)
except exception.DBError:
raise exception.ApiError(_("Unknown instance type: %s") % name)
def get_by_flavor_id(flavor_id):
"""retrieve instance type's name by flavor_id"""
def get_instance_type_by_name(name):
"""Retrieves single instance type by name."""
if name is None:
return get_default_instance_type()
try:
ctxt = context.get_admin_context()
return db.instance_type_get_by_name(ctxt, name)
except exception.DBError:
raise exception.ApiError(_("Unknown instance type: %s") % name)
# TODO(termie): flavor-specific code should probably be in the API that uses
# flavors.
def get_instance_type_by_flavor_id(flavor_id):
"""Retrieve instance type by flavor_id."""
if flavor_id is None:
return FLAGS.default_instance_type
return get_default_instance_type()
try:
ctxt = context.get_admin_context()
flavor = db.instance_type_get_by_flavor_id(ctxt, flavor_id)
return flavor['name']
return db.instance_type_get_by_flavor_id(ctxt, flavor_id)
except exception.DBError, e:
LOG.exception(_('DB error: %s' % e))
raise exception.ApiError(_("Unknown flavor: %s" % flavor_id))
LOG.exception(_('DB error: %s') % e)
raise exception.ApiError(_("Unknown flavor: %s") % flavor_id)

View File

@@ -17,8 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Handles all processes relating to instances (guest vms).
"""Handles all processes relating to instances (guest vms).
The :py:class:`ComputeManager` class is a :py:class:`nova.manager.Manager` that
handles RPC calls relating to creating instances. It is responsible for
@@ -33,6 +32,7 @@ terminating it.
by :func:`nova.utils.import_object`
:volume_manager: Name of class that handles persistent storage, loaded by
:func:`nova.utils.import_object`
"""
import datetime
@@ -56,6 +56,7 @@ from nova import volume
from nova.compute import power_state
from nova.virt import driver
FLAGS = flags.FLAGS
flags.DEFINE_string('instances_path', '$state_path/instances',
'where instances are stored on disk')
@@ -75,19 +76,14 @@ flags.DEFINE_integer("rescue_timeout", 0,
"Automatically unrescue an instance after N seconds."
" Set to 0 to disable.")
LOG = logging.getLogger('nova.compute.manager')
def checks_instance_lock(function):
"""
decorator used for preventing action against locked instances
unless, of course, you happen to be admin
"""
"""Decorator to prevent action against locked instances for non-admins."""
@functools.wraps(function)
def decorated_function(self, context, instance_id, *args, **kwargs):
LOG.info(_("check_instance_lock: decorating: |%s|"), function,
context=context)
LOG.info(_("check_instance_lock: arguments: |%(self)s| |%(context)s|"
@@ -113,7 +109,6 @@ def checks_instance_lock(function):
class ComputeManager(manager.SchedulerDependentManager):
"""Manages the running instances from creation to destruction."""
def __init__(self, compute_driver=None, *args, **kwargs):
@@ -137,9 +132,7 @@ class ComputeManager(manager.SchedulerDependentManager):
*args, **kwargs)
def init_host(self):
"""Do any initialization that needs to be run if this is a
standalone service.
"""
"""Initialization for a standalone compute service."""
self.driver.init_host(host=self.host)
def _update_state(self, context, instance_id):
@@ -154,16 +147,18 @@ class ComputeManager(manager.SchedulerDependentManager):
self.db.instance_set_state(context, instance_id, state)
def get_console_topic(self, context, **kwargs):
"""Retrieves the console host for a project on this host
Currently this is just set in the flags for each compute
host."""
"""Retrieves the console host for a project on this host.
Currently this is just set in the flags for each compute host.
"""
#TODO(mdragon): perhaps make this variable by console_type?
return self.db.queue_get_for(context,
FLAGS.console_topic,
FLAGS.console_host)
def get_network_topic(self, context, **kwargs):
"""Retrieves the network host for a project on this host"""
"""Retrieves the network host for a project on this host."""
# TODO(vish): This method should be memoized. This will make
# the call to get_network_host cheaper, so that
# it can pas messages instead of checking the db
@@ -180,15 +175,23 @@ class ComputeManager(manager.SchedulerDependentManager):
return self.driver.get_console_pool_info(console_type)
@exception.wrap_exception
def refresh_security_group_rules(self, context,
security_group_id, **kwargs):
"""This call passes straight through to the virtualization driver."""
def refresh_security_group_rules(self, context, security_group_id,
**kwargs):
"""Tell the virtualization driver to refresh security group rules.
Passes straight through to the virtualization driver.
"""
return self.driver.refresh_security_group_rules(security_group_id)
@exception.wrap_exception
def refresh_security_group_members(self, context,
security_group_id, **kwargs):
"""This call passes straight through to the virtualization driver."""
"""Tell the virtualization driver to refresh security group members.
Passes straight through to the virtualization driver.
"""
return self.driver.refresh_security_group_members(security_group_id)
@exception.wrap_exception
@@ -250,7 +253,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def terminate_instance(self, context, instance_id):
"""Terminate an instance on this machine."""
"""Terminate an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_("Terminating instance %s"), instance_id, context=context)
@@ -298,7 +301,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def reboot_instance(self, context, instance_id):
"""Reboot an instance on this server."""
"""Reboot an instance on this host."""
context = context.elevated()
self._update_state(context, instance_id)
instance_ref = self.db.instance_get(context, instance_id)
@@ -322,7 +325,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def snapshot_instance(self, context, instance_id, image_id):
"""Snapshot an instance on this server."""
"""Snapshot an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
@@ -345,7 +348,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def set_admin_password(self, context, instance_id, new_pass=None):
"""Set the root/admin password for an instance on this server."""
"""Set the root/admin password for an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
instance_id = instance_ref['id']
@@ -366,7 +369,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def inject_file(self, context, instance_id, path, file_contents):
"""Write a file to the specified path on an instance on this server"""
"""Write a file to the specified path in an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
instance_id = instance_ref['id']
@@ -384,44 +387,34 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def rescue_instance(self, context, instance_id):
"""Rescue an instance on this server."""
"""Rescue an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: rescuing'), instance_id, context=context)
self.db.instance_set_state(
context,
instance_id,
power_state.NOSTATE,
'rescuing')
self.db.instance_set_state(context,
instance_id,
power_state.NOSTATE,
'rescuing')
self.network_manager.setup_compute_network(context, instance_id)
self.driver.rescue(
instance_ref,
lambda result: self._update_state_callback(
self,
context,
instance_id,
result))
_update_state = lambda result: self._update_state_callback(
self, context, instance_id, result)
self.driver.rescue(instance_ref, _update_state)
self._update_state(context, instance_id)
@exception.wrap_exception
@checks_instance_lock
def unrescue_instance(self, context, instance_id):
"""Rescue an instance on this server."""
"""Rescue an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: unrescuing'), instance_id, context=context)
self.db.instance_set_state(
context,
instance_id,
power_state.NOSTATE,
'unrescuing')
self.driver.unrescue(
instance_ref,
lambda result: self._update_state_callback(
self,
context,
instance_id,
result))
self.db.instance_set_state(context,
instance_id,
power_state.NOSTATE,
'unrescuing')
_update_state = lambda result: self._update_state_callback(
self, context, instance_id, result)
self.driver.unrescue(instance_ref, _update_state)
self._update_state(context, instance_id)
@staticmethod
@@ -432,18 +425,20 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def confirm_resize(self, context, instance_id, migration_id):
"""Destroys the source instance"""
"""Destroys the source instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
migration_ref = self.db.migration_get(context, migration_id)
self.driver.destroy(instance_ref)
@exception.wrap_exception
@checks_instance_lock
def revert_resize(self, context, instance_id, migration_id):
"""Destroys the new instance on the destination machine,
reverts the model changes, and powers on the old
instance on the source machine"""
"""Destroys the new instance on the destination machine.
Reverts the model changes, and powers on the old instance on the
source machine.
"""
instance_ref = self.db.instance_get(context, instance_id)
migration_ref = self.db.migration_get(context, migration_id)
@@ -460,9 +455,12 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def finish_revert_resize(self, context, instance_id, migration_id):
"""Finishes the second half of reverting a resize, powering back on
the source instance and reverting the resized attributes in the
database"""
"""Finishes the second half of reverting a resize.
Power back on the source instance and revert the resized attributes
in the database.
"""
instance_ref = self.db.instance_get(context, instance_id)
migration_ref = self.db.migration_get(context, migration_id)
instance_type = self.db.instance_type_get_by_flavor_id(context,
@@ -482,8 +480,11 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def prep_resize(self, context, instance_id, flavor_id):
"""Initiates the process of moving a running instance to another
host, possibly changing the RAM and disk size in the process"""
"""Initiates the process of moving a running instance to another host.
Possibly changes the RAM and disk size in the process.
"""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
if instance_ref['host'] == FLAGS.host:
@@ -515,34 +516,38 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def resize_instance(self, context, instance_id, migration_id):
"""Starts the migration of a running instance to another host"""
"""Starts the migration of a running instance to another host."""
migration_ref = self.db.migration_get(context, migration_id)
instance_ref = self.db.instance_get(context, instance_id)
self.db.migration_update(context, migration_id,
{'status': 'migrating', })
self.db.migration_update(context,
migration_id,
{'status': 'migrating'})
disk_info = self.driver.migrate_disk_and_power_off(instance_ref,
migration_ref['dest_host'])
self.db.migration_update(context, migration_id,
{'status': 'post-migrating', })
disk_info = self.driver.migrate_disk_and_power_off(
instance_ref, migration_ref['dest_host'])
self.db.migration_update(context,
migration_id,
{'status': 'post-migrating'})
service = self.db.service_get_by_host_and_topic(context,
migration_ref['dest_compute'], FLAGS.compute_topic)
topic = self.db.queue_get_for(context, FLAGS.compute_topic,
migration_ref['dest_compute'])
rpc.cast(context, topic,
{'method': 'finish_resize',
'args': {
'migration_id': migration_id,
'instance_id': instance_id,
'disk_info': disk_info, },
})
service = self.db.service_get_by_host_and_topic(
context, migration_ref['dest_compute'], FLAGS.compute_topic)
topic = self.db.queue_get_for(context,
FLAGS.compute_topic,
migration_ref['dest_compute'])
rpc.cast(context, topic, {'method': 'finish_resize',
'args': {'migration_id': migration_id,
'instance_id': instance_id,
'disk_info': disk_info}})
@exception.wrap_exception
@checks_instance_lock
def finish_resize(self, context, instance_id, migration_id, disk_info):
"""Completes the migration process by setting up the newly transferred
disk and turning on the instance on its new host machine"""
"""Completes the migration process.
Sets up the newly transferred disk and turns on the instance at its
new host machine.
"""
migration_ref = self.db.migration_get(context, migration_id)
instance_ref = self.db.instance_get(context,
migration_ref['instance_id'])
@@ -567,7 +572,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def pause_instance(self, context, instance_id):
"""Pause an instance on this server."""
"""Pause an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: pausing'), instance_id, context=context)
@@ -584,7 +589,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def unpause_instance(self, context, instance_id):
"""Unpause a paused instance on this server."""
"""Unpause a paused instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: unpausing'), instance_id, context=context)
@@ -600,7 +605,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def get_diagnostics(self, context, instance_id):
"""Retrieve diagnostics for an instance on this server."""
"""Retrieve diagnostics for an instance on this host."""
instance_ref = self.db.instance_get(context, instance_id)
if instance_ref["state"] == power_state.RUNNING:
@@ -611,10 +616,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def suspend_instance(self, context, instance_id):
"""
suspend the instance with instance_id
"""
"""Suspend the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: suspending'), instance_id, context=context)
@@ -630,10 +632,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def resume_instance(self, context, instance_id):
"""
resume the suspended instance with instance_id
"""
"""Resume the given suspended instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: resuming'), instance_id, context=context)
@@ -648,34 +647,23 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def lock_instance(self, context, instance_id):
"""
lock the instance with instance_id
"""
"""Lock the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: locking'), instance_id, context=context)
self.db.instance_update(context, instance_id, {'locked': True})
@exception.wrap_exception
def unlock_instance(self, context, instance_id):
"""
unlock the instance with instance_id
"""
"""Unlock the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: unlocking'), instance_id, context=context)
self.db.instance_update(context, instance_id, {'locked': False})
@exception.wrap_exception
def get_lock(self, context, instance_id):
"""
return the boolean state of (instance with instance_id)'s lock
"""
"""Return the boolean state of the given instance's lock."""
context = context.elevated()
LOG.debug(_('instance %s: getting locked state'), instance_id,
context=context)
@@ -684,10 +672,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@checks_instance_lock
def reset_network(self, context, instance_id):
"""
Reset networking on the instance.
"""
"""Reset networking on the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: reset network'), instance_id,
@@ -696,10 +681,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@checks_instance_lock
def inject_network_info(self, context, instance_id):
"""
Inject network info for the instance.
"""
"""Inject network info for the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: inject network info'), instance_id,
@@ -708,7 +690,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def get_console_output(self, context, instance_id):
"""Send the console output for an instance."""
"""Send the console output for the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_("Get console output for instance %s"), instance_id,
@@ -717,20 +699,18 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def get_ajax_console(self, context, instance_id):
"""Return connection information for an ajax console"""
"""Return connection information for an ajax console."""
context = context.elevated()
LOG.debug(_("instance %s: getting ajax console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id)
return self.driver.get_ajax_console(instance_ref)
@exception.wrap_exception
def get_vnc_console(self, context, instance_id):
"""Return connection information for an vnc console."""
"""Return connection information for a vnc console."""
context = context.elevated()
LOG.debug(_("instance %s: getting vnc console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id)
return self.driver.get_vnc_console(instance_ref)
@checks_instance_lock
@@ -792,7 +772,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def compare_cpu(self, context, cpu_info):
"""Checks the host cpu is compatible to a cpu given by xml.
"""Checks that the host cpu is compatible with a cpu given by xml.
:param context: security context
:param cpu_info: json string obtained from virConnect.getCapabilities
@@ -813,7 +793,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:returns: tmpfile name(basename)
"""
dirpath = FLAGS.instances_path
fd, tmp_file = tempfile.mkstemp(dir=dirpath)
LOG.debug(_("Creating tmpfile %s to notify to other "
@@ -830,7 +809,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param filename: confirm existence of FLAGS.instances_path/thisfile
"""
tmp_file = os.path.join(FLAGS.instances_path, filename)
if not os.path.exists(tmp_file):
raise exception.NotFound(_('%s not found') % tmp_file)
@@ -843,7 +821,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param filename: remove existence of FLAGS.instances_path/thisfile
"""
tmp_file = os.path.join(FLAGS.instances_path, filename)
os.remove(tmp_file)
@@ -855,7 +832,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:returns: See driver.update_available_resource()
"""
return self.driver.update_available_resource(context, self.host)
def pre_live_migration(self, context, instance_id, time=None):
@@ -865,7 +841,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param instance_id: nova.db.sqlalchemy.models.Instance.Id
"""
if not time:
time = greenthread
@@ -924,7 +899,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param dest: destination host
"""
# Get instance for error handling.
instance_ref = self.db.instance_get(context, instance_id)
i_name = instance_ref.name
@@ -1020,13 +994,10 @@ class ComputeManager(manager.SchedulerDependentManager):
:param ctxt: security context
:param instance_id: nova.db.sqlalchemy.models.Instance.Id
:param host:
DB column value is updated by this hostname.
if none, the host instance currently running is selected.
:param dest: destination host
:param host: DB column value is updated by this hostname.
If none, the host instance currently running is selected.
"""
if not host:
host = instance_ref['host']
@@ -1105,6 +1076,14 @@ class ComputeManager(manager.SchedulerDependentManager):
vm_state = vm_instance.state
vms_not_found_in_db.remove(name)
if db_instance['state_description'] == 'migrating':
# A situation which db record exists, but no instance"
# sometimes occurs while live-migration at src compute,
# this case should be ignored.
LOG.debug(_("Ignoring %(name)s, as it's currently being "
"migrated.") % locals())
continue
if vm_state != db_state:
LOG.info(_("DB/VM state mismatch. Changing state from "
"'%(db_state)s' to '%(vm_state)s'") % locals())
@@ -1112,16 +1091,15 @@ class ComputeManager(manager.SchedulerDependentManager):
db_instance['id'],
vm_state)
if vm_state == power_state.SHUTOFF:
# TODO(soren): This is what the compute manager does when you
# terminate an instance. At some point I figure we'll have a
# "terminated" state and some sort of cleanup job that runs
# occasionally, cleaning them out.
self.db.instance_destroy(context, db_instance['id'])
# NOTE(justinsb): We no longer auto-remove SHUTOFF instances
# It's quite hard to get them back when we do.
# Are there VMs not in the DB?
for vm_not_found_in_db in vms_not_found_in_db:
name = vm_not_found_in_db
# TODO(justinsb): What to do here? Adopt it? Shut it down?
LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring")
% locals())
# We only care about instances that compute *should* know about
if name.startswith("instance-"):
# TODO(justinsb): What to do here? Adopt it? Shut it down?
LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring")
% locals())

View File

@@ -260,7 +260,7 @@ class Instance(object):
try:
data = self.fetch_cpu_stats()
if data != None:
if data is not None:
LOG.debug('CPU: %s', data)
update_rrd(self, 'cpu', data)
@@ -313,7 +313,7 @@ class Instance(object):
LOG.debug('CPU: %d', self.cputime)
# Skip calculation on first pass. Need delta to get a meaningful value.
if cputime_last_updated == None:
if cputime_last_updated is None:
return None
# Calculate the number of seconds between samples.

View File

@@ -15,23 +15,19 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Handles ConsoleProxy API requests
"""
"""Handles ConsoleProxy API requests."""
from nova import exception
from nova.db import base
from nova import flags
from nova import rpc
from nova.db import base
FLAGS = flags.FLAGS
class API(base.Base):
"""API for spining up or down console proxy connections"""
"""API for spinning up or down console proxy connections."""
def __init__(self, **kwargs):
super(API, self).__init__(**kwargs)
@@ -51,8 +47,8 @@ class API(base.Base):
self.db.queue_get_for(context,
FLAGS.console_topic,
pool['host']),
{"method": "remove_console",
"args": {"console_id": console['id']}})
{'method': 'remove_console',
'args': {'console_id': console['id']}})
def create_console(self, context, instance_id):
instance = self.db.instance_get(context, instance_id)
@@ -63,13 +59,12 @@ class API(base.Base):
# here.
rpc.cast(context,
self._get_console_topic(context, instance['host']),
{"method": "add_console",
"args": {"instance_id": instance_id}})
{'method': 'add_console',
'args': {'instance_id': instance_id}})
def _get_console_topic(self, context, instance_host):
topic = self.db.queue_get_for(context,
FLAGS.compute_topic,
instance_host)
return rpc.call(context,
topic,
{"method": "get_console_topic", "args": {'fake': 1}})
return rpc.call(context, topic, {'method': 'get_console_topic',
'args': {'fake': 1}})

View File

@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Fake ConsoleProxy driver for tests.
"""
"""Fake ConsoleProxy driver for tests."""
from nova import exception
@@ -27,32 +25,32 @@ class FakeConsoleProxy(object):
@property
def console_type(self):
return "fake"
return 'fake'
def setup_console(self, context, console):
"""Sets up actual proxies"""
"""Sets up actual proxies."""
pass
def teardown_console(self, context, console):
"""Tears down actual proxies"""
"""Tears down actual proxies."""
pass
def init_host(self):
"""Start up any config'ed consoles on start"""
"""Start up any config'ed consoles on start."""
pass
def generate_password(self, length=8):
"""Returns random console password"""
return "fakepass"
"""Returns random console password."""
return 'fakepass'
def get_port(self, context):
"""get available port for consoles that need one"""
"""Get available port for consoles that need one."""
return 5999
def fix_pool_password(self, password):
"""Trim password to length, and any other massaging"""
"""Trim password to length, and any other massaging."""
return password
def fix_console_password(self, password):
"""Trim password to length, and any other massaging"""
"""Trim password to length, and any other massaging."""
return password

View File

@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Console Proxy Service
"""
"""Console Proxy Service."""
import functools
import socket
@@ -29,6 +27,7 @@ from nova import manager
from nova import rpc
from nova import utils
FLAGS = flags.FLAGS
flags.DEFINE_string('console_driver',
'nova.console.xvp.XVPConsoleProxy',
@@ -41,9 +40,11 @@ flags.DEFINE_string('console_public_hostname',
class ConsoleProxyManager(manager.Manager):
"""Sets up and tears down any console proxy connections.
""" Sets up and tears down any proxy connections needed for accessing
instance consoles securely"""
Needed for accessing instance consoles securely.
"""
def __init__(self, console_driver=None, *args, **kwargs):
if not console_driver:
@@ -67,7 +68,7 @@ class ConsoleProxyManager(manager.Manager):
pool['id'],
instance_id)
except exception.NotFound:
logging.debug(_("Adding console"))
logging.debug(_('Adding console'))
if not password:
password = utils.generate_password(8)
if not port:
@@ -115,8 +116,8 @@ class ConsoleProxyManager(manager.Manager):
self.db.queue_get_for(context,
FLAGS.compute_topic,
instance_host),
{"method": "get_console_pool_info",
"args": {"console_type": console_type}})
{'method': 'get_console_pool_info',
'args': {'console_type': console_type}})
pool_info['password'] = self.driver.fix_pool_password(
pool_info['password'])
pool_info['host'] = self.host

View File

@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
VMRC console drivers.
"""
"""VMRC console drivers."""
import base64
import json
@@ -27,6 +25,8 @@ from nova import flags
from nova import log as logging
from nova.virt.vmwareapi import vim_util
FLAGS = flags.FLAGS
flags.DEFINE_integer('console_vmrc_port',
443,
"port for VMware VMRC connections")
@@ -34,8 +34,6 @@ flags.DEFINE_integer('console_vmrc_error_retries',
10,
"number of retries for retrieving VMRC information")
FLAGS = flags.FLAGS
class VMRCConsole(object):
"""VMRC console driver with ESX credentials."""
@@ -69,34 +67,34 @@ class VMRCConsole(object):
return password
def generate_password(self, vim_session, pool, instance_name):
"""
Returns VMRC Connection credentials.
"""Returns VMRC Connection credentials.
Return string is of the form '<VM PATH>:<ESX Username>@<ESX Password>'.
"""
username, password = pool['username'], pool['password']
vms = vim_session._call_method(vim_util, "get_objects",
"VirtualMachine", ["name", "config.files.vmPathName"])
vms = vim_session._call_method(vim_util, 'get_objects',
'VirtualMachine', ['name', 'config.files.vmPathName'])
vm_ds_path_name = None
vm_ref = None
for vm in vms:
vm_name = None
ds_path_name = None
for prop in vm.propSet:
if prop.name == "name":
if prop.name == 'name':
vm_name = prop.val
elif prop.name == "config.files.vmPathName":
elif prop.name == 'config.files.vmPathName':
ds_path_name = prop.val
if vm_name == instance_name:
vm_ref = vm.obj
vm_ds_path_name = ds_path_name
break
if vm_ref is None:
raise exception.NotFound(_("instance - %s not present") %
raise exception.NotFound(_('instance - %s not present') %
instance_name)
json_data = json.dumps({"vm_id": vm_ds_path_name,
"username": username,
"password": password})
json_data = json.dumps({'vm_id': vm_ds_path_name,
'username': username,
'password': password})
return base64.b64encode(json_data)
def is_otp(self):
@@ -115,28 +113,28 @@ class VMRCSessionConsole(VMRCConsole):
return 'vmrc+session'
def generate_password(self, vim_session, pool, instance_name):
"""
Returns a VMRC Session.
"""Returns a VMRC Session.
Return string is of the form '<VM MOID>:<VMRC Ticket>'.
"""
vms = vim_session._call_method(vim_util, "get_objects",
"VirtualMachine", ["name"])
vm_ref = None
vms = vim_session._call_method(vim_util, 'get_objects',
'VirtualMachine', ['name'])
vm_ref = NoneV
for vm in vms:
if vm.propSet[0].val == instance_name:
vm_ref = vm.obj
if vm_ref is None:
raise exception.NotFound(_("instance - %s not present") %
raise exception.NotFound(_('instance - %s not present') %
instance_name)
virtual_machine_ticket = \
vim_session._call_method(
vim_session._get_vim(),
"AcquireCloneTicket",
'AcquireCloneTicket',
vim_session._get_vim().get_service_content().sessionManager)
json_data = json.dumps({"vm_id": str(vm_ref.value),
"username": virtual_machine_ticket,
"password": virtual_machine_ticket})
json_data = json.dumps({'vm_id': str(vm_ref.value),
'username': virtual_machine_ticket,
'password': virtual_machine_ticket})
return base64.b64encode(json_data)
def is_otp(self):

View File

@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
VMRC Console Manager.
"""
"""VMRC Console Manager."""
from nova import exception
from nova import flags
@@ -25,24 +23,21 @@ from nova import log as logging
from nova import manager
from nova import rpc
from nova import utils
from nova.virt.vmwareapi_conn import VMWareAPISession
from nova.virt import vmwareapi_conn
LOG = logging.getLogger("nova.console.vmrc_manager")
FLAGS = flags.FLAGS
flags.DEFINE_string('console_public_hostname',
'',
flags.DEFINE_string('console_public_hostname', '',
'Publicly visible name for this console host')
flags.DEFINE_string('console_driver',
'nova.console.vmrc.VMRCConsole',
flags.DEFINE_string('console_driver', 'nova.console.vmrc.VMRCConsole',
'Driver to use for the console')
class ConsoleVMRCManager(manager.Manager):
"""
Manager to handle VMRC connections needed for accessing instance consoles.
"""
"""Manager to handle VMRC connections for accessing instance consoles."""
def __init__(self, console_driver=None, *args, **kwargs):
self.driver = utils.import_object(FLAGS.console_driver)
@@ -56,16 +51,17 @@ class ConsoleVMRCManager(manager.Manager):
"""Get VIM session for the pool specified."""
vim_session = None
if pool['id'] not in self.sessions.keys():
vim_session = VMWareAPISession(pool['address'],
pool['username'],
pool['password'],
FLAGS.console_vmrc_error_retries)
vim_session = vmwareapi_conn.VMWareAPISession(
pool['address'],
pool['username'],
pool['password'],
FLAGS.console_vmrc_error_retries)
self.sessions[pool['id']] = vim_session
return self.sessions[pool['id']]
def _generate_console(self, context, pool, name, instance_id, instance):
"""Sets up console for the instance."""
LOG.debug(_("Adding console"))
LOG.debug(_('Adding console'))
password = self.driver.generate_password(
self._get_vim_session(pool),
@@ -84,9 +80,10 @@ class ConsoleVMRCManager(manager.Manager):
@exception.wrap_exception
def add_console(self, context, instance_id, password=None,
port=None, **kwargs):
"""
Adds a console for the instance. If it is one time password, then we
generate new console credentials.
"""Adds a console for the instance.
If it is one time password, then we generate new console credentials.
"""
instance = self.db.instance_get(context, instance_id)
host = instance['host']
@@ -97,19 +94,17 @@ class ConsoleVMRCManager(manager.Manager):
pool['id'],
instance_id)
if self.driver.is_otp():
console = self._generate_console(
context,
pool,
name,
instance_id,
instance)
console = self._generate_console(context,
pool,
name,
instance_id,
instance)
except exception.NotFound:
console = self._generate_console(
context,
pool,
name,
instance_id,
instance)
console = self._generate_console(context,
pool,
name,
instance_id,
instance)
return console['id']
@exception.wrap_exception
@@ -118,13 +113,11 @@ class ConsoleVMRCManager(manager.Manager):
try:
console = self.db.console_get(context, console_id)
except exception.NotFound:
LOG.debug(_("Tried to remove non-existent console "
"%(console_id)s.") %
{'console_id': console_id})
LOG.debug(_('Tried to remove non-existent console '
'%(console_id)s.') % {'console_id': console_id})
return
LOG.debug(_("Removing console "
"%(console_id)s.") %
{'console_id': console_id})
LOG.debug(_('Removing console '
'%(console_id)s.') % {'console_id': console_id})
self.db.console_delete(context, console_id)
self.driver.teardown_console(context, console)
@@ -139,11 +132,11 @@ class ConsoleVMRCManager(manager.Manager):
console_type)
except exception.NotFound:
pool_info = rpc.call(context,
self.db.queue_get_for(context,
FLAGS.compute_topic,
instance_host),
{"method": "get_console_pool_info",
"args": {"console_type": console_type}})
self.db.queue_get_for(context,
FLAGS.compute_topic,
instance_host),
{'method': 'get_console_pool_info',
'args': {'console_type': console_type}})
pool_info['password'] = self.driver.fix_pool_password(
pool_info['password'])
pool_info['host'] = self.host

View File

@@ -15,16 +15,14 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
XVP (Xenserver VNC Proxy) driver.
"""
"""XVP (Xenserver VNC Proxy) driver."""
import fcntl
import os
import signal
import subprocess
from Cheetah.Template import Template
from Cheetah import Template
from nova import context
from nova import db
@@ -33,6 +31,8 @@ from nova import flags
from nova import log as logging
from nova import utils
FLAGS = flags.FLAGS
flags.DEFINE_string('console_xvp_conf_template',
utils.abspath('console/xvp.conf.template'),
'XVP conf template')
@@ -47,12 +47,11 @@ flags.DEFINE_string('console_xvp_log',
'XVP log file')
flags.DEFINE_integer('console_xvp_multiplex_port',
5900,
"port for XVP to multiplex VNC connections on")
FLAGS = flags.FLAGS
'port for XVP to multiplex VNC connections on')
class XVPConsoleProxy(object):
"""Sets up XVP config, and manages xvp daemon"""
"""Sets up XVP config, and manages XVP daemon."""
def __init__(self):
self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read()
@@ -61,50 +60,51 @@ class XVPConsoleProxy(object):
@property
def console_type(self):
return "vnc+xvp"
return 'vnc+xvp'
def get_port(self, context):
"""get available port for consoles that need one"""
"""Get available port for consoles that need one."""
#TODO(mdragon): implement port selection for non multiplex ports,
# we are not using that, but someone else may want
# it.
return FLAGS.console_xvp_multiplex_port
def setup_console(self, context, console):
"""Sets up actual proxies"""
"""Sets up actual proxies."""
self._rebuild_xvp_conf(context.elevated())
def teardown_console(self, context, console):
"""Tears down actual proxies"""
"""Tears down actual proxies."""
self._rebuild_xvp_conf(context.elevated())
def init_host(self):
"""Start up any config'ed consoles on start"""
"""Start up any config'ed consoles on start."""
ctxt = context.get_admin_context()
self._rebuild_xvp_conf(ctxt)
def fix_pool_password(self, password):
"""Trim password to length, and encode"""
"""Trim password to length, and encode."""
return self._xvp_encrypt(password, is_pool_password=True)
def fix_console_password(self, password):
"""Trim password to length, and encode"""
"""Trim password to length, and encode."""
return self._xvp_encrypt(password)
def _rebuild_xvp_conf(self, context):
logging.debug(_("Rebuilding xvp conf"))
logging.debug(_('Rebuilding xvp conf'))
pools = [pool for pool in
db.console_pool_get_all_by_host_type(context, self.host,
self.console_type)
if pool['consoles']]
if not pools:
logging.debug("No console pools!")
logging.debug('No console pools!')
self._xvp_stop()
return
conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port,
'pools': pools,
'pass_encode': self.fix_console_password}
config = str(Template(self.xvpconf_template, searchList=[conf_data]))
config = str(Template.Template(self.xvpconf_template,
searchList=[conf_data]))
self._write_conf(config)
self._xvp_restart()
@@ -114,7 +114,7 @@ class XVPConsoleProxy(object):
cfile.write(config)
def _xvp_stop(self):
logging.debug(_("Stopping xvp"))
logging.debug(_('Stopping xvp'))
pid = self._xvp_pid()
if not pid:
return
@@ -127,19 +127,19 @@ class XVPConsoleProxy(object):
def _xvp_start(self):
if self._xvp_check_running():
return
logging.debug(_("Starting xvp"))
logging.debug(_('Starting xvp'))
try:
utils.execute('xvp',
'-p', FLAGS.console_xvp_pid,
'-c', FLAGS.console_xvp_conf,
'-l', FLAGS.console_xvp_log)
except exception.ProcessExecutionError, err:
logging.error(_("Error starting xvp: %s") % err)
logging.error(_('Error starting xvp: %s') % err)
def _xvp_restart(self):
logging.debug(_("Restarting xvp"))
logging.debug(_('Restarting xvp'))
if not self._xvp_check_running():
logging.debug(_("xvp not running..."))
logging.debug(_('xvp not running...'))
self._xvp_start()
else:
pid = self._xvp_pid()
@@ -178,7 +178,9 @@ class XVPConsoleProxy(object):
Note that xvp's obfuscation should not be considered 'real' encryption.
It simply DES encrypts the passwords with static keys plainly viewable
in the xvp source code."""
in the xvp source code.
"""
maxlen = 8
flag = '-e'
if is_pool_password:

View File

@@ -16,9 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
RequestContext: context for requests that persist through all of nova.
"""
"""RequestContext: context for requests that persist through all of nova."""
import datetime
import random
@@ -28,6 +26,12 @@ from nova import utils
class RequestContext(object):
"""Security context and request information.
Represents the user taking a given action within the system.
"""
def __init__(self, user, project, is_admin=None, read_deleted=False,
remote_address=None, timestamp=None, request_id=None):
if hasattr(user, 'id'):

View File

@@ -15,10 +15,11 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Wrappers around standard crypto data elements.
"""Wrappers around standard crypto data elements.
Includes root and intermediate CAs, SSH key_pairs and x509 certificates.
"""
import base64
@@ -43,6 +44,8 @@ from nova import log as logging
LOG = logging.getLogger("nova.crypto")
FLAGS = flags.FLAGS
flags.DEFINE_string('ca_file', 'cacert.pem', _('Filename of root CA'))
flags.DEFINE_string('key_file',
@@ -90,13 +93,13 @@ def key_path(project_id=None):
def fetch_ca(project_id=None, chain=True):
if not FLAGS.use_project_ca:
project_id = None
buffer = ""
buffer = ''
if project_id:
with open(ca_path(project_id), "r") as cafile:
with open(ca_path(project_id), 'r') as cafile:
buffer += cafile.read()
if not chain:
return buffer
with open(ca_path(None), "r") as cafile:
with open(ca_path(None), 'r') as cafile:
buffer += cafile.read()
return buffer
@@ -143,7 +146,7 @@ def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
def revoke_cert(project_id, file_name):
"""Revoke a cert by file name"""
"""Revoke a cert by file name."""
start = os.getcwd()
os.chdir(ca_folder(project_id))
# NOTE(vish): potential race condition here
@@ -155,14 +158,14 @@ def revoke_cert(project_id, file_name):
def revoke_certs_by_user(user_id):
"""Revoke all user certs"""
"""Revoke all user certs."""
admin = context.get_admin_context()
for cert in db.certificate_get_all_by_user(admin, user_id):
revoke_cert(cert['project_id'], cert['file_name'])
def revoke_certs_by_project(project_id):
"""Revoke all project certs"""
"""Revoke all project certs."""
# NOTE(vish): This is somewhat useless because we can just shut down
# the vpn.
admin = context.get_admin_context()
@@ -171,29 +174,29 @@ def revoke_certs_by_project(project_id):
def revoke_certs_by_user_and_project(user_id, project_id):
"""Revoke certs for user in project"""
"""Revoke certs for user in project."""
admin = context.get_admin_context()
for cert in db.certificate_get_all_by_user(admin, user_id, project_id):
revoke_cert(cert['project_id'], cert['file_name'])
def _project_cert_subject(project_id):
"""Helper to generate user cert subject"""
"""Helper to generate user cert subject."""
return FLAGS.project_cert_subject % (project_id, utils.isotime())
def _vpn_cert_subject(project_id):
"""Helper to generate user cert subject"""
"""Helper to generate user cert subject."""
return FLAGS.vpn_cert_subject % (project_id, utils.isotime())
def _user_cert_subject(user_id, project_id):
"""Helper to generate user cert subject"""
"""Helper to generate user cert subject."""
return FLAGS.user_cert_subject % (project_id, user_id, utils.isotime())
def generate_x509_cert(user_id, project_id, bits=1024):
"""Generate and sign a cert for user in project"""
"""Generate and sign a cert for user in project."""
subject = _user_cert_subject(user_id, project_id)
tmpdir = tempfile.mkdtemp()
keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key'))
@@ -205,7 +208,7 @@ def generate_x509_cert(user_id, project_id, bits=1024):
csr = open(csrfile).read()
shutil.rmtree(tmpdir)
(serial, signed_csr) = sign_csr(csr, project_id)
fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % serial)
fname = os.path.join(ca_folder(project_id), 'newcerts/%s.pem' % serial)
cert = {'user_id': user_id,
'project_id': project_id,
'file_name': fname}
@@ -215,30 +218,36 @@ def generate_x509_cert(user_id, project_id, bits=1024):
def _ensure_project_folder(project_id):
if not os.path.exists(ca_path(project_id)):
geninter_sh_path = os.path.join(os.path.dirname(__file__),
'CA',
'geninter.sh')
start = os.getcwd()
os.chdir(ca_folder())
utils.execute('sh', 'geninter.sh', project_id,
utils.execute('sh', geninter_sh_path, project_id,
_project_cert_subject(project_id))
os.chdir(start)
def generate_vpn_files(project_id):
project_folder = ca_folder(project_id)
csr_fn = os.path.join(project_folder, "server.csr")
crt_fn = os.path.join(project_folder, "server.crt")
csr_fn = os.path.join(project_folder, 'server.csr')
crt_fn = os.path.join(project_folder, 'server.crt')
genvpn_sh_path = os.path.join(os.path.dirname(__file__),
'CA',
'genvpn.sh')
if os.path.exists(crt_fn):
return
_ensure_project_folder(project_id)
start = os.getcwd()
os.chdir(ca_folder())
# TODO(vish): the shell scripts could all be done in python
utils.execute('sh', 'genvpn.sh',
utils.execute('sh', genvpn_sh_path,
project_id, _vpn_cert_subject(project_id))
with open(csr_fn, "r") as csrfile:
with open(csr_fn, 'r') as csrfile:
csr_text = csrfile.read()
(serial, signed_csr) = sign_csr(csr_text, project_id)
with open(crt_fn, "w") as crtfile:
with open(crt_fn, 'w') as crtfile:
crtfile.write(signed_csr)
os.chdir(start)
@@ -255,26 +264,28 @@ def sign_csr(csr_text, project_id=None):
def _sign_csr(csr_text, ca_folder):
tmpfolder = tempfile.mkdtemp()
inbound = os.path.join(tmpfolder, "inbound.csr")
outbound = os.path.join(tmpfolder, "outbound.csr")
csrfile = open(inbound, "w")
inbound = os.path.join(tmpfolder, 'inbound.csr')
outbound = os.path.join(tmpfolder, 'outbound.csr')
csrfile = open(inbound, 'w')
csrfile.write(csr_text)
csrfile.close()
LOG.debug(_("Flags path: %s"), ca_folder)
LOG.debug(_('Flags path: %s'), ca_folder)
start = os.getcwd()
# Change working dir to CA
if not os.path.exists(ca_folder):
os.makedirs(ca_folder)
os.chdir(ca_folder)
utils.execute('openssl', 'ca', '-batch', '-out', outbound, '-config',
'./openssl.cnf', '-infiles', inbound)
out, _err = utils.execute('openssl', 'x509', '-in', outbound,
'-serial', '-noout')
serial = string.strip(out.rpartition("=")[2])
serial = string.strip(out.rpartition('=')[2])
os.chdir(start)
with open(outbound, "r") as crtfile:
with open(outbound, 'r') as crtfile:
return (serial, crtfile.read())
def mkreq(bits, subject="foo", ca=0):
def mkreq(bits, subject='foo', ca=0):
pk = M2Crypto.EVP.PKey()
req = M2Crypto.X509.Request()
rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
@@ -306,7 +317,7 @@ def mkcacert(subject='nova', years=1):
cert.set_not_before(now)
cert.set_not_after(nowPlusYear)
issuer = M2Crypto.X509.X509_Name()
issuer.C = "US"
issuer.C = 'US'
issuer.CN = subject
cert.set_issuer(issuer)
cert.set_pubkey(pkey)
@@ -344,13 +355,15 @@ def mkcacert(subject='nova', years=1):
# http://code.google.com/p/boto
def compute_md5(fp):
"""
"""Compute an md5 hash.
:type fp: file
:param fp: File pointer to the file to MD5 hash. The file pointer will be
reset to the beginning of the file before the method returns.
:rtype: tuple
:return: the hex digest version of the MD5 hash
:returns: the hex digest version of the MD5 hash
"""
m = hashlib.md5()
fp.seek(0)

View File

@@ -15,8 +15,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Defines interface for DB access.
"""Defines interface for DB access.
The underlying driver is loaded as a :class:`LazyPluggable`.
@@ -30,6 +30,7 @@ The underlying driver is loaded as a :class:`LazyPluggable`.
:enable_new_services: when adding a new service to the database, is it in the
pool of available hardware (Default: True)
"""
from nova import exception
@@ -86,7 +87,7 @@ def service_get(context, service_id):
def service_get_by_host_and_topic(context, host, topic):
"""Get a service by host it's on and topic it listens to"""
"""Get a service by host it's on and topic it listens to."""
return IMPL.service_get_by_host_and_topic(context, host, topic)
@@ -113,7 +114,7 @@ def service_get_all_compute_by_host(context, host):
def service_get_all_compute_sorted(context):
"""Get all compute services sorted by instance count.
Returns a list of (Service, instance_count) tuples.
:returns: a list of (Service, instance_count) tuples.
"""
return IMPL.service_get_all_compute_sorted(context)
@@ -122,7 +123,7 @@ def service_get_all_compute_sorted(context):
def service_get_all_network_sorted(context):
"""Get all network services sorted by network count.
Returns a list of (Service, network_count) tuples.
:returns: a list of (Service, network_count) tuples.
"""
return IMPL.service_get_all_network_sorted(context)
@@ -131,7 +132,7 @@ def service_get_all_network_sorted(context):
def service_get_all_volume_sorted(context):
"""Get all volume services sorted by volume count.
Returns a list of (Service, volume_count) tuples.
:returns: a list of (Service, volume_count) tuples.
"""
return IMPL.service_get_all_volume_sorted(context)
@@ -241,7 +242,7 @@ def floating_ip_count_by_project(context, project_id):
def floating_ip_deallocate(context, address):
"""Deallocate an floating ip by address"""
"""Deallocate an floating ip by address."""
return IMPL.floating_ip_deallocate(context, address)
@@ -253,7 +254,7 @@ def floating_ip_destroy(context, address):
def floating_ip_disassociate(context, address):
"""Disassociate an floating ip from a fixed ip by address.
Returns the address of the existing fixed ip.
:returns: the address of the existing fixed ip.
"""
return IMPL.floating_ip_disassociate(context, address)
@@ -294,22 +295,22 @@ def floating_ip_update(context, address, values):
####################
def migration_update(context, id, values):
"""Update a migration instance"""
"""Update a migration instance."""
return IMPL.migration_update(context, id, values)
def migration_create(context, values):
"""Create a migration record"""
"""Create a migration record."""
return IMPL.migration_create(context, values)
def migration_get(context, migration_id):
"""Finds a migration by the id"""
"""Finds a migration by the id."""
return IMPL.migration_get(context, migration_id)
def migration_get_by_instance_and_status(context, instance_id, status):
"""Finds a migration by the instance id its migrating"""
"""Finds a migration by the instance id its migrating."""
return IMPL.migration_get_by_instance_and_status(context, instance_id,
status)
@@ -579,7 +580,9 @@ def network_create_safe(context, values):
def network_delete_safe(context, network_id):
"""Delete network with key network_id.
This method assumes that the network is not associated with any project
"""
return IMPL.network_delete_safe(context, network_id)
@@ -674,7 +677,6 @@ def project_get_network(context, project_id, associate=True):
network if one is not found, otherwise it returns None.
"""
return IMPL.project_get_network(context, project_id, associate)
@@ -722,7 +724,9 @@ def iscsi_target_create_safe(context, values):
The device is not returned. If the create violates the unique
constraints because the iscsi_target and host already exist,
no exception is raised."""
no exception is raised.
"""
return IMPL.iscsi_target_create_safe(context, values)
@@ -1050,10 +1054,7 @@ def project_delete(context, project_id):
def host_get_networks(context, host):
"""Return all networks for which the given host is the designated
network host.
"""
"""All networks for which the given host is the network host."""
return IMPL.host_get_networks(context, host)
@@ -1115,33 +1116,40 @@ def console_get(context, console_id, instance_id=None):
def instance_type_create(context, values):
"""Create a new instance type"""
"""Create a new instance type."""
return IMPL.instance_type_create(context, values)
def instance_type_get_all(context, inactive=False):
"""Get all instance types"""
"""Get all instance types."""
return IMPL.instance_type_get_all(context, inactive)
def instance_type_get_by_id(context, id):
"""Get instance type by id."""
return IMPL.instance_type_get_by_id(context, id)
def instance_type_get_by_name(context, name):
"""Get instance type by name"""
"""Get instance type by name."""
return IMPL.instance_type_get_by_name(context, name)
def instance_type_get_by_flavor_id(context, id):
"""Get instance type by name"""
"""Get instance type by name."""
return IMPL.instance_type_get_by_flavor_id(context, id)
def instance_type_destroy(context, name):
"""Delete a instance type"""
"""Delete a instance type."""
return IMPL.instance_type_destroy(context, name)
def instance_type_purge(context, name):
"""Purges (removes) an instance type from DB
Use instance_type_destroy for most cases
"""Purges (removes) an instance type from DB.
Use instance_type_destroy for most cases
"""
return IMPL.instance_type_purge(context, name)
@@ -1178,15 +1186,15 @@ def zone_get_all(context):
def instance_metadata_get(context, instance_id):
"""Get all metadata for an instance"""
"""Get all metadata for an instance."""
return IMPL.instance_metadata_get(context, instance_id)
def instance_metadata_delete(context, instance_id, key):
"""Delete the given metadata item"""
"""Delete the given metadata item."""
IMPL.instance_metadata_delete(context, instance_id, key)
def instance_metadata_update_or_create(context, instance_id, metadata):
"""Create or update instance metadata"""
"""Create or update instance metadata."""
IMPL.instance_metadata_update_or_create(context, instance_id, metadata)

View File

@@ -16,20 +16,20 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Base class for classes that need modular database access.
"""
"""Base class for classes that need modular database access."""
from nova import utils
from nova import flags
FLAGS = flags.FLAGS
flags.DEFINE_string('db_driver', 'nova.db.api',
'driver to use for database access')
class Base(object):
"""DB driver is injected in the init method"""
"""DB driver is injected in the init method."""
def __init__(self, db_driver=None):
if not db_driver:
db_driver = FLAGS.db_driver

View File

@@ -15,11 +15,13 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Database setup and migration commands."""
from nova import flags
from nova import utils
FLAGS = flags.FLAGS
flags.DECLARE('db_backend', 'nova.db.api')

View File

@@ -660,7 +660,9 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time):
filter(models.FixedIp.instance_id != None).\
filter_by(allocated=0).\
update({'instance_id': None,
'leased': 0})
'leased': 0,
'updated_at': datetime.datetime.utcnow()},
synchronize_session='fetch')
return result
@@ -768,9 +770,10 @@ def instance_create(context, values):
metadata = values.get('metadata')
metadata_refs = []
if metadata:
for metadata_item in metadata:
for k, v in metadata.iteritems():
metadata_ref = models.InstanceMetadata()
metadata_ref.update(metadata_item)
metadata_ref['key'] = k
metadata_ref['value'] = v
metadata_refs.append(metadata_ref)
values['metadata'] = metadata_refs
@@ -829,6 +832,7 @@ def instance_get(context, instance_id, session=None):
options(joinedload('volumes')).\
options(joinedload_all('fixed_ip.network')).\
options(joinedload('metadata')).\
options(joinedload('instance_type')).\
filter_by(id=instance_id).\
filter_by(deleted=can_read_deleted(context)).\
first()
@@ -838,6 +842,7 @@ def instance_get(context, instance_id, session=None):
options(joinedload_all('security_groups.rules')).\
options(joinedload('volumes')).\
options(joinedload('metadata')).\
options(joinedload('instance_type')).\
filter_by(project_id=context.project_id).\
filter_by(id=instance_id).\
filter_by(deleted=False).\
@@ -857,6 +862,7 @@ def instance_get_all(context):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
options(joinedload('instance_type')).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -868,6 +874,7 @@ def instance_get_all_by_user(context, user_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
options(joinedload('instance_type')).\
filter_by(deleted=can_read_deleted(context)).\
filter_by(user_id=user_id).\
all()
@@ -880,6 +887,7 @@ def instance_get_all_by_host(context, host):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
options(joinedload('instance_type')).\
filter_by(host=host).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -894,6 +902,7 @@ def instance_get_all_by_project(context, project_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
options(joinedload('instance_type')).\
filter_by(project_id=project_id).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -908,6 +917,7 @@ def instance_get_all_by_reservation(context, reservation_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
options(joinedload('instance_type')).\
filter_by(reservation_id=reservation_id).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -916,6 +926,7 @@ def instance_get_all_by_reservation(context, reservation_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
options(joinedload('instance_type')).\
filter_by(project_id=context.project_id).\
filter_by(reservation_id=reservation_id).\
filter_by(deleted=False).\
@@ -928,6 +939,7 @@ def instance_get_project_vpn(context, project_id):
return session.query(models.Instance).\
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload('instance_type')).\
filter_by(project_id=project_id).\
filter_by(image_id=FLAGS.vpn_image_id).\
filter_by(deleted=can_read_deleted(context)).\
@@ -1824,7 +1836,7 @@ def security_group_get_by_instance(context, instance_id):
def security_group_exists(context, project_id, group_name):
try:
group = security_group_get_by_name(context, project_id, group_name)
return group != None
return group is not None
except exception.NotFound:
return False
@@ -2368,6 +2380,19 @@ def instance_type_get_all(context, inactive=False):
raise exception.NotFound
@require_context
def instance_type_get_by_id(context, id):
"""Returns a dict describing specific instance_type"""
session = get_session()
inst_type = session.query(models.InstanceTypes).\
filter_by(id=id).\
first()
if not inst_type:
raise exception.NotFound(_("No instance type with id %s") % id)
else:
return dict(inst_type)
@require_context
def instance_type_get_by_name(context, name):
"""Returns a dict describing specific instance_type"""

View File

@@ -0,0 +1,86 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 OpenStack LLC.
#
# 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.
from sqlalchemy import *
from sqlalchemy.sql import text
from migrate import *
#from nova import log as logging
meta = MetaData()
c_instance_type = Column('instance_type',
String(length=255, convert_unicode=False,
assert_unicode=None, unicode_error=None,
_warn_on_bytestring=False),
nullable=True)
c_instance_type_id = Column('instance_type_id',
String(length=255, convert_unicode=False,
assert_unicode=None, unicode_error=None,
_warn_on_bytestring=False),
nullable=True)
instance_types = Table('instance_types', meta,
Column('id', Integer(), primary_key=True, nullable=False),
Column('name',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False),
unique=True))
def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine;
# bind migrate_engine to your metadata
meta.bind = migrate_engine
instances = Table('instances', meta, autoload=True,
autoload_with=migrate_engine)
instances.create_column(c_instance_type_id)
type_names = {}
recs = migrate_engine.execute(instance_types.select())
for row in recs:
type_names[row[0]] = row[1]
for type_id, type_name in type_names.iteritems():
migrate_engine.execute(instances.update()\
.where(instances.c.instance_type == type_name)\
.values(instance_type_id=type_id))
instances.c.instance_type.drop()
def downgrade(migrate_engine):
meta.bind = migrate_engine
instances = Table('instances', meta, autoload=True,
autoload_with=migrate_engine)
instances.create_column(c_instance_type)
recs = migrate_engine.execute(instance_types.select())
for row in recs:
type_id = row[0]
type_name = row[1]
migrate_engine.execute(instances.update()\
.where(instances.c.instance_type_id == type_id)\
.values(instance_type=type_name))
instances.c.instance_type_id.drop()

View File

@@ -209,7 +209,7 @@ class Instance(BASE, NovaBase):
hostname = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
instance_type = Column(String(255))
instance_type_id = Column(String(255))
user_data = Column(Text)
@@ -268,6 +268,12 @@ class InstanceTypes(BASE, NovaBase):
rxtx_quota = Column(Integer, nullable=False, default=0)
rxtx_cap = Column(Integer, nullable=False, default=0)
instances = relationship(Instance,
backref=backref('instance_type', uselist=False),
foreign_keys=id,
primaryjoin='and_(Instance.instance_type_id == '
'InstanceTypes.id)')
class Volume(BASE, NovaBase):
"""Represents a block storage device that can be attached to a vm."""

View File

@@ -16,31 +16,34 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Nova base exception handling, including decorator for re-raising
Nova-type exceptions. SHOULD include dedicated exception logging.
"""Nova base exception handling.
Includes decorator for re-raising Nova-type exceptions.
SHOULD include dedicated exception logging.
"""
from nova import log as logging
LOG = logging.getLogger('nova.exception')
class ProcessExecutionError(IOError):
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None):
if description is None:
description = _("Unexpected error while running command.")
description = _('Unexpected error while running command.')
if exit_code is None:
exit_code = '-'
message = _("%(description)s\nCommand: %(cmd)s\n"
"Exit code: %(exit_code)s\nStdout: %(stdout)r\n"
"Stderr: %(stderr)r") % locals()
message = _('%(description)s\nCommand: %(cmd)s\n'
'Exit code: %(exit_code)s\nStdout: %(stdout)r\n'
'Stderr: %(stderr)r') % locals()
IOError.__init__(self, message)
class Error(Exception):
def __init__(self, message=None):
super(Error, self).__init__(message)
@@ -97,7 +100,7 @@ class TimeoutException(Error):
class DBError(Error):
"""Wraps an implementation specific exception"""
"""Wraps an implementation specific exception."""
def __init__(self, inner_exception):
self.inner_exception = inner_exception
super(DBError, self).__init__(str(inner_exception))
@@ -108,7 +111,7 @@ def wrap_db_error(f):
try:
return f(*args, **kwargs)
except Exception, e:
LOG.exception(_('DB exception wrapped'))
LOG.exception(_('DB exception wrapped.'))
raise DBError(e)
return _wrap
_wrap.func_name = f.func_name

View File

@@ -18,14 +18,14 @@
"""Super simple fake memcache client."""
import utils
from nova import utils
class Client(object):
"""Replicates a tiny subset of memcached client interface."""
def __init__(self, *args, **kwargs):
"""Ignores the passed in args"""
"""Ignores the passed in args."""
self.cache = {}
def get(self, key):

View File

@@ -16,9 +16,13 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
"""Command-line flag library.
Wraps gflags.
Package-level global flags are defined here, the rest are defined
where they're used.
"""
import getopt
@@ -145,10 +149,12 @@ class FlagValues(gflags.FlagValues):
class StrWrapper(object):
"""Wrapper around FlagValues objects
"""Wrapper around FlagValues objects.
Wraps FlagValues objects for string.Template so that we're
sure to return strings."""
sure to return strings.
"""
def __init__(self, context_objs):
self.context_objs = context_objs
@@ -169,6 +175,7 @@ def _GetCallingModule():
We generally use this function to get the name of the module calling a
DEFINE_foo... function.
"""
# Walk down the stack to find the first globals dict that's not ours.
for depth in range(1, sys.getrecursionlimit()):
@@ -192,6 +199,7 @@ def __GetModuleName(globals_dict):
Returns:
A string (the name of the module) or None (if the module could not
be identified.
"""
for name, module in sys.modules.iteritems():
if getattr(module, '__dict__', None) is globals_dict:
@@ -326,7 +334,7 @@ DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger')
DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
"Top-level directory for maintaining nova's state")
DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'),
"Directory for lock files")
'Directory for lock files')
DEFINE_string('logdir', None, 'output to a per-service log file in named '
'directory')

View File

@@ -14,6 +14,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Implementation of an fake image service"""
import copy
@@ -44,11 +45,10 @@ class FakeImageService(service.BaseImageService):
'created_at': timestamp,
'updated_at': timestamp,
'status': 'active',
'type': 'machine',
'container_format': 'ami',
'disk_format': 'raw',
'properties': {'kernel_id': FLAGS.null_kernel,
'ramdisk_id': FLAGS.null_kernel,
'disk_format': 'ami'}
}
'ramdisk_id': FLAGS.null_kernel}}
self.create(None, image)
super(FakeImageService, self).__init__()
@@ -70,14 +70,14 @@ class FakeImageService(service.BaseImageService):
image = self.images.get(image_id)
if image:
return copy.deepcopy(image)
LOG.warn("Unable to find image id %s. Have images: %s",
LOG.warn('Unable to find image id %s. Have images: %s',
image_id, self.images)
raise exception.NotFound
def create(self, context, data):
"""Store the image data and return the new image id.
:raises Duplicate if the image already exist.
:raises: Duplicate if the image already exist.
"""
image_id = int(data['id'])
@@ -89,7 +89,7 @@ class FakeImageService(service.BaseImageService):
def update(self, context, image_id, data):
"""Replace the contents of the given image with the new data.
:raises NotFound if the image does not exist.
:raises: NotFound if the image does not exist.
"""
image_id = int(image_id)
@@ -100,7 +100,7 @@ class FakeImageService(service.BaseImageService):
def delete(self, context, image_id):
"""Delete the given image.
:raises NotFound if the image does not exist.
:raises: NotFound if the image does not exist.
"""
image_id = int(image_id)

View File

@@ -14,6 +14,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Implementation of an image service that uses Glance as the backend"""
from __future__ import absolute_import
@@ -31,16 +32,18 @@ from nova.image import service
LOG = logging.getLogger('nova.image.glance')
FLAGS = flags.FLAGS
GlanceClient = utils.import_class('glance.client.Client')
class GlanceImageService(service.BaseImageService):
"""Provides storage and retrieval of disk image objects within Glance."""
GLANCE_ONLY_ATTRS = ["size", "location", "disk_format",
"container_format"]
GLANCE_ONLY_ATTRS = ['size', 'location', 'disk_format',
'container_format']
# NOTE(sirp): Overriding to use _translate_to_service provided by
# BaseImageService
@@ -56,9 +59,7 @@ class GlanceImageService(service.BaseImageService):
self.client = client
def index(self, context):
"""
Calls out to Glance for a list of images available
"""
"""Calls out to Glance for a list of images available."""
# NOTE(sirp): We need to use `get_images_detailed` and not
# `get_images` here because we need `is_public` and `properties`
# included so we can filter by user
@@ -71,9 +72,7 @@ class GlanceImageService(service.BaseImageService):
return filtered
def detail(self, context):
"""
Calls out to Glance for a list of detailed image information
"""
"""Calls out to Glance for a list of detailed image information."""
filtered = []
image_metas = self.client.get_images_detailed()
for image_meta in image_metas:
@@ -83,9 +82,7 @@ class GlanceImageService(service.BaseImageService):
return filtered
def show(self, context, image_id):
"""
Returns a dict containing image data for the given opaque image id.
"""
"""Returns a dict with image data for the given opaque image id."""
try:
image_meta = self.client.get_image_meta(image_id)
except glance_exception.NotFound:
@@ -98,9 +95,7 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta
def show_by_name(self, context, name):
"""
Returns a dict containing image data for the given name.
"""
"""Returns a dict containing image data for the given name."""
# TODO(vish): replace this with more efficient call when glance
# supports it.
image_metas = self.detail(context)
@@ -110,9 +105,7 @@ class GlanceImageService(service.BaseImageService):
raise exception.NotFound
def get(self, context, image_id, data):
"""
Calls out to Glance for metadata and data and writes data.
"""
"""Calls out to Glance for metadata and data and writes data."""
try:
image_meta, image_chunks = self.client.get_image(image_id)
except glance_exception.NotFound:
@@ -125,16 +118,16 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta
def create(self, context, image_meta, data=None):
"""
Store the image data and return the new image id.
"""Store the image data and return the new image id.
:raises: AlreadyExists if the image already exist.
:raises AlreadyExists if the image already exist.
"""
# Translate Base -> Service
LOG.debug(_("Creating image in Glance. Metadata passed in %s"),
LOG.debug(_('Creating image in Glance. Metadata passed in %s'),
image_meta)
sent_service_image_meta = self._translate_to_service(image_meta)
LOG.debug(_("Metadata after formatting for Glance %s"),
LOG.debug(_('Metadata after formatting for Glance %s'),
sent_service_image_meta)
recv_service_image_meta = self.client.add_image(
@@ -142,15 +135,18 @@ class GlanceImageService(service.BaseImageService):
# Translate Service -> Base
base_image_meta = self._translate_to_base(recv_service_image_meta)
LOG.debug(_("Metadata returned from Glance formatted for Base %s"),
LOG.debug(_('Metadata returned from Glance formatted for Base %s'),
base_image_meta)
return base_image_meta
def update(self, context, image_id, image_meta, data=None):
"""Replace the contents of the given image with the new data.
:raises NotFound if the image does not exist.
:raises: NotFound if the image does not exist.
"""
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
try:
image_meta = self.client.update_image(image_id, image_meta, data)
except glance_exception.NotFound:
@@ -160,11 +156,13 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta
def delete(self, context, image_id):
"""
Delete the given image.
"""Delete the given image.
:raises: NotFound if the image does not exist.
:raises NotFound if the image does not exist.
"""
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
try:
result = self.client.delete_image(image_id)
except glance_exception.NotFound:
@@ -172,53 +170,21 @@ class GlanceImageService(service.BaseImageService):
return result
def delete_all(self):
"""
Clears out all images
"""
"""Clears out all images."""
pass
@classmethod
def _translate_to_base(cls, image_meta):
"""Overriding the base translation to handle conversion to datetime
objects
"""
image_meta = service.BaseImageService._translate_to_base(image_meta)
"""Override translation to handle conversion to datetime objects."""
image_meta = service.BaseImageService._propertify_metadata(
image_meta, cls.SERVICE_IMAGE_ATTRS)
image_meta = _convert_timestamps_to_datetimes(image_meta)
return image_meta
@staticmethod
def _is_image_available(context, image_meta):
"""
Images are always available if they are public or if the user is an
admin.
Otherwise, we filter by project_id (if present) and then fall-back to
images owned by user.
"""
# FIXME(sirp): We should be filtering by user_id on the Glance side
# for security; however, we can't do that until we get authn/authz
# sorted out. Until then, filtering in Nova.
if image_meta['is_public'] or context.is_admin:
return True
properties = image_meta['properties']
if context.project_id and ('project_id' in properties):
return str(properties['project_id']) == str(project_id)
try:
user_id = properties['user_id']
except KeyError:
return False
return str(user_id) == str(context.user_id)
# utility functions
def _convert_timestamps_to_datetimes(image_meta):
"""
Returns image with known timestamp fields converted to datetime objects
"""
"""Returns image with timestamp fields converted to datetime objects."""
for attr in ['created_at', 'updated_at', 'deleted_at']:
if image_meta.get(attr):
image_meta[attr] = _parse_glance_iso8601_timestamp(
@@ -227,10 +193,8 @@ def _convert_timestamps_to_datetimes(image_meta):
def _parse_glance_iso8601_timestamp(timestamp):
"""
Parse a subset of iso8601 timestamps into datetime objects
"""
iso_formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"]
"""Parse a subset of iso8601 timestamps into datetime objects."""
iso_formats = ['%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S']
for iso_format in iso_formats:
try:
@@ -238,5 +202,5 @@ def _parse_glance_iso8601_timestamp(timestamp):
except ValueError:
pass
raise ValueError(_("%(timestamp)s does not follow any of the "
"signatures: %(ISO_FORMATS)s") % locals())
raise ValueError(_('%(timestamp)s does not follow any of the '
'signatures: %(ISO_FORMATS)s') % locals())

View File

@@ -23,14 +23,15 @@ import shutil
from nova import exception
from nova import flags
from nova import log as logging
from nova.image import service
from nova import utils
from nova.image import service
FLAGS = flags.FLAGS
flags.DEFINE_string('images_path', '$state_path/images',
'path to decrypted images')
LOG = logging.getLogger('nova.image.local')
@@ -56,9 +57,8 @@ class LocalImageService(service.BaseImageService):
try:
unhexed_image_id = int(image_dir, 16)
except ValueError:
LOG.error(
_("%s is not in correct directory naming format"\
% image_dir))
LOG.error(_('%s is not in correct directory naming format')
% image_dir)
else:
images.append(unhexed_image_id)
return images
@@ -84,7 +84,10 @@ class LocalImageService(service.BaseImageService):
def show(self, context, image_id):
try:
with open(self._path_to(image_id)) as metadata_file:
return json.load(metadata_file)
image_meta = json.load(metadata_file)
if not self._is_image_available(context, image_meta):
raise exception.NotFound
return image_meta
except (IOError, ValueError):
raise exception.NotFound
@@ -98,7 +101,7 @@ class LocalImageService(service.BaseImageService):
if name == cantidate.get('name'):
image = cantidate
break
if image == None:
if image is None:
raise exception.NotFound
return image
@@ -119,10 +122,15 @@ class LocalImageService(service.BaseImageService):
image_path = self._path_to(image_id, None)
if not os.path.exists(image_path):
os.mkdir(image_path)
return self.update(context, image_id, metadata, data)
return self._store(context, image_id, metadata, data)
def update(self, context, image_id, metadata, data=None):
"""Replace the contents of the given image with the new data."""
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
return self._store(context, image_id, metadata, data)
def _store(self, context, image_id, metadata, data=None):
metadata['id'] = image_id
try:
if data:
@@ -140,9 +148,12 @@ class LocalImageService(service.BaseImageService):
def delete(self, context, image_id):
"""Delete the given image.
Raises OSError if the image does not exist.
:raises: NotFound if the image does not exist.
"""
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
try:
shutil.rmtree(self._path_to(image_id, None))
except (IOError, ValueError):

View File

@@ -16,13 +16,9 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Proxy AMI-related calls from the cloud controller, to the running
objectstore service.
"""
"""Proxy AMI-related calls from cloud controller to objectstore service."""
import binascii
import eventlet
import os
import shutil
import tarfile
@@ -30,6 +26,7 @@ import tempfile
from xml.etree import ElementTree
import boto.s3.connection
import eventlet
from nova import crypto
from nova import exception
@@ -46,64 +43,41 @@ flags.DEFINE_string('image_decryption_dir', '/tmp',
class S3ImageService(service.BaseImageService):
"""Wraps an existing image service to support s3 based register."""
def __init__(self, service=None, *args, **kwargs):
if service == None:
if service is None:
service = utils.import_object(FLAGS.image_service)
self.service = service
self.service.__init__(*args, **kwargs)
def create(self, context, metadata, data=None):
"""metadata['properties'] should contain image_location"""
"""Create an image.
metadata['properties'] should contain image_location.
"""
image = self._s3_create(context, metadata)
return image
def delete(self, context, image_id):
# FIXME(vish): call to show is to check filter
self.show(context, image_id)
self.service.delete(context, image_id)
def update(self, context, image_id, metadata, data=None):
# FIXME(vish): call to show is to check filter
self.show(context, image_id)
image = self.service.update(context, image_id, metadata, data)
return image
def index(self, context):
images = self.service.index(context)
# FIXME(vish): index doesn't filter so we do it manually
return self._filter(context, images)
return self.service.index(context)
def detail(self, context):
images = self.service.detail(context)
# FIXME(vish): detail doesn't filter so we do it manually
return self._filter(context, images)
@classmethod
def _is_visible(cls, context, image):
return (context.is_admin
or context.project_id == image['properties']['owner_id']
or image['properties']['is_public'] == 'True')
@classmethod
def _filter(cls, context, images):
filtered = []
for image in images:
if not cls._is_visible(context, image):
continue
filtered.append(image)
return filtered
return self.service.detail(context)
def show(self, context, image_id):
image = self.service.show(context, image_id)
if not self._is_visible(context, image):
raise exception.NotFound
return image
return self.service.show(context, image_id)
def show_by_name(self, context, name):
image = self.service.show_by_name(context, name)
if not self._is_visible(context, image):
raise exception.NotFound
return image
return self.service.show_by_name(context, name)
@staticmethod
def _conn(context):
@@ -128,12 +102,12 @@ class S3ImageService(service.BaseImageService):
return local_filename
def _s3_create(self, context, metadata):
"""Gets a manifext from s3 and makes an image"""
"""Gets a manifext from s3 and makes an image."""
image_path = tempfile.mkdtemp(dir=FLAGS.image_decryption_dir)
image_location = metadata['properties']['image_location']
bucket_name = image_location.split("/")[0]
bucket_name = image_location.split('/')[0]
manifest_path = image_location[len(bucket_name) + 1:]
bucket = self._conn(context).get_bucket(bucket_name)
key = bucket.get_key(manifest_path)
@@ -144,7 +118,7 @@ class S3ImageService(service.BaseImageService):
image_type = 'machine'
try:
kernel_id = manifest.find("machine_configuration/kernel_id").text
kernel_id = manifest.find('machine_configuration/kernel_id').text
if kernel_id == 'true':
image_format = 'aki'
image_type = 'kernel'
@@ -153,7 +127,7 @@ class S3ImageService(service.BaseImageService):
kernel_id = None
try:
ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text
ramdisk_id = manifest.find('machine_configuration/ramdisk_id').text
if ramdisk_id == 'true':
image_format = 'ari'
image_type = 'ramdisk'
@@ -162,12 +136,12 @@ class S3ImageService(service.BaseImageService):
ramdisk_id = None
try:
arch = manifest.find("machine_configuration/architecture").text
arch = manifest.find('machine_configuration/architecture').text
except Exception:
arch = 'x86_64'
properties = metadata['properties']
properties['owner_id'] = context.project_id
properties['project_id'] = context.project_id
properties['architecture'] = arch
if kernel_id:
@@ -176,8 +150,6 @@ class S3ImageService(service.BaseImageService):
if ramdisk_id:
properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id)
properties['is_public'] = False
properties['type'] = image_type
metadata.update({'disk_format': image_format,
'container_format': image_format,
'status': 'queued',
@@ -190,7 +162,7 @@ class S3ImageService(service.BaseImageService):
def delayed_create():
"""This handles the fetching and decrypting of the part files."""
parts = []
for fn_element in manifest.find("image").getiterator("filename"):
for fn_element in manifest.find('image').getiterator('filename'):
part = self._download_file(bucket, fn_element.text, image_path)
parts.append(part)
@@ -204,9 +176,9 @@ class S3ImageService(service.BaseImageService):
metadata['properties']['image_state'] = 'decrypting'
self.service.update(context, image_id, metadata)
hex_key = manifest.find("image/ec2_encrypted_key").text
hex_key = manifest.find('image/ec2_encrypted_key').text
encrypted_key = binascii.a2b_hex(hex_key)
hex_iv = manifest.find("image/ec2_encrypted_iv").text
hex_iv = manifest.find('image/ec2_encrypted_iv').text
encrypted_iv = binascii.a2b_hex(hex_iv)
# FIXME(vish): grab key from common service so this can run on
@@ -244,7 +216,7 @@ class S3ImageService(service.BaseImageService):
process_input=encrypted_key,
check_exit_code=False)
if err:
raise exception.Error(_("Failed to decrypt private key: %s")
raise exception.Error(_('Failed to decrypt private key: %s')
% err)
iv, err = utils.execute('openssl',
'rsautl',
@@ -253,8 +225,8 @@ class S3ImageService(service.BaseImageService):
process_input=encrypted_iv,
check_exit_code=False)
if err:
raise exception.Error(_("Failed to decrypt initialization "
"vector: %s") % err)
raise exception.Error(_('Failed to decrypt initialization '
'vector: %s') % err)
_out, err = utils.execute('openssl', 'enc',
'-d', '-aes-128-cbc',
@@ -264,14 +236,14 @@ class S3ImageService(service.BaseImageService):
'-out', '%s' % (decrypted_filename,),
check_exit_code=False)
if err:
raise exception.Error(_("Failed to decrypt image file "
"%(image_file)s: %(err)s") %
raise exception.Error(_('Failed to decrypt image file '
'%(image_file)s: %(err)s') %
{'image_file': encrypted_filename,
'err': err})
@staticmethod
def _untarzip_image(path, filename):
tar_file = tarfile.open(filename, "r|gz")
tar_file = tarfile.open(filename, 'r|gz')
tar_file.extractall(path)
image_file = tar_file.getnames()[0]
tar_file.close()

View File

@@ -20,7 +20,7 @@ from nova import utils
class BaseImageService(object):
"""Base class for providing image search and retrieval services
"""Base class for providing image search and retrieval services.
ImageService exposes two concepts of metadata:
@@ -35,7 +35,9 @@ class BaseImageService(object):
This means that ImageServices will return BASE_IMAGE_ATTRS as keys in the
metadata dict, all other attributes will be returned as keys in the nested
'properties' dict.
"""
BASE_IMAGE_ATTRS = ['id', 'name', 'created_at', 'updated_at',
'deleted_at', 'deleted', 'status', 'is_public']
@@ -45,23 +47,18 @@ class BaseImageService(object):
SERVICE_IMAGE_ATTRS = []
def index(self, context):
"""
Returns a sequence of mappings of id and name information about
images.
"""List images.
:rtype: array
:retval: a sequence of mappings with the following signature
{'id': opaque id of image, 'name': name of image}
:returns: a sequence of mappings with the following signature
{'id': opaque id of image, 'name': name of image}
"""
raise NotImplementedError
def detail(self, context):
"""
Returns a sequence of mappings of detailed information about images.
"""Detailed information about an images.
:rtype: array
:retval: a sequence of mappings with the following signature
:returns: a sequence of mappings with the following signature
{'id': opaque id of image,
'name': name of image,
'created_at': creation datetime object,
@@ -77,15 +74,14 @@ class BaseImageService(object):
NotImplementedError, in which case Nova will emulate this method
with repeated calls to show() for each image received from the
index() method.
"""
raise NotImplementedError
def show(self, context, image_id):
"""
Returns a dict containing image metadata for the given opaque image id.
:retval a mapping with the following signature:
"""Detailed information about an image.
:returns: a mapping with the following signature:
{'id': opaque id of image,
'name': name of image,
'created_at': creation datetime object,
@@ -96,75 +92,107 @@ class BaseImageService(object):
'is_public': boolean indicating if image is public
}, ...
:raises NotFound if the image does not exist
:raises: NotFound if the image does not exist
"""
raise NotImplementedError
def get(self, context, data):
"""
Returns a dict containing image metadata and writes image data to data.
"""Get an image.
:param data: a file-like object to hold binary image data
:returns: a dict containing image metadata, writes image data to data.
:raises: NotFound if the image does not exist
:raises NotFound if the image does not exist
"""
raise NotImplementedError
def create(self, context, metadata, data=None):
"""
Store the image metadata and data and return the new image metadata.
"""Store the image metadata and data.
:raises AlreadyExists if the image already exist.
:returns: the new image metadata.
:raises: AlreadyExists if the image already exist.
"""
raise NotImplementedError
def update(self, context, image_id, metadata, data=None):
"""Update the given image metadata and data and return the metadata
"""Update the given image metadata and data and return the metadata.
:raises NotFound if the image does not exist.
:raises: NotFound if the image does not exist.
"""
raise NotImplementedError
def delete(self, context, image_id):
"""
Delete the given image.
"""Delete the given image.
:raises NotFound if the image does not exist.
:raises: NotFound if the image does not exist.
"""
raise NotImplementedError
@staticmethod
def _is_image_available(context, image_meta):
"""Check image availability.
Images are always available if they are public or if the user is an
admin.
Otherwise, we filter by project_id (if present) and then fall-back to
images owned by user.
"""
# FIXME(sirp): We should be filtering by user_id on the Glance side
# for security; however, we can't do that until we get authn/authz
# sorted out. Until then, filtering in Nova.
if image_meta['is_public'] or context.is_admin:
return True
properties = image_meta['properties']
if context.project_id and ('project_id' in properties):
return str(properties['project_id']) == str(context.project_id)
try:
user_id = properties['user_id']
except KeyError:
return False
return str(user_id) == str(context.user_id)
@classmethod
def _translate_to_base(cls, metadata):
"""Return a metadata dictionary that is BaseImageService compliant.
This is used by subclasses to expose only a metadata dictionary that
is the same across ImageService implementations.
"""
return cls._propertify_metadata(metadata, cls.BASE_IMAGE_ATTRS)
@classmethod
def _translate_to_service(cls, metadata):
"""Return a metadata dictionary that is usable by the ImageService
subclass.
"""Return a metadata dict that is usable by the ImageService subclass.
As an example, Glance has additional attributes (like 'location'); the
BaseImageService considers these properties, but we need to translate
these back to first-class attrs for sending to Glance. This method
handles this by allowing you to specify the attributes an ImageService
considers first-class.
"""
if not cls.SERVICE_IMAGE_ATTRS:
raise NotImplementedError(_("Cannot use this without specifying "
"SERVICE_IMAGE_ATTRS for subclass"))
raise NotImplementedError(_('Cannot use this without specifying '
'SERVICE_IMAGE_ATTRS for subclass'))
return cls._propertify_metadata(metadata, cls.SERVICE_IMAGE_ATTRS)
@staticmethod
def _propertify_metadata(metadata, keys):
"""Return a dict with any unrecognized keys placed in the nested
'properties' dict.
"""Move unknown keys to a nested 'properties' dict.
:returns: a new dict with the keys moved.
"""
flattened = utils.flatten_dict(metadata)
attributes, properties = utils.partition_dict(flattened, keys)

View File

@@ -16,16 +16,15 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Nova logging handler.
"""Nova logging handler.
This module adds to logging functionality by adding the option to specify
a context object when calling the various log methods. If the context object
is not specified, default formatting is used.
It also allows setting of formatting information through flags.
"""
"""
import cStringIO
import inspect
@@ -41,34 +40,28 @@ from nova import version
FLAGS = flags.FLAGS
flags.DEFINE_string('logging_context_format_string',
'%(asctime)s %(levelname)s %(name)s '
'[%(request_id)s %(user)s '
'%(project)s] %(message)s',
'format string to use for log messages with context')
flags.DEFINE_string('logging_default_format_string',
'%(asctime)s %(levelname)s %(name)s [-] '
'%(message)s',
'format string to use for log messages without context')
flags.DEFINE_string('logging_debug_format_suffix',
'from (pid=%(process)d) %(funcName)s'
' %(pathname)s:%(lineno)d',
'data to append to log format when level is DEBUG')
flags.DEFINE_string('logging_exception_prefix',
'(%(name)s): TRACE: ',
'prefix each line of exception output with this format')
flags.DEFINE_list('default_log_levels',
['amqplib=WARN',
'sqlalchemy=WARN',
'boto=WARN',
'eventlet.wsgi.server=WARN'],
'list of logger=LEVEL pairs')
flags.DEFINE_bool('use_syslog', False, 'output to syslog')
flags.DEFINE_string('logfile', None, 'output to named file')
@@ -83,6 +76,8 @@ WARN = logging.WARN
INFO = logging.INFO
DEBUG = logging.DEBUG
NOTSET = logging.NOTSET
# methods
getLogger = logging.getLogger
debug = logging.debug
@@ -93,6 +88,8 @@ error = logging.error
exception = logging.exception
critical = logging.critical
log = logging.log
# handlers
StreamHandler = logging.StreamHandler
WatchedFileHandler = logging.handlers.WatchedFileHandler
@@ -106,7 +103,7 @@ logging.addLevelName(AUDIT, 'AUDIT')
def _dictify_context(context):
if context == None:
if context is None:
return None
if not isinstance(context, dict) \
and getattr(context, 'to_dict', None):
@@ -127,17 +124,18 @@ def _get_log_file_path(binary=None):
class NovaLogger(logging.Logger):
"""
NovaLogger manages request context and formatting.
"""NovaLogger manages request context and formatting.
This becomes the class that is instanciated by logging.getLogger.
"""
def __init__(self, name, level=NOTSET):
logging.Logger.__init__(self, name, level)
self.setup_from_flags()
def setup_from_flags(self):
"""Setup logger from flags"""
"""Setup logger from flags."""
level = NOTSET
for pair in FLAGS.default_log_levels:
logger, _sep, level_name = pair.partition('=')
@@ -148,7 +146,7 @@ class NovaLogger(logging.Logger):
self.setLevel(level)
def _log(self, level, msg, args, exc_info=None, extra=None, context=None):
"""Extract context from any log call"""
"""Extract context from any log call."""
if not extra:
extra = {}
if context:
@@ -157,17 +155,17 @@ class NovaLogger(logging.Logger):
return logging.Logger._log(self, level, msg, args, exc_info, extra)
def addHandler(self, handler):
"""Each handler gets our custom formatter"""
"""Each handler gets our custom formatter."""
handler.setFormatter(_formatter)
return logging.Logger.addHandler(self, handler)
def audit(self, msg, *args, **kwargs):
"""Shortcut for our AUDIT level"""
"""Shortcut for our AUDIT level."""
if self.isEnabledFor(AUDIT):
self._log(AUDIT, msg, args, **kwargs)
def exception(self, msg, *args, **kwargs):
"""Logging.exception doesn't handle kwargs, so breaks context"""
"""Logging.exception doesn't handle kwargs, so breaks context."""
if not kwargs.get('exc_info'):
kwargs['exc_info'] = 1
self.error(msg, *args, **kwargs)
@@ -181,14 +179,13 @@ class NovaLogger(logging.Logger):
for k in env.keys():
if not isinstance(env[k], str):
env.pop(k)
message = "Environment: %s" % json.dumps(env)
message = 'Environment: %s' % json.dumps(env)
kwargs.pop('exc_info')
self.error(message, **kwargs)
class NovaFormatter(logging.Formatter):
"""
A nova.context.RequestContext aware formatter configured through flags.
"""A nova.context.RequestContext aware formatter configured through flags.
The flags used to set format strings are: logging_context_foramt_string
and logging_default_format_string. You can also specify
@@ -197,10 +194,11 @@ class NovaFormatter(logging.Formatter):
For information about what variables are available for the formatter see:
http://docs.python.org/library/logging.html#formatter
"""
def format(self, record):
"""Uses contextstring if request_id is set, otherwise default"""
"""Uses contextstring if request_id is set, otherwise default."""
if record.__dict__.get('request_id', None):
self._fmt = FLAGS.logging_context_format_string
else:
@@ -214,20 +212,21 @@ class NovaFormatter(logging.Formatter):
return logging.Formatter.format(self, record)
def formatException(self, exc_info, record=None):
"""Format exception output with FLAGS.logging_exception_prefix"""
"""Format exception output with FLAGS.logging_exception_prefix."""
if not record:
return logging.Formatter.formatException(self, exc_info)
stringbuffer = cStringIO.StringIO()
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2],
None, stringbuffer)
lines = stringbuffer.getvalue().split("\n")
lines = stringbuffer.getvalue().split('\n')
stringbuffer.close()
formatted_lines = []
for line in lines:
pl = FLAGS.logging_exception_prefix % record.__dict__
fl = "%s%s" % (pl, line)
fl = '%s%s' % (pl, line)
formatted_lines.append(fl)
return "\n".join(formatted_lines)
return '\n'.join(formatted_lines)
_formatter = NovaFormatter()
@@ -241,7 +240,7 @@ class NovaRootLogger(NovaLogger):
NovaLogger.__init__(self, name, level)
def setup_from_flags(self):
"""Setup logger from flags"""
"""Setup logger from flags."""
global _filelog
if FLAGS.use_syslog:
self.syslog = SysLogHandler(address='/dev/log')

View File

@@ -16,7 +16,8 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
"""Base Manager class.
Managers are responsible for a certain aspect of the sytem. It is a logical
grouping of code relating to a portion of the system. In general other
components should be using the manager to make changes to the components that
@@ -49,16 +50,19 @@ Managers will often provide methods for initial setup of a host or periodic
tasksto a wrapping service.
This module provides Manager, a base class for managers.
"""
from nova import utils
from nova import flags
from nova import log as logging
from nova import utils
from nova.db import base
from nova.scheduler import api
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.manager')
@@ -70,23 +74,29 @@ class Manager(base.Base):
super(Manager, self).__init__(db_driver)
def periodic_tasks(self, context=None):
"""Tasks to be run at a periodic interval"""
"""Tasks to be run at a periodic interval."""
pass
def init_host(self):
"""Do any initialization that needs to be run if this is a standalone
service. Child classes should override this method."""
"""Handle initialization if this is a standalone service.
Child classes should override this method.
"""
pass
class SchedulerDependentManager(Manager):
"""Periodically send capability updates to the Scheduler services.
Services that need to update the Scheduler of their capabilities
should derive from this class. Otherwise they can derive from
manager.Manager directly. Updates are only sent after
update_service_capabilities is called with non-None values."""
def __init__(self, host=None, db_driver=None, service_name="undefined"):
Services that need to update the Scheduler of their capabilities
should derive from this class. Otherwise they can derive from
manager.Manager directly. Updates are only sent after
update_service_capabilities is called with non-None values.
"""
def __init__(self, host=None, db_driver=None, service_name='undefined'):
self.last_capabilities = None
self.service_name = service_name
super(SchedulerDependentManager, self).__init__(host, db_driver)
@@ -96,9 +106,9 @@ class SchedulerDependentManager(Manager):
self.last_capabilities = capabilities
def periodic_tasks(self, context=None):
"""Pass data back to the scheduler at a periodic interval"""
"""Pass data back to the scheduler at a periodic interval."""
if self.last_capabilities:
LOG.debug(_("Notifying Schedulers of capabilities ..."))
LOG.debug(_('Notifying Schedulers of capabilities ...'))
api.update_service_capabilities(context, self.service_name,
self.host, self.last_capabilities)

View File

@@ -66,6 +66,21 @@ class API(base.Base):
if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode):
fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip)
floating_ip = self.db.floating_ip_get_by_address(context, floating_ip)
# Check if the floating ip address is allocated
if floating_ip['project_id'] is None:
raise exception.ApiError(_("Address (%s) is not allocated") %
floating_ip['address'])
# Check if the floating ip address is allocated to the same project
if floating_ip['project_id'] != context.project_id:
LOG.warn(_("Address (%(address)s) is not allocated to your "
"project (%(project)s)"),
{'address': floating_ip['address'],
'project': context.project_id})
raise exception.ApiError(_("Address (%(address)s) is not "
"allocated to your project"
"(%(project)s)") %
{'address': floating_ip['address'],
'project': context.project_id})
# NOTE(vish): Perhaps we should just pass this on to compute and
# let compute communicate with network.
host = fixed_ip['network']['host']

View File

@@ -391,6 +391,12 @@ def unbind_floating_ip(floating_ip):
'dev', FLAGS.public_interface)
def ensure_metadata_ip():
"""Sets up local metadata ip"""
_execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32',
'scope', 'link', 'dev', 'lo', check_exit_code=False)
def ensure_vlan_forward(public_ip, port, private_ip):
"""Sets up forwarding rules for vlan"""
iptables_manager.ipv4['filter'].add_rule("FORWARD",
@@ -442,6 +448,7 @@ def ensure_vlan(vlan_num):
return interface
@utils.synchronized('ensure_bridge', external=True)
def ensure_bridge(bridge, interface, net_attrs=None):
"""Create a bridge unless it already exists.
@@ -495,6 +502,8 @@ def ensure_bridge(bridge, interface, net_attrs=None):
fields = line.split()
if fields and fields[0] == "0.0.0.0" and fields[-1] == interface:
gateway = fields[1]
_execute('sudo', 'route', 'del', 'default', 'gw', gateway,
'dev', interface, check_exit_code=False)
out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface,
'scope', 'global')
for line in out.split("\n"):
@@ -504,7 +513,7 @@ def ensure_bridge(bridge, interface, net_attrs=None):
_execute(*_ip_bridge_cmd('del', params, fields[-1]))
_execute(*_ip_bridge_cmd('add', params, bridge))
if gateway:
_execute('sudo', 'route', 'add', '0.0.0.0', 'gw', gateway)
_execute('sudo', 'route', 'add', 'default', 'gw', gateway)
out, err = _execute('sudo', 'brctl', 'addif', bridge, interface,
check_exit_code=False)

View File

@@ -126,6 +126,7 @@ class NetworkManager(manager.SchedulerDependentManager):
standalone service.
"""
self.driver.init_host()
self.driver.ensure_metadata_ip()
# Set up networking for the projects for which we're already
# the designated network host.
ctxt = context.get_admin_context()

View File

@@ -47,7 +47,7 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):
network_ref = network_utils.NetworkHelper.find_network_with_name_label(
session,
bridge)
if network_ref == None:
if network_ref is None:
# If bridge does not exists
# 1 - create network
description = "network for nova bridge %s" % bridge

View File

@@ -15,16 +15,15 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Quotas for instances, volumes, and floating ips
"""
"""Quotas for instances, volumes, and floating ips."""
from nova import db
from nova import exception
from nova import flags
FLAGS = flags.FLAGS
FLAGS = flags.FLAGS
flags.DEFINE_integer('quota_instances', 10,
'number of instances allowed per project')
flags.DEFINE_integer('quota_cores', 20,
@@ -64,7 +63,7 @@ def get_quota(context, project_id):
def allowed_instances(context, num_instances, instance_type):
"""Check quota and return min(num_instances, allowed_instances)"""
"""Check quota and return min(num_instances, allowed_instances)."""
project_id = context.project_id
context = context.elevated()
used_instances, used_cores = db.instance_data_get_for_project(context,
@@ -79,7 +78,7 @@ def allowed_instances(context, num_instances, instance_type):
def allowed_volumes(context, num_volumes, size):
"""Check quota and return min(num_volumes, allowed_volumes)"""
"""Check quota and return min(num_volumes, allowed_volumes)."""
project_id = context.project_id
context = context.elevated()
used_volumes, used_gigabytes = db.volume_data_get_for_project(context,
@@ -95,7 +94,7 @@ def allowed_volumes(context, num_volumes, size):
def allowed_floating_ips(context, num_floating_ips):
"""Check quota and return min(num_floating_ips, allowed_floating_ips)"""
"""Check quota and return min(num_floating_ips, allowed_floating_ips)."""
project_id = context.project_id
context = context.elevated()
used_floating_ips = db.floating_ip_count_by_project(context, project_id)
@@ -105,7 +104,7 @@ def allowed_floating_ips(context, num_floating_ips):
def allowed_metadata_items(context, num_metadata_items):
"""Check quota; return min(num_metadata_items,allowed_metadata_items)"""
"""Check quota; return min(num_metadata_items,allowed_metadata_items)."""
project_id = context.project_id
context = context.elevated()
quota = get_quota(context, project_id)
@@ -114,20 +113,20 @@ def allowed_metadata_items(context, num_metadata_items):
def allowed_injected_files(context):
"""Return the number of injected files allowed"""
"""Return the number of injected files allowed."""
return FLAGS.quota_max_injected_files
def allowed_injected_file_content_bytes(context):
"""Return the number of bytes allowed per injected file content"""
"""Return the number of bytes allowed per injected file content."""
return FLAGS.quota_max_injected_file_content_bytes
def allowed_injected_file_path_bytes(context):
"""Return the number of bytes allowed in an injected file path"""
"""Return the number of bytes allowed in an injected file path."""
return FLAGS.quota_max_injected_file_path_bytes
class QuotaError(exception.ApiError):
"""Quota Exceeeded"""
"""Quota Exceeeded."""
pass

View File

@@ -16,9 +16,12 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
AMQP-based RPC. Queues have consumers and publishers.
"""AMQP-based RPC.
Queues have consumers and publishers.
No fan-out support yet.
"""
import json
@@ -40,17 +43,19 @@ from nova import log as logging
from nova import utils
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.rpc')
FLAGS = flags.FLAGS
flags.DEFINE_integer('rpc_thread_pool_size', 1024, 'Size of RPC thread pool')
class Connection(carrot_connection.BrokerConnection):
"""Connection instance object"""
"""Connection instance object."""
@classmethod
def instance(cls, new=True):
"""Returns the instance"""
"""Returns the instance."""
if new or not hasattr(cls, '_instance'):
params = dict(hostname=FLAGS.rabbit_host,
port=FLAGS.rabbit_port,
@@ -71,9 +76,11 @@ class Connection(carrot_connection.BrokerConnection):
@classmethod
def recreate(cls):
"""Recreates the connection instance
"""Recreates the connection instance.
This is necessary to recover from some network errors/disconnects"""
This is necessary to recover from some network errors/disconnects.
"""
try:
del cls._instance
except AttributeError, e:
@@ -84,10 +91,12 @@ class Connection(carrot_connection.BrokerConnection):
class Consumer(messaging.Consumer):
"""Consumer base class
"""Consumer base class.
Contains methods for connecting the fetch method to async loops.
Contains methods for connecting the fetch method to async loops
"""
def __init__(self, *args, **kwargs):
for i in xrange(FLAGS.rabbit_max_retries):
if i > 0:
@@ -100,19 +109,18 @@ class Consumer(messaging.Consumer):
fl_host = FLAGS.rabbit_host
fl_port = FLAGS.rabbit_port
fl_intv = FLAGS.rabbit_retry_interval
LOG.error(_("AMQP server on %(fl_host)s:%(fl_port)d is"
" unreachable: %(e)s. Trying again in %(fl_intv)d"
" seconds.")
% locals())
LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is'
' unreachable: %(e)s. Trying again in %(fl_intv)d'
' seconds.') % locals())
self.failed_connection = True
if self.failed_connection:
LOG.error(_("Unable to connect to AMQP server "
"after %d tries. Shutting down."),
LOG.error(_('Unable to connect to AMQP server '
'after %d tries. Shutting down.'),
FLAGS.rabbit_max_retries)
sys.exit(1)
def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False):
"""Wraps the parent fetch with some logic for failed connections"""
"""Wraps the parent fetch with some logic for failed connection."""
# TODO(vish): the logic for failed connections and logging should be
# refactored into some sort of connection manager object
try:
@@ -125,17 +133,15 @@ class Consumer(messaging.Consumer):
self.declare()
super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks)
if self.failed_connection:
LOG.error(_("Reconnected to queue"))
LOG.error(_('Reconnected to queue'))
self.failed_connection = False
# NOTE(vish): This is catching all errors because we really don't
# want exceptions to be logged 10 times a second if some
# persistent failure occurs.
except Exception, e: # pylint: disable=W0703
if not self.failed_connection:
LOG.exception(_("Failed to fetch message from queue: %s" % e))
LOG.exception(_('Failed to fetch message from queue: %s' % e))
self.failed_connection = True
else:
LOG.exception(_("Unhandled exception %s" % e))
def attach_to_eventlet(self):
"""Only needed for unit tests!"""
@@ -145,8 +151,9 @@ class Consumer(messaging.Consumer):
class AdapterConsumer(Consumer):
"""Calls methods on a proxy object based on method and args"""
def __init__(self, connection=None, topic="broadcast", proxy=None):
"""Calls methods on a proxy object based on method and args."""
def __init__(self, connection=None, topic='broadcast', proxy=None):
LOG.debug(_('Initing the Adapter Consumer for %s') % topic)
self.proxy = proxy
self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size)
@@ -158,13 +165,14 @@ class AdapterConsumer(Consumer):
@exception.wrap_exception
def _receive(self, message_data, message):
"""Magically looks for a method on the proxy object and calls it
"""Magically looks for a method on the proxy object and calls it.
Message data should be a dictionary with two keys:
method: string representing the method to call
args: dictionary of arg: value
Example: {'method': 'echo', 'args': {'value': 42}}
"""
LOG.debug(_('received %s') % message_data)
msg_id = message_data.pop('_msg_id', None)
@@ -191,22 +199,23 @@ class AdapterConsumer(Consumer):
if msg_id:
msg_reply(msg_id, rval, None)
except Exception as e:
logging.exception("Exception during message handling")
logging.exception('Exception during message handling')
if msg_id:
msg_reply(msg_id, None, sys.exc_info())
return
class Publisher(messaging.Publisher):
"""Publisher base class"""
"""Publisher base class."""
pass
class TopicAdapterConsumer(AdapterConsumer):
"""Consumes messages on a specific topic"""
exchange_type = "topic"
"""Consumes messages on a specific topic."""
def __init__(self, connection=None, topic="broadcast", proxy=None):
exchange_type = 'topic'
def __init__(self, connection=None, topic='broadcast', proxy=None):
self.queue = topic
self.routing_key = topic
self.exchange = FLAGS.control_exchange
@@ -216,27 +225,29 @@ class TopicAdapterConsumer(AdapterConsumer):
class FanoutAdapterConsumer(AdapterConsumer):
"""Consumes messages from a fanout exchange"""
exchange_type = "fanout"
"""Consumes messages from a fanout exchange."""
def __init__(self, connection=None, topic="broadcast", proxy=None):
self.exchange = "%s_fanout" % topic
exchange_type = 'fanout'
def __init__(self, connection=None, topic='broadcast', proxy=None):
self.exchange = '%s_fanout' % topic
self.routing_key = topic
unique = uuid.uuid4().hex
self.queue = "%s_fanout_%s" % (topic, unique)
self.queue = '%s_fanout_%s' % (topic, unique)
self.durable = False
LOG.info(_("Created '%(exchange)s' fanout exchange "
"with '%(key)s' routing key"),
dict(exchange=self.exchange, key=self.routing_key))
LOG.info(_('Created "%(exchange)s" fanout exchange '
'with "%(key)s" routing key'),
dict(exchange=self.exchange, key=self.routing_key))
super(FanoutAdapterConsumer, self).__init__(connection=connection,
topic=topic, proxy=proxy)
class TopicPublisher(Publisher):
"""Publishes messages on a specific topic"""
exchange_type = "topic"
"""Publishes messages on a specific topic."""
def __init__(self, connection=None, topic="broadcast"):
exchange_type = 'topic'
def __init__(self, connection=None, topic='broadcast'):
self.routing_key = topic
self.exchange = FLAGS.control_exchange
self.durable = False
@@ -245,20 +256,22 @@ class TopicPublisher(Publisher):
class FanoutPublisher(Publisher):
"""Publishes messages to a fanout exchange."""
exchange_type = "fanout"
exchange_type = 'fanout'
def __init__(self, topic, connection=None):
self.exchange = "%s_fanout" % topic
self.queue = "%s_fanout" % topic
self.exchange = '%s_fanout' % topic
self.queue = '%s_fanout' % topic
self.durable = False
LOG.info(_("Creating '%(exchange)s' fanout exchange"),
dict(exchange=self.exchange))
LOG.info(_('Creating "%(exchange)s" fanout exchange'),
dict(exchange=self.exchange))
super(FanoutPublisher, self).__init__(connection=connection)
class DirectConsumer(Consumer):
"""Consumes messages directly on a channel specified by msg_id"""
exchange_type = "direct"
"""Consumes messages directly on a channel specified by msg_id."""
exchange_type = 'direct'
def __init__(self, connection=None, msg_id=None):
self.queue = msg_id
@@ -270,8 +283,9 @@ class DirectConsumer(Consumer):
class DirectPublisher(Publisher):
"""Publishes messages directly on a channel specified by msg_id"""
exchange_type = "direct"
"""Publishes messages directly on a channel specified by msg_id."""
exchange_type = 'direct'
def __init__(self, connection=None, msg_id=None):
self.routing_key = msg_id
@@ -281,9 +295,9 @@ class DirectPublisher(Publisher):
def msg_reply(msg_id, reply=None, failure=None):
"""Sends a reply or an error on the channel signified by msg_id
"""Sends a reply or an error on the channel signified by msg_id.
failure should be a sys.exc_info() tuple.
Failure should be a sys.exc_info() tuple.
"""
if failure:
@@ -305,17 +319,20 @@ def msg_reply(msg_id, reply=None, failure=None):
class RemoteError(exception.Error):
"""Signifies that a remote class has raised an exception
"""Signifies that a remote class has raised an exception.
Containes a string representation of the type of the original exception,
the value of the original exception, and the traceback. These are
sent to the parent as a joined string so printing the exception
contains all of the relevent info."""
contains all of the relevent info.
"""
def __init__(self, exc_type, value, traceback):
self.exc_type = exc_type
self.value = value
self.traceback = traceback
super(RemoteError, self).__init__("%s %s\n%s" % (exc_type,
super(RemoteError, self).__init__('%s %s\n%s' % (exc_type,
value,
traceback))
@@ -341,6 +358,7 @@ def _pack_context(msg, context):
context out into a bunch of separate keys. If we want to support
more arguments in rabbit messages, we may want to do the same
for args at some point.
"""
context = dict([('_context_%s' % key, value)
for (key, value) in context.to_dict().iteritems()])
@@ -348,11 +366,11 @@ def _pack_context(msg, context):
def call(context, topic, msg):
"""Sends a message on a topic and wait for a response"""
LOG.debug(_("Making asynchronous call on %s ..."), topic)
"""Sends a message on a topic and wait for a response."""
LOG.debug(_('Making asynchronous call on %s ...'), topic)
msg_id = uuid.uuid4().hex
msg.update({'_msg_id': msg_id})
LOG.debug(_("MSG_ID is %s") % (msg_id))
LOG.debug(_('MSG_ID is %s') % (msg_id))
_pack_context(msg, context)
class WaitMessage(object):
@@ -389,8 +407,8 @@ def call(context, topic, msg):
def cast(context, topic, msg):
"""Sends a message on a topic without waiting for a response"""
LOG.debug(_("Making asynchronous cast on %s..."), topic)
"""Sends a message on a topic without waiting for a response."""
LOG.debug(_('Making asynchronous cast on %s...'), topic)
_pack_context(msg, context)
conn = Connection.instance()
publisher = TopicPublisher(connection=conn, topic=topic)
@@ -399,8 +417,8 @@ def cast(context, topic, msg):
def fanout_cast(context, topic, msg):
"""Sends a message on a fanout exchange without waiting for a response"""
LOG.debug(_("Making asynchronous fanout cast..."))
"""Sends a message on a fanout exchange without waiting for a response."""
LOG.debug(_('Making asynchronous fanout cast...'))
_pack_context(msg, context)
conn = Connection.instance()
publisher = FanoutPublisher(topic, connection=conn)
@@ -409,14 +427,14 @@ def fanout_cast(context, topic, msg):
def generic_response(message_data, message):
"""Logs a result and exits"""
"""Logs a result and exits."""
LOG.debug(_('response %s'), message_data)
message.ack()
sys.exit(0)
def send_message(topic, message, wait=True):
"""Sends a message for testing"""
"""Sends a message for testing."""
msg_id = uuid.uuid4().hex
message.update({'_msg_id': msg_id})
LOG.debug(_('topic is %s'), topic)
@@ -427,14 +445,14 @@ def send_message(topic, message, wait=True):
queue=msg_id,
exchange=msg_id,
auto_delete=True,
exchange_type="direct",
exchange_type='direct',
routing_key=msg_id)
consumer.register_callback(generic_response)
publisher = messaging.Publisher(connection=Connection.instance(),
exchange=FLAGS.control_exchange,
durable=False,
exchange_type="topic",
exchange_type='topic',
routing_key=topic)
publisher.send(message)
publisher.close()
@@ -443,8 +461,8 @@ def send_message(topic, message, wait=True):
consumer.wait()
if __name__ == "__main__":
# NOTE(vish): you can send messages from the command line using
# topic and a json sting representing a dictionary
# for the method
if __name__ == '__main__':
# You can send messages from the command line using
# topic and a json string representing a dictionary
# for the method
send_message(sys.argv[1], json.loads(sys.argv[2]))

View File

@@ -34,5 +34,7 @@ class ChanceScheduler(driver.Scheduler):
hosts = self.hosts_up(context, topic)
if not hosts:
raise driver.NoValidHost(_("No hosts found"))
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))
return hosts[int(random.random() * len(hosts))]

View File

@@ -72,7 +72,9 @@ class SimpleScheduler(chance.ChanceScheduler):
{'host': service['host'],
'scheduled_at': now})
return service['host']
raise driver.NoValidHost(_("No hosts found"))
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))
def schedule_create_volume(self, context, volume_id, *_args, **_kwargs):
"""Picks a host that is up and has the fewest volumes."""
@@ -107,7 +109,9 @@ class SimpleScheduler(chance.ChanceScheduler):
{'host': service['host'],
'scheduled_at': now})
return service['host']
raise driver.NoValidHost(_("No hosts found"))
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))
def schedule_set_network_host(self, context, *_args, **_kwargs):
"""Picks a host that is up and has the fewest networks."""
@@ -119,4 +123,6 @@ class SimpleScheduler(chance.ChanceScheduler):
raise driver.NoValidHost(_("All hosts have too many networks"))
if self.service_is_up(service):
return service['host']
raise driver.NoValidHost(_("No hosts found"))
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))

View File

@@ -52,5 +52,8 @@ class ZoneScheduler(driver.Scheduler):
zone = _kwargs.get('availability_zone')
hosts = self.hosts_up_with_zone(context, topic, zone)
if not hosts:
raise driver.NoValidHost(_("No hosts found"))
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))
return hosts[int(random.random() * len(hosts))]

View File

@@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Generic Node baseclass for all workers that run on hosts
"""
"""Generic Node baseclass for all workers that run on hosts."""
import inspect
import os
@@ -30,13 +28,11 @@ from eventlet import event
from eventlet import greenthread
from eventlet import greenpool
from sqlalchemy.exc import OperationalError
from nova import context
from nova import db
from nova import exception
from nova import log as logging
from nova import flags
from nova import log as logging
from nova import rpc
from nova import utils
from nova import version
@@ -79,7 +75,7 @@ class Service(object):
def start(self):
vcs_string = version.version_string_with_vcs()
logging.audit(_("Starting %(topic)s node (version %(vcs_string)s)"),
logging.audit(_('Starting %(topic)s node (version %(vcs_string)s)'),
{'topic': self.topic, 'vcs_string': vcs_string})
self.manager.init_host()
self.model_disconnected = False
@@ -140,29 +136,24 @@ class Service(object):
return getattr(manager, key)
@classmethod
def create(cls,
host=None,
binary=None,
topic=None,
manager=None,
report_interval=None,
periodic_interval=None):
def create(cls, host=None, binary=None, topic=None, manager=None,
report_interval=None, periodic_interval=None):
"""Instantiates class and passes back application object.
Args:
host, defaults to FLAGS.host
binary, defaults to basename of executable
topic, defaults to bin_name - "nova-" part
manager, defaults to FLAGS.<topic>_manager
report_interval, defaults to FLAGS.report_interval
periodic_interval, defaults to FLAGS.periodic_interval
:param host: defaults to FLAGS.host
:param binary: defaults to basename of executable
:param topic: defaults to bin_name - 'nova-' part
:param manager: defaults to FLAGS.<topic>_manager
:param report_interval: defaults to FLAGS.report_interval
:param periodic_interval: defaults to FLAGS.periodic_interval
"""
if not host:
host = FLAGS.host
if not binary:
binary = os.path.basename(inspect.stack()[-1][1])
if not topic:
topic = binary.rpartition("nova-")[2]
topic = binary.rpartition('nova-')[2]
if not manager:
manager = FLAGS.get('%s_manager' % topic, None)
if not report_interval:
@@ -175,12 +166,12 @@ class Service(object):
return service_obj
def kill(self):
"""Destroy the service object in the datastore"""
"""Destroy the service object in the datastore."""
self.stop()
try:
db.service_destroy(context.get_admin_context(), self.service_id)
except exception.NotFound:
logging.warn(_("Service killed that has no database entry"))
logging.warn(_('Service killed that has no database entry'))
def stop(self):
for x in self.timers:
@@ -198,7 +189,7 @@ class Service(object):
pass
def periodic_tasks(self):
"""Tasks to be run at a periodic interval"""
"""Tasks to be run at a periodic interval."""
self.manager.periodic_tasks(context.get_admin_context())
def report_state(self):
@@ -208,8 +199,8 @@ class Service(object):
try:
service_ref = db.service_get(ctxt, self.service_id)
except exception.NotFound:
logging.debug(_("The service database object disappeared, "
"Recreating it."))
logging.debug(_('The service database object disappeared, '
'Recreating it.'))
self._create_service_ref(ctxt)
service_ref = db.service_get(ctxt, self.service_id)
@@ -218,23 +209,24 @@ class Service(object):
{'report_count': service_ref['report_count'] + 1})
# TODO(termie): make this pattern be more elegant.
if getattr(self, "model_disconnected", False):
if getattr(self, 'model_disconnected', False):
self.model_disconnected = False
logging.error(_("Recovered model server connection!"))
logging.error(_('Recovered model server connection!'))
# TODO(vish): this should probably only catch connection errors
except Exception: # pylint: disable=W0702
if not getattr(self, "model_disconnected", False):
if not getattr(self, 'model_disconnected', False):
self.model_disconnected = True
logging.exception(_("model server went away"))
logging.exception(_('model server went away'))
class WsgiService(object):
"""Base class for WSGI based services.
For each api you define, you must also define these flags:
:<api>_listen: The address on which to listen
:<api>_listen_port: The port on which to listen
:<api>_listen: The address on which to listen
:<api>_listen_port: The port on which to listen
"""
def __init__(self, conf, apis):
@@ -250,13 +242,14 @@ class WsgiService(object):
class ApiService(WsgiService):
"""Class for our nova-api service"""
"""Class for our nova-api service."""
@classmethod
def create(cls, conf=None):
if not conf:
conf = wsgi.paste_config_file(FLAGS.api_paste_config)
if not conf:
message = (_("No paste configuration found for: %s"),
message = (_('No paste configuration found for: %s'),
FLAGS.api_paste_config)
raise exception.Error(message)
api_endpoints = ['ec2', 'osapi']
@@ -280,11 +273,11 @@ def serve(*services):
FLAGS.ParseNewFlags()
name = '_'.join(x.binary for x in services)
logging.debug(_("Serving %s"), name)
logging.debug(_("Full set of FLAGS:"))
logging.debug(_('Serving %s'), name)
logging.debug(_('Full set of FLAGS:'))
for flag in FLAGS:
flag_get = FLAGS.get(flag, None)
logging.debug("%(flag)s : %(flag_get)s" % locals())
logging.debug('%(flag)s : %(flag_get)s' % locals())
for x in services:
x.start()
@@ -315,20 +308,20 @@ def serve_wsgi(cls, conf=None):
def _run_wsgi(paste_config_file, apis):
logging.debug(_("Using paste.deploy config at: %s"), paste_config_file)
logging.debug(_('Using paste.deploy config at: %s'), paste_config_file)
apps = []
for api in apis:
config = wsgi.load_paste_configuration(paste_config_file, api)
if config is None:
logging.debug(_("No paste configuration for app: %s"), api)
logging.debug(_('No paste configuration for app: %s'), api)
continue
logging.debug(_("App Config: %(api)s\n%(config)r") % locals())
logging.info(_("Running %s API"), api)
logging.debug(_('App Config: %(api)s\n%(config)r') % locals())
logging.info(_('Running %s API'), api)
app = wsgi.load_paste_app(paste_config_file, api)
apps.append((app, getattr(FLAGS, "%s_listen_port" % api),
getattr(FLAGS, "%s_listen" % api)))
apps.append((app, getattr(FLAGS, '%s_listen_port' % api),
getattr(FLAGS, '%s_listen' % api)))
if len(apps) == 0:
logging.error(_("No known API applications configured in %s."),
logging.error(_('No known API applications configured in %s.'),
paste_config_file)
return

Some files were not shown because too many files have changed in this diff Show More