Merged with trunk, fixed up test that wasn't checking namespace

This commit is contained in:
Justin Santa Barbara 2011-04-06 14:02:16 -07:00
commit 5530504c14
40 changed files with 417 additions and 559 deletions

View File

@ -32,6 +32,7 @@ Jesse Andrews <anotherjesse@gmail.com>
Joe Heck <heckj@mac.com>
Joel Moore <joelbm24@gmail.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>

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

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

@ -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

@ -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):
@ -757,6 +765,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']
@ -884,10 +894,7 @@ class CloudController(object):
image_type = image['properties'].get('type')
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')
@ -895,11 +902,15 @@ class CloudController(object):
if ramdisk_id:
i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ramdisk')
i['imageOwnerId'] = image['properties'].get('owner_id')
i['imageLocation'] = image['properties'].get('image_location')
if name:
i['imageLocation'] = "%s (%s)" % (image['properties'].
get('image_location'), name)
else:
i['imageLocation'] = image['properties'].get('image_location')
i['imageState'] = image['properties'].get('image_state')
i['displayName'] = image.get('name')
i['displayName'] = name
i['description'] = image.get('description')
i['type'] = image_type
i['imageType'] = image_type
i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True'
i['architecture'] = image['properties'].get('architecture')
return i

View File

@ -180,8 +180,7 @@ class Controller(common.OpenstackController):
builder = self._get_view_builder(req)
server = builder.build(inst, is_detail=True)
password = "%s%s" % (server['server']['name'][:4],
utils.generate_password(12))
password = utils.generate_password(16)
server['server']['adminPass'] = password
self.compute_api.set_admin_password(context, server['server']['id'],
password)
@ -288,11 +287,12 @@ class Controller(common.OpenstackController):
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(common.OpenstackController):
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)
@ -629,6 +632,19 @@ 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)

View File

@ -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,7 +77,7 @@ 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 = {}

View File

@ -37,10 +37,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)

View File

@ -215,9 +215,12 @@ 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)
@ -227,13 +230,16 @@ def generate_vpn_files(project_id):
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',
'geninter.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:
csr_text = csrfile.read()
@ -263,6 +269,8 @@ def _sign_csr(csr_text, 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)

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

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

@ -74,7 +74,12 @@ class Connection(carrot_connection.BrokerConnection):
"""Recreates the connection instance
This is necessary to recover from some network errors/disconnects"""
del cls._instance
try:
del cls._instance
except AttributeError, e:
# The _instance stuff is for testing purposes. Usually we don't use
# it. So don't freak out if it doesn't exist.
pass
return cls.instance()
@ -125,9 +130,9 @@ class Consumer(messaging.Consumer):
# 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: # pylint: disable=W0703
except Exception, e: # pylint: disable=W0703
if not self.failed_connection:
LOG.exception(_("Failed to fetch message from queue"))
LOG.exception(_("Failed to fetch message from queue: %s" % e))
self.failed_connection = True
def attach_to_eventlet(self):

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

@ -263,7 +263,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{'id': 124, 'name': 'queued backup'},
{'id': 125, 'name': 'saving backup'},
{'id': 126, 'name': 'active backup'},
{'id': 127, 'name': 'killed backup'}]
{'id': 127, 'name': 'killed backup'},
{'id': 129, 'name': None}]
self.assertDictListMatch(response_list, expected)
@ -340,6 +341,25 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
self.assertEqual(expected_image.toxml(), actual_image.toxml())
def test_get_image_xml_no_name(self):
request = webob.Request.blank('/v1.0/images/129')
request.accept = "application/xml"
response = request.get_response(fakes.wsgi_app())
actual_image = minidom.parseString(response.body.replace(" ", ""))
expected_now = self.NOW_API_FORMAT
expected_image = minidom.parseString("""
<image id="129"
name="None"
updated="%(expected_now)s"
created="%(expected_now)s"
status="ACTIVE"
xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" />
""" % (locals()))
self.assertEqual(expected_image.toxml(), actual_image.toxml())
def test_get_image_v1_1_xml(self):
request = webob.Request.blank('/v1.1/images/123')
request.accept = "application/xml"
@ -522,6 +542,13 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'FAILED',
},
{
'id': 129,
'name': None,
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
}]
self.assertDictListMatch(expected, response_list)
@ -641,7 +668,29 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
"type": "application/xml",
"href": "http://localhost/v1.1/images/127",
}],
}]
},
{
'id': 129,
'name': None,
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
"links": [{
"rel": "self",
"href": "http://localhost/v1.1/images/129",
},
{
"rel": "bookmark",
"type": "application/json",
"href": "http://localhost/v1.1/images/129",
},
{
"rel": "bookmark",
"type": "application/xml",
"href": "http://localhost/v1.1/images/129",
}],
},
]
self.assertDictListMatch(expected, response_list)
@ -700,4 +749,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
status='active', properties=other_backup_properties)
image_id += 1
# Image without a name
add_fixture(id=image_id, is_public=True, status='active',
properties={})
image_id += 1
return fixtures

