Merged with trunk, fixed up test that wasn't checking namespace
This commit is contained in:
commit
5530504c14
1
Authors
1
Authors
@ -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>
|
||||
|
@ -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
|
||||
|
0
CA/.gitignore → nova/CA/.gitignore
vendored
0
CA/.gitignore → nova/CA/.gitignore
vendored
@ -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
|
@ -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
|
@ -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)])
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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":
|
||||
|
@ -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 = {}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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']
|
||||
|
11
nova/rpc.py
11
nova/rpc.py
@ -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):
|
||||
|
@ -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))]
|
||||
|
@ -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?"))
|
||||
|
@ -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))]
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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]
|
||||
|
@ -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})
|
||||
|
15
setup.py
15
setup.py
@ -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',
|
||||
|
@ -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'),
|
||||
|
@ -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
|
||||
|
@ -30,4 +30,5 @@ sqlalchemy-migrate
|
||||
netaddr
|
||||
sphinx
|
||||
glance
|
||||
nova-adminclient
|
||||
suds==0.4
|
||||
|
Loading…
Reference in New Issue
Block a user