summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2011-04-05 14:44:39 -0700
committerVishvananda Ishaya <vishvananda@gmail.com>2011-04-05 14:44:39 -0700
commit7ab8478e312faabcbe2b2ef00c4b0a5cad2af11d (patch)
tree8434388eb28e8a9f3ec3ce2d580662f0f21e3978
parent1654afaabba498ab690c07ccc6de858783dc1742 (diff)
parent94ccd2f4a1c42a8574fe65972650428130ae850d (diff)
downloadnova-7ab8478e312faabcbe2b2ef00c4b0a5cad2af11d.tar.gz
nova-7ab8478e312faabcbe2b2ef00c4b0a5cad2af11d.tar.xz
nova-7ab8478e312faabcbe2b2ef00c4b0a5cad2af11d.zip
merged trunk
-rw-r--r--Authors1
-rw-r--r--MANIFEST.in2
-rw-r--r--nova/CA/.gitignore (renamed from CA/.gitignore)0
-rwxr-xr-xnova/CA/geninter.sh (renamed from CA/geninter.sh)2
-rwxr-xr-xnova/CA/genrootca.sh (renamed from CA/genrootca.sh)3
-rwxr-xr-xnova/CA/genvpn.sh (renamed from CA/genvpn.sh)0
-rw-r--r--nova/CA/newcerts/.placeholder (renamed from CA/newcerts/.placeholder)0
-rw-r--r--nova/CA/openssl.cnf.tmpl (renamed from CA/openssl.cnf.tmpl)0
-rw-r--r--nova/CA/private/.placeholder (renamed from CA/private/.placeholder)0
-rw-r--r--nova/CA/projects/.gitignore (renamed from CA/projects/.gitignore)0
-rw-r--r--nova/CA/projects/.placeholder (renamed from CA/projects/.placeholder)0
-rw-r--r--nova/CA/reqs/.gitignore (renamed from CA/reqs/.gitignore)0
-rw-r--r--nova/CA/reqs/.placeholder (renamed from CA/reqs/.placeholder)0
-rw-r--r--nova/adminclient.py473
-rw-r--r--nova/api/ec2/cloud.py26
-rw-r--r--nova/api/openstack/servers.py28
-rw-r--r--nova/api/openstack/views/images.py10
-rw-r--r--nova/api/openstack/views/servers.py22
-rw-r--r--nova/compute/api.py6
-rw-r--r--nova/crypto.py10
-rw-r--r--nova/db/sqlalchemy/api.py4
-rw-r--r--nova/network/api.py15
-rw-r--r--nova/rpc.py11
-rw-r--r--nova/scheduler/chance.py4
-rw-r--r--nova/scheduler/simple.py12
-rw-r--r--nova/scheduler/zone.py5
-rw-r--r--nova/tests/api/openstack/test_images.py57
-rw-r--r--nova/tests/api/openstack/test_servers.py78
-rw-r--r--nova/tests/test_cloud.py33
-rw-r--r--nova/virt/hyperv.py4
-rw-r--r--nova/virt/libvirt_conn.py5
-rw-r--r--nova/virt/vmwareapi/vim.py50
-rw-r--r--nova/virt/vmwareapi_conn.py3
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/agent83
-rw-r--r--smoketests/test_admin.py2
-rwxr-xr-xtools/euca-get-ajax-console6
-rw-r--r--tools/pip-requires1
37 files changed, 397 insertions, 559 deletions
diff --git a/Authors b/Authors
index eccf38a43..48b912184 100644
--- a/Authors
+++ b/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>
diff --git a/MANIFEST.in b/MANIFEST.in
index bf30d1546..e7a6e7da4 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -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
diff --git a/CA/.gitignore b/nova/CA/.gitignore
index fae0922bf..fae0922bf 100644
--- a/CA/.gitignore
+++ b/nova/CA/.gitignore
diff --git a/CA/geninter.sh b/nova/CA/geninter.sh
index 1fbcc9e73..4b7f5a55c 100755
--- a/CA/geninter.sh
+++ b/nova/CA/geninter.sh
@@ -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
diff --git a/CA/genrootca.sh b/nova/CA/genrootca.sh
index 8f2c3ee3f..091cf17fc 100755
--- a/CA/genrootca.sh
+++ b/nova/CA/genrootca.sh
@@ -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
diff --git a/CA/genvpn.sh b/nova/CA/genvpn.sh
index 7e7db185d..7e7db185d 100755
--- a/CA/genvpn.sh
+++ b/nova/CA/genvpn.sh
diff --git a/CA/newcerts/.placeholder b/nova/CA/newcerts/.placeholder
index e69de29bb..e69de29bb 100644
--- a/CA/newcerts/.placeholder
+++ b/nova/CA/newcerts/.placeholder
diff --git a/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl
index dd81f1c2b..dd81f1c2b 100644
--- a/CA/openssl.cnf.tmpl
+++ b/nova/CA/openssl.cnf.tmpl
diff --git a/CA/private/.placeholder b/nova/CA/private/.placeholder
index e69de29bb..e69de29bb 100644
--- a/CA/private/.placeholder
+++ b/nova/CA/private/.placeholder
diff --git a/CA/projects/.gitignore b/nova/CA/projects/.gitignore
index 72e8ffc0d..72e8ffc0d 100644
--- a/CA/projects/.gitignore
+++ b/nova/CA/projects/.gitignore
diff --git a/CA/projects/.placeholder b/nova/CA/projects/.placeholder
index e69de29bb..e69de29bb 100644
--- a/CA/projects/.placeholder
+++ b/nova/CA/projects/.placeholder
diff --git a/CA/reqs/.gitignore b/nova/CA/reqs/.gitignore
index 72e8ffc0d..72e8ffc0d 100644
--- a/CA/reqs/.gitignore
+++ b/nova/CA/reqs/.gitignore
diff --git a/CA/reqs/.placeholder b/nova/CA/reqs/.placeholder
index e69de29bb..e69de29bb 100644
--- a/CA/reqs/.placeholder
+++ b/nova/CA/reqs/.placeholder
diff --git a/nova/adminclient.py b/nova/adminclient.py
deleted file mode 100644
index f570e12c2..000000000
--- a/nova/adminclient.py
+++ /dev/null
@@ -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)])
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 3541e49ca..02a30220a 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -103,10 +103,17 @@ 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()
+ 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 +764,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']
@@ -898,10 +907,7 @@ class CloudController(object):
image_type = self._image_type(image.get('container_format'))
ec2_id = self._image_ec2_id(image.get('id'), image_type)
name = image.get('name')
- if name:
- i['imageId'] = "%s (%s)" % (ec2_id, name)
- else:
- i['imageId'] = ec2_id
+ i['imageId'] = ec2_id
kernel_id = image['properties'].get('kernel_id')
if kernel_id:
i['kernelId'] = self._image_ec2_id(kernel_id, 'aki')
@@ -909,14 +915,18 @@ class CloudController(object):
if ramdisk_id:
i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari')
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')
display_mapping = {'aki': 'kernel',
'ari': 'ramdisk',
'ami': 'machine'}
- i['type'] = display_mapping.get(image_type)
+ i['imageType'] = display_mapping.get(image_type)
i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True'
i['architecture'] = image['properties'].get('architecture')
return i
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index d640e8684..681919a33 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -180,8 +180,7 @@ class Controller(wsgi.Controller):
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(wsgi.Controller):
resize a server"""
actions = {
- 'reboot': self._action_reboot,
- 'resize': self._action_resize,
+ 'changePassword': self._action_change_password,
+ 'reboot': self._action_reboot,
+ 'resize': self._action_resize,
'confirmResize': self._action_confirm_resize,
- 'revertResize': self._action_revert_resize,
- 'rebuild': self._action_rebuild,
+ 'revertResize': self._action_revert_resize,
+ 'rebuild': self._action_rebuild,
}
input_dict = self._deserialize(req.body, req.get_content_type())
@@ -301,6 +301,9 @@ class Controller(wsgi.Controller):
return actions[key](input_dict, req, id)
return faults.Fault(exc.HTTPNotImplemented())
+ def _action_change_password(self, input_dict, req, id):
+ return exc.HTTPNotImplemented()
+
def _action_confirm_resize(self, input_dict, req, id):
try:
self.compute_api.confirm_resize(req.environ['nova.context'], id)
@@ -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)
diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py
index 3807fa95f..16195b050 100644
--- a/nova/api/openstack/views/images.py
+++ b/nova/api/openstack/views/images.py
@@ -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":
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index 4e7f62eb3..d24c025be 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -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 = {}
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 1dbd73f8f..996955fe3 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -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)
diff --git a/nova/crypto.py b/nova/crypto.py
index b112e5b92..2b122e560 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -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()
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index b2a13a01b..6da8dac10 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -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
diff --git a/nova/network/api.py b/nova/network/api.py
index 4ee1148cb..c56e3062b 100644
--- a/nova/network/api.py
+++ b/nova/network/api.py
@@ -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']
diff --git a/nova/rpc.py b/nova/rpc.py
index 388f78d69..b610cdf9b 100644
--- a/nova/rpc.py
+++ b/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):
diff --git a/nova/scheduler/chance.py b/nova/scheduler/chance.py
index 9deaa2777..f4461cee2 100644
--- a/nova/scheduler/chance.py
+++ b/nova/scheduler/chance.py
@@ -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))]
diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py
index 0191ceb3d..dd568d2c6 100644
--- a/nova/scheduler/simple.py
+++ b/nova/scheduler/simple.py
@@ -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?"))
diff --git a/nova/scheduler/zone.py b/nova/scheduler/zone.py
index 49786cd32..44d5a166f 100644
--- a/nova/scheduler/zone.py
+++ b/nova/scheduler/zone.py
@@ -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))]
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 57e447dce..69cc3116d 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -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)
@@ -339,6 +340,24 @@ 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" />
+ """ % (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"
@@ -516,6 +535,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)
@@ -635,7 +661,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)
@@ -694,4 +742,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
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 130b8c5d5..313676e72 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -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):
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index 00803d0ad..5cb969979 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -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
diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py
index a1ed5ebbf..13f403a66 100644
--- a/nova/virt/hyperv.py
+++ b/nova/virt/hyperv.py
@@ -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
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index adcb2ffa3..4df26ecb5 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -841,7 +841,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
@@ -969,7 +969,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"
diff --git a/nova/virt/vmwareapi/vim.py b/nova/virt/vmwareapi/vim.py
index ba14f1512..159e16a80 100644
--- a/nova/virt/vmwareapi/vim.py
+++ b/nova/virt/vmwareapi/vim.py
@@ -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
diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py
index 87c3fa299..20c1b2b45 100644
--- a/nova/virt/vmwareapi_conn.py
+++ b/nova/virt/vmwareapi_conn.py
@@ -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):
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
index 94eaabe73..5496a6bd5 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
@@ -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})
diff --git a/smoketests/test_admin.py b/smoketests/test_admin.py
index 46e5b2233..1b7a8d673 100644
--- a/smoketests/test_admin.py
+++ b/smoketests/test_admin.py
@@ -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'),
diff --git a/tools/euca-get-ajax-console b/tools/euca-get-ajax-console
index 3df3dcb53..a67c79d90 100755
--- a/tools/euca-get-ajax-console
+++ b/tools/euca-get-ajax-console
@@ -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
diff --git a/tools/pip-requires b/tools/pip-requires
index 4ab9644d8..6ea446493 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -30,4 +30,5 @@ sqlalchemy-migrate
netaddr
sphinx
glance
+nova-adminclient
suds==0.4