View File

@ -377,7 +377,6 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
server = json.loads(res.body)['server']
self.assertEqual('serv', server['adminPass'][:4])
self.assertEqual(16, len(server['adminPass']))
self.assertEqual('server_test', server['name'])
self.assertEqual(1, server['id'])
@ -486,7 +485,6 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
server = json.loads(res.body)['server']
self.assertEqual('serv', server['adminPass'][:4])
self.assertEqual(16, len(server['adminPass']))
self.assertEqual('server_test', server['name'])
self.assertEqual(1, server['id'])
@ -631,6 +629,7 @@ class ServersTest(test.TestCase):
self.assertEqual(s['name'], 'server%d' % i)
self.assertEqual(s['imageId'], '10')
self.assertEqual(s['flavorId'], '1')
self.assertEqual(s['status'], 'BUILD')
self.assertEqual(s['metadata']['seq'], i)
def test_get_all_server_details_v1_1(self):
@ -644,6 +643,7 @@ class ServersTest(test.TestCase):
self.assertEqual(s['name'], 'server%d' % i)
self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10')
self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1')
self.assertEqual(s['status'], 'BUILD')
self.assertEqual(s['metadata']['seq'], i)
def test_get_all_server_details_with_host(self):
@ -764,6 +764,74 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
def test_server_change_password(self):
body = {'changePassword': {'adminPass': '1234pass'}}
req = webob.Request.blank('/v1.0/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 501)
def test_server_change_password_v1_1(self):
class MockSetAdminPassword(object):
def __init__(self):
self.instance_id = None
self.password = None
def __call__(self, context, instance_id, password):
self.instance_id = instance_id
self.password = password
mock_method = MockSetAdminPassword()
self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method)
body = {'changePassword': {'adminPass': '1234pass'}}
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
self.assertEqual(mock_method.instance_id, '1')
self.assertEqual(mock_method.password, '1234pass')
def test_server_change_password_bad_request_v1_1(self):
body = {'changePassword': {'pass': '12345'}}
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_server_change_password_empty_string_v1_1(self):
body = {'changePassword': {'adminPass': ''}}
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_server_change_password_none_v1_1(self):
body = {'changePassword': {'adminPass': None}}
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_server_change_password_not_a_string_v1_1(self):
body = {'changePassword': {'adminPass': 1234}}
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_server_reboot(self):
body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={},
@ -849,7 +917,7 @@ class ServersTest(test.TestCase):
fake_migration_get)
res = req.get_response(fakes.wsgi_app())
body = json.loads(res.body)
self.assertEqual(body['server']['status'], 'resize-confirm')
self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM')
def test_confirm_resize_server(self):
req = self.webreq('/1/action', 'POST', dict(confirmResize=None))
@ -1426,7 +1494,7 @@ class TestServerInstanceCreation(test.TestCase):
self.assertEquals(response.status_int, 200)
response = json.loads(response.body)
self.assertTrue('adminPass' in response['server'])
self.assertTrue(response['server']['adminPass'].startswith('fake'))
self.assertEqual(16, len(response['server']['adminPass']))
def test_create_instance_admin_pass_xml(self):
request, response, dummy = \
@ -1435,7 +1503,7 @@ class TestServerInstanceCreation(test.TestCase):
dom = minidom.parseString(response.body)
server = dom.childNodes[0]
self.assertEquals(server.nodeName, 'server')
self.assertTrue(server.getAttribute('adminPass').startswith('fake'))
self.assertEqual(16, len(server.getAttribute('adminPass')))
class TestGetKernelRamdiskFromImage(test.TestCase):

View File

@ -41,6 +41,7 @@ from nova.compute import power_state
from nova.api.ec2 import cloud
from nova.api.ec2 import ec2utils
from nova.image import local
from nova.exception import NotFound
FLAGS = flags.FLAGS
@ -71,7 +72,8 @@ class CloudTestCase(test.TestCase):
host = self.network.get_network_host(self.context.elevated())
def fake_show(meh, context, id):
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
'type': 'machine'}}
self.stubs.Set(local.LocalImageService, 'show', fake_show)
self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show)
@ -216,6 +218,35 @@ class CloudTestCase(test.TestCase):
db.service_destroy(self.context, comp1['id'])
db.service_destroy(self.context, comp2['id'])
def test_describe_images(self):
describe_images = self.cloud.describe_images
def fake_detail(meh, context):
return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
'type': 'machine'}}]
def fake_show_none(meh, context, id):
raise NotFound
self.stubs.Set(local.LocalImageService, 'detail', fake_detail)
# list all
result1 = describe_images(self.context)
result1 = result1['imagesSet'][0]
self.assertEqual(result1['imageId'], 'ami-00000001')
# provided a valid image_id
result2 = describe_images(self.context, ['ami-00000001'])
self.assertEqual(1, len(result2['imagesSet']))
# provide more than 1 valid image_id
result3 = describe_images(self.context, ['ami-00000001',
'ami-00000002'])
self.assertEqual(2, len(result3['imagesSet']))
# provide an non-existing image_id
self.stubs.UnsetAll()
self.stubs.Set(local.LocalImageService, 'show', fake_show_none)
self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show_none)
self.assertRaises(NotFound, describe_images,
self.context, ['ami-fake'])
def test_console_output(self):
instance_type = FLAGS.default_instance_type
max_count = 1

View File

@ -485,3 +485,7 @@ class HyperVConnection(driver.ComputeDriver):
def poll_rescued_instances(self, timeout):
pass
def update_available_resource(self, ctxt, host):
"""This method is supported only by libvirt."""
return

View File

@ -842,7 +842,7 @@ class LibvirtConnection(driver.ComputeDriver):
for (network_ref, mapping) in network_info:
ifc_num += 1
if not 'injected' in network_ref:
if not network_ref['injected']:
continue
have_injected_networks = True
@ -970,7 +970,8 @@ class LibvirtConnection(driver.ComputeDriver):
'nics': nics}
if FLAGS.vnc_enabled:
xml_info['vncserver_host'] = FLAGS.vncserver_host
if FLAGS.libvirt_type != 'lxc':
xml_info['vncserver_host'] = FLAGS.vncserver_host
if not rescue:
if instance['kernel_id']:
xml_info['kernel'] = xml_info['basepath'] + "/kernel"

View File

@ -21,10 +21,10 @@ Classes for making VMware VI SOAP calls.
import httplib
from suds import WebFault
from suds.client import Client
from suds.plugin import MessagePlugin
from suds.sudsobject import Property
try:
import suds
except ImportError:
suds = None
from nova import flags
from nova.virt.vmwareapi import error_util
@ -42,24 +42,25 @@ flags.DEFINE_string('vmwareapi_wsdl_loc',
'Refer readme-vmware to setup')
class VIMMessagePlugin(MessagePlugin):
if suds:
class VIMMessagePlugin(suds.plugin.MessagePlugin):
def addAttributeForValue(self, node):
# suds does not handle AnyType properly.
# VI SDK requires type attribute to be set when AnyType is used
if node.name == 'value':
node.set('xsi:type', 'xsd:string')
def addAttributeForValue(self, node):
# suds does not handle AnyType properly.
# VI SDK requires type attribute to be set when AnyType is used
if node.name == 'value':
node.set('xsi:type', 'xsd:string')
def marshalled(self, context):
"""suds will send the specified soap envelope.
Provides the plugin with the opportunity to prune empty
nodes and fixup nodes before sending it to the server.
"""
# suds builds the entire request object based on the wsdl schema.
# VI SDK throws server errors if optional SOAP nodes are sent without
# values, e.g. <test/> as opposed to <test>test</test>
context.envelope.prune()
context.envelope.walk(self.addAttributeForValue)
def marshalled(self, context):
"""suds will send the specified soap envelope.
Provides the plugin with the opportunity to prune empty
nodes and fixup nodes before sending it to the server.
"""
# suds builds the entire request object based on the wsdl schema.
# VI SDK throws server errors if optional SOAP nodes are sent
# without values, e.g. <test/> as opposed to <test>test</test>
context.envelope.prune()
context.envelope.walk(self.addAttributeForValue)
class Vim:
@ -75,6 +76,9 @@ class Vim:
protocol: http or https
host : ESX IPAddress[:port] or ESX Hostname[:port]
"""
if not suds:
raise Exception(_("Unable to import suds."))
self._protocol = protocol
self._host_name = host
wsdl_url = FLAGS.vmwareapi_wsdl_loc
@ -84,7 +88,7 @@ class Vim:
#wsdl_url = '%s://%s/sdk/vimService.wsdl' % (self._protocol,
# self._host_name)
url = '%s://%s/sdk' % (self._protocol, self._host_name)
self.client = Client(wsdl_url, location=url,
self.client = suds.client.Client(wsdl_url, location=url,
plugins=[VIMMessagePlugin()])
self._service_content = \
self.RetrieveServiceContent("ServiceInstance")
@ -127,7 +131,7 @@ class Vim:
# check of the SOAP response
except error_util.VimFaultException, excep:
raise
except WebFault, excep:
except suds.WebFault, excep:
doc = excep.document
detail = doc.childAtPath("/Envelope/Body/Fault/detail")
fault_list = []
@ -163,7 +167,7 @@ class Vim:
"""Builds the request managed object."""
# Request Managed Object Builder
if type(managed_object) == type(""):
mo = Property(managed_object)
mo = suds.sudsobject.Property(managed_object)
mo._type = managed_object
else:
mo = managed_object

View File

@ -47,6 +47,7 @@ from nova.virt.vmwareapi import vim
from nova.virt.vmwareapi import vim_util
from nova.virt.vmwareapi.vmops import VMWareVMOps
LOG = logging.getLogger("nova.virt.vmwareapi_conn")
FLAGS = flags.FLAGS
@ -109,7 +110,7 @@ class VMWareESXConnection(object):
def __init__(self, host_ip, host_username, host_password,
api_retry_count, scheme="https"):
session = VMWareAPISession(host_ip, host_username, host_password,
api_retry_count, scheme=scheme)
api_retry_count, scheme=scheme)
self._vmops = VMWareVMOps(session)
def init_host(self, host):

View File

@ -550,6 +550,7 @@ def paste_config_file(basename):
"""
configfiles = [basename,
os.path.join(FLAGS.state_path, 'etc', 'nova', basename),
os.path.join(FLAGS.state_path, 'etc', basename),
os.path.join(FLAGS.state_path, basename),
'/etc/nova/%s' % basename]

View File

@ -22,6 +22,8 @@
# XenAPI plugin for reading/writing information to xenstore
#
import base64
import commands
try:
import json
except ImportError:
@ -66,7 +68,7 @@ def key_init(self, arg_dict):
try:
resp = _wait_for_agent(self, request_id, arg_dict)
except TimeoutError, e:
raise PluginError("%s" % e)
raise PluginError(e)
return resp
@ -87,7 +89,7 @@ def password(self, arg_dict):
try:
resp = _wait_for_agent(self, request_id, arg_dict)
except TimeoutError, e:
raise PluginError("%s" % e)
raise PluginError(e)
return resp
@ -102,6 +104,75 @@ def resetnetwork(self, arg_dict):
xenstore.write_record(self, arg_dict)
@jsonify
def inject_file(self, arg_dict):
"""Expects a file path and the contents of the file to be written. Both
should be base64-encoded in order to eliminate errors as they are passed
through the stack. Writes that information to xenstore for the agent,
which will decode the file and intended path, and create it on the
instance. The original agent munged both of these into a single entry;
the new agent keeps them separate. We will need to test for the new agent,
and write the xenstore records to match the agent version. We will also
need to test to determine if the file injection method on the agent has
been disabled, and raise a NotImplemented error if that is the case.
"""
b64_path = arg_dict["b64_path"]
b64_file = arg_dict["b64_file"]
request_id = arg_dict["id"]
if self._agent_has_method("file_inject"):
# New version of the agent. Agent should receive a 'value'
# key whose value is a dictionary containing 'b64_path' and
# 'b64_file'. See old version below.
arg_dict["value"] = json.dumps({"name": "file_inject",
"value": {"b64_path": b64_path, "b64_file": b64_file}})
elif self._agent_has_method("injectfile"):
# Old agent requires file path and file contents to be
# combined into one base64 value.
raw_path = base64.b64decode(b64_path)
raw_file = base64.b64decode(b64_file)
new_b64 = base64.b64encode("%s,%s") % (raw_path, raw_file)
arg_dict["value"] = json.dumps({"name": "injectfile",
"value": new_b64})
else:
# Either the methods don't exist in the agent, or they
# have been disabled.
raise NotImplementedError(_("NOT IMPLEMENTED: Agent does not"
" support file injection."))
arg_dict["path"] = "data/host/%s" % request_id
xenstore.write_record(self, arg_dict)
try:
resp = _wait_for_agent(self, request_id, arg_dict)
except TimeoutError, e:
raise PluginError(e)
return resp
def _agent_has_method(self, method):
"""Check that the agent has a particular method by checking its
features. Cache the features so we don't have to query the agent
every time we need to check.
"""
try:
self._agent_methods
except AttributeError:
self._agent_methods = []
if not self._agent_methods:
# Haven't been defined
tmp_id = commands.getoutput("uuidgen")
dct = {}
dct["value"] = json.dumps({"name": "features", "value": ""})
dct["path"] = "data/host/%s" % tmp_id
xenstore.write_record(self, dct)
try:
resp = _wait_for_agent(self, tmp_id, dct)
except TimeoutError, e:
raise PluginError(e)
response = json.loads(resp)
# The agent returns a comma-separated list of methods.
self._agent_methods = response.split(",")
return method in self._agent_methods
def _wait_for_agent(self, request_id, arg_dict):
"""Periodically checks xenstore for a response from the agent.
The request is always written to 'data/host/{id}', and
@ -119,9 +190,8 @@ def _wait_for_agent(self, request_id, arg_dict):
# First, delete the request record
arg_dict["path"] = "data/host/%s" % request_id
xenstore.delete_record(self, arg_dict)
raise TimeoutError(
"TIMEOUT: No response from agent within %s seconds." %
AGENT_TIMEOUT)
raise TimeoutError(_("TIMEOUT: No response from agent within"
" %s seconds.") % AGENT_TIMEOUT)
ret = xenstore.read_record(self, arg_dict)
# Note: the response for None with be a string that includes
# double quotes.
@ -136,4 +206,5 @@ if __name__ == "__main__":
XenAPIPlugin.dispatch(
{"key_init": key_init,
"password": password,
"resetnetwork": resetnetwork})
"resetnetwork": resetnetwork,
"inject_file": inject_file})

View File

@ -16,6 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import glob
import os
import subprocess
import sys
@ -86,6 +87,19 @@ try:
except:
pass
def find_data_files(destdir, srcdir):
package_data = []
files = []
for d in glob.glob('%s/*' % (srcdir, )):
if os.path.isdir(d):
package_data += find_data_files(
os.path.join(destdir, os.path.basename(d)), d)
else:
files += [d]
package_data += [(destdir, files)]
return package_data
DistUtilsExtra.auto.setup(name='nova',
version=version.canonical_version_string(),
description='cloud computing fabric controller',
@ -96,6 +110,7 @@ DistUtilsExtra.auto.setup(name='nova',
packages=find_packages(exclude=['bin', 'smoketests']),
include_package_data=True,
test_suite='nose.collector',
data_files=find_data_files('share/nova', 'tools'),
scripts=['bin/nova-ajax-console-proxy',
'bin/nova-api',
'bin/nova-compute',

View File

@ -30,7 +30,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
from nova import adminclient
from smoketests import flags
from smoketests import base
@ -47,6 +46,7 @@ TEST_PROJECTNAME = '%sproject' % TEST_PREFIX
class AdminSmokeTestCase(base.SmokeTestCase):
def setUp(self):
import nova_adminclient as adminclient
self.admin = adminclient.NovaAdminClient(
access_key=os.getenv('EC2_ACCESS_KEY'),
secret_key=os.getenv('EC2_SECRET_KEY'),

View File

@ -35,6 +35,7 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
import boto
import nova
from boto.ec2.connection import EC2Connection
import euca2ools
from euca2ools import Euca2ool, InstanceValidationError, Util
usage_string = """
@ -93,8 +94,13 @@ def override_connect_ec2(aws_access_key_id=None,
aws_secret_access_key, **kwargs)
# override boto's connect_ec2 method, so that we can use NovaEC2Connection
# (This is for Euca2ools 1.2)
boto.connect_ec2 = override_connect_ec2
# Override Euca2ools' EC2Connection class (which it gets from boto)
# (This is for Euca2ools 1.3)
euca2ools.EC2Connection = NovaEC2Connection
def usage(status=1):
print usage_string

View File

@ -30,4 +30,5 @@ sqlalchemy-migrate
netaddr
sphinx
glance
nova-adminclient
suds==0.4