Merging trunk
This commit is contained in:
6
Authors
6
Authors
@@ -4,8 +4,8 @@ Anthony Young <sleepsonthefloor@gmail.com>
|
|||||||
Antony Messerli <ant@openstack.org>
|
Antony Messerli <ant@openstack.org>
|
||||||
Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
|
Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
|
||||||
Chiradeep Vittal <chiradeep@cloud.com>
|
Chiradeep Vittal <chiradeep@cloud.com>
|
||||||
Chris Behrens <cbehrens@codestud.com>
|
|
||||||
Chmouel Boudjnah <chmouel@chmouel.com>
|
Chmouel Boudjnah <chmouel@chmouel.com>
|
||||||
|
Chris Behrens <cbehrens@codestud.com>
|
||||||
Cory Wright <corywright@gmail.com>
|
Cory Wright <corywright@gmail.com>
|
||||||
David Pravec <David.Pravec@danix.org>
|
David Pravec <David.Pravec@danix.org>
|
||||||
Dean Troyer <dtroyer@gmail.com>
|
Dean Troyer <dtroyer@gmail.com>
|
||||||
@@ -14,6 +14,7 @@ Ed Leafe <ed@leafe.com>
|
|||||||
Eldar Nugaev <enugaev@griddynamics.com>
|
Eldar Nugaev <enugaev@griddynamics.com>
|
||||||
Eric Day <eday@oddments.org>
|
Eric Day <eday@oddments.org>
|
||||||
Ewan Mellor <ewan.mellor@citrix.com>
|
Ewan Mellor <ewan.mellor@citrix.com>
|
||||||
|
Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>
|
||||||
Hisaki Ohara <hisaki.ohara@intel.com>
|
Hisaki Ohara <hisaki.ohara@intel.com>
|
||||||
Ilya Alekseyev <ialekseev@griddynamics.com>
|
Ilya Alekseyev <ialekseev@griddynamics.com>
|
||||||
Jay Pipes <jaypipes@gmail.com>
|
Jay Pipes <jaypipes@gmail.com>
|
||||||
@@ -26,11 +27,14 @@ Josh Kearney <josh.kearney@rackspace.com>
|
|||||||
Joshua McKenty <jmckenty@gmail.com>
|
Joshua McKenty <jmckenty@gmail.com>
|
||||||
Justin Santa Barbara <justin@fathomdb.com>
|
Justin Santa Barbara <justin@fathomdb.com>
|
||||||
Ken Pepple <ken.pepple@gmail.com>
|
Ken Pepple <ken.pepple@gmail.com>
|
||||||
|
Koji Iida <iida.koji@lab.ntt.co.jp>
|
||||||
Lorin Hochstein <lorin@isi.edu>
|
Lorin Hochstein <lorin@isi.edu>
|
||||||
Matt Dietz <matt.dietz@rackspace.com>
|
Matt Dietz <matt.dietz@rackspace.com>
|
||||||
Michael Gundlach <michael.gundlach@rackspace.com>
|
Michael Gundlach <michael.gundlach@rackspace.com>
|
||||||
Monsyne Dragon <mdragon@rackspace.com>
|
Monsyne Dragon <mdragon@rackspace.com>
|
||||||
Monty Taylor <mordred@inaugust.com>
|
Monty Taylor <mordred@inaugust.com>
|
||||||
|
MORITA Kazutaka <morita.kazutaka@gmail.com>
|
||||||
|
Nachi Ueno <ueno.nachi@lab.ntt.co.jp> <openstack@lab.ntt.co.jp> <nati.ueno@gmail.com> <nova@u4>
|
||||||
Paul Voccio <paul@openstack.org>
|
Paul Voccio <paul@openstack.org>
|
||||||
Rick Clark <rick@openstack.org>
|
Rick Clark <rick@openstack.org>
|
||||||
Rick Harris <rconradharris@gmail.com>
|
Rick Harris <rconradharris@gmail.com>
|
||||||
|
|||||||
61
bin/nova-direct-api
Executable file
61
bin/nova-direct-api
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# pylint: disable-msg=C0103
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Starter script for Nova Direct API."""
|
||||||
|
|
||||||
|
import gettext
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# If ../nova/__init__.py exists, add ../ to Python search path, so that
|
||||||
|
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||||
|
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||||
|
os.pardir,
|
||||||
|
os.pardir))
|
||||||
|
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
|
||||||
|
sys.path.insert(0, possible_topdir)
|
||||||
|
|
||||||
|
gettext.install('nova', unicode=1)
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import utils
|
||||||
|
from nova import wsgi
|
||||||
|
from nova.api import direct
|
||||||
|
from nova.compute import api as compute_api
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
flags.DEFINE_integer('direct_port', 8001, 'Direct API port')
|
||||||
|
flags.DEFINE_string('direct_host', '0.0.0.0', 'Direct API host')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
utils.default_flagfile()
|
||||||
|
FLAGS(sys.argv)
|
||||||
|
|
||||||
|
direct.register_service('compute', compute_api.ComputeAPI())
|
||||||
|
direct.register_service('reflect', direct.Reflection())
|
||||||
|
router = direct.Router()
|
||||||
|
with_json = direct.JsonParamsMiddleware(router)
|
||||||
|
with_req = direct.PostParamsMiddleware(with_json)
|
||||||
|
with_auth = direct.DelegatedAuthMiddleware(with_req)
|
||||||
|
|
||||||
|
server = wsgi.Server()
|
||||||
|
server.start(with_auth, FLAGS.direct_port, host=FLAGS.direct_host)
|
||||||
|
server.wait()
|
||||||
@@ -91,6 +91,7 @@ flags.DECLARE('num_networks', 'nova.network.manager')
|
|||||||
flags.DECLARE('network_size', 'nova.network.manager')
|
flags.DECLARE('network_size', 'nova.network.manager')
|
||||||
flags.DECLARE('vlan_start', 'nova.network.manager')
|
flags.DECLARE('vlan_start', 'nova.network.manager')
|
||||||
flags.DECLARE('vpn_start', 'nova.network.manager')
|
flags.DECLARE('vpn_start', 'nova.network.manager')
|
||||||
|
flags.DECLARE('fixed_range_v6', 'nova.network.manager')
|
||||||
|
|
||||||
|
|
||||||
class VpnCommands(object):
|
class VpnCommands(object):
|
||||||
@@ -439,11 +440,12 @@ class NetworkCommands(object):
|
|||||||
"""Class for managing networks."""
|
"""Class for managing networks."""
|
||||||
|
|
||||||
def create(self, fixed_range=None, num_networks=None,
|
def create(self, fixed_range=None, num_networks=None,
|
||||||
network_size=None, vlan_start=None, vpn_start=None):
|
network_size=None, vlan_start=None, vpn_start=None,
|
||||||
|
fixed_range_v6=None):
|
||||||
"""Creates fixed ips for host by range
|
"""Creates fixed ips for host by range
|
||||||
arguments: [fixed_range=FLAG], [num_networks=FLAG],
|
arguments: [fixed_range=FLAG], [num_networks=FLAG],
|
||||||
[network_size=FLAG], [vlan_start=FLAG],
|
[network_size=FLAG], [vlan_start=FLAG],
|
||||||
[vpn_start=FLAG]"""
|
[vpn_start=FLAG], [fixed_range_v6=FLAG]"""
|
||||||
if not fixed_range:
|
if not fixed_range:
|
||||||
fixed_range = FLAGS.fixed_range
|
fixed_range = FLAGS.fixed_range
|
||||||
if not num_networks:
|
if not num_networks:
|
||||||
@@ -454,11 +456,13 @@ class NetworkCommands(object):
|
|||||||
vlan_start = FLAGS.vlan_start
|
vlan_start = FLAGS.vlan_start
|
||||||
if not vpn_start:
|
if not vpn_start:
|
||||||
vpn_start = FLAGS.vpn_start
|
vpn_start = FLAGS.vpn_start
|
||||||
|
if not fixed_range_v6:
|
||||||
|
fixed_range_v6 = FLAGS.fixed_range_v6
|
||||||
net_manager = utils.import_object(FLAGS.network_manager)
|
net_manager = utils.import_object(FLAGS.network_manager)
|
||||||
net_manager.create_networks(context.get_admin_context(),
|
net_manager.create_networks(context.get_admin_context(),
|
||||||
fixed_range, int(num_networks),
|
fixed_range, int(num_networks),
|
||||||
int(network_size), int(vlan_start),
|
int(network_size), int(vlan_start),
|
||||||
int(vpn_start))
|
int(vpn_start), fixed_range_v6)
|
||||||
|
|
||||||
|
|
||||||
class ServiceCommands(object):
|
class ServiceCommands(object):
|
||||||
|
|||||||
145
bin/stack
Executable file
145
bin/stack
Executable file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""CLI for the Direct API."""
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
eventlet.monkey_patch()
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pprint
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
# If ../nova/__init__.py exists, add ../ to Python search path, so that
|
||||||
|
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||||
|
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||||
|
os.pardir,
|
||||||
|
os.pardir))
|
||||||
|
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
|
||||||
|
sys.path.insert(0, possible_topdir)
|
||||||
|
|
||||||
|
import gflags
|
||||||
|
from nova import utils
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = gflags.FLAGS
|
||||||
|
gflags.DEFINE_string('host', '127.0.0.1', 'Direct API host')
|
||||||
|
gflags.DEFINE_integer('port', 8001, 'Direct API host')
|
||||||
|
gflags.DEFINE_string('user', 'user1', 'Direct API username')
|
||||||
|
gflags.DEFINE_string('project', 'proj1', 'Direct API project')
|
||||||
|
|
||||||
|
|
||||||
|
USAGE = """usage: stack [options] <controller> <method> [arg1=value arg2=value]
|
||||||
|
|
||||||
|
`stack help` should output the list of available controllers
|
||||||
|
`stack <controller>` should output the available methods for that controller
|
||||||
|
`stack help <controller>` should do the same
|
||||||
|
`stack help <controller> <method>` should output info for a method
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def format_help(d):
|
||||||
|
"""Format help text, keys are labels and values are descriptions."""
|
||||||
|
indent = max([len(k) for k in d])
|
||||||
|
out = []
|
||||||
|
for k, v in d.iteritems():
|
||||||
|
t = textwrap.TextWrapper(initial_indent=' %s ' % k.ljust(indent),
|
||||||
|
subsequent_indent=' ' * (indent + 6))
|
||||||
|
out.extend(t.wrap(v))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def help_all():
|
||||||
|
rv = do_request('reflect', 'get_controllers')
|
||||||
|
out = format_help(rv)
|
||||||
|
return (USAGE + str(FLAGS.MainModuleHelp()) +
|
||||||
|
'\n\nAvailable controllers:\n' +
|
||||||
|
'\n'.join(out) + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
def help_controller(controller):
|
||||||
|
rv = do_request('reflect', 'get_methods')
|
||||||
|
methods = dict([(k.split('/')[2], v) for k, v in rv.iteritems()
|
||||||
|
if k.startswith('/%s' % controller)])
|
||||||
|
return ('Available methods for %s:\n' % controller +
|
||||||
|
'\n'.join(format_help(methods)))
|
||||||
|
|
||||||
|
|
||||||
|
def help_method(controller, method):
|
||||||
|
rv = do_request('reflect',
|
||||||
|
'get_method_info',
|
||||||
|
{'method': '/%s/%s' % (controller, method)})
|
||||||
|
|
||||||
|
sig = '%s(%s):' % (method, ', '.join(['='.join(x) for x in rv['args']]))
|
||||||
|
out = textwrap.wrap(sig, subsequent_indent=' ' * len('%s(' % method))
|
||||||
|
out.append('\n' + rv['doc'])
|
||||||
|
return '\n'.join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def do_request(controller, method, params=None):
|
||||||
|
if params:
|
||||||
|
data = urllib.urlencode(params)
|
||||||
|
else:
|
||||||
|
data = None
|
||||||
|
|
||||||
|
url = 'http://%s:%s/%s/%s' % (FLAGS.host, FLAGS.port, controller, method)
|
||||||
|
headers = {'X-OpenStack-User': FLAGS.user,
|
||||||
|
'X-OpenStack-Project': FLAGS.project}
|
||||||
|
|
||||||
|
req = urllib2.Request(url, data, headers)
|
||||||
|
resp = urllib2.urlopen(req)
|
||||||
|
return utils.loads(resp.read())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
args = FLAGS(sys.argv)
|
||||||
|
|
||||||
|
cmd = args.pop(0)
|
||||||
|
if not args:
|
||||||
|
print help_all()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
first = args.pop(0)
|
||||||
|
if first == 'help':
|
||||||
|
action = help_all
|
||||||
|
params = []
|
||||||
|
if args:
|
||||||
|
params.append(args.pop(0))
|
||||||
|
action = help_controller
|
||||||
|
if args:
|
||||||
|
params.append(args.pop(0))
|
||||||
|
action = help_method
|
||||||
|
print action(*params)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
controller = first
|
||||||
|
if not args:
|
||||||
|
print help_controller(controller)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
method = args.pop(0)
|
||||||
|
params = {}
|
||||||
|
for x in args:
|
||||||
|
key, value = x.split('=', 1)
|
||||||
|
params[key] = value
|
||||||
|
|
||||||
|
pprint.pprint(do_request(controller, method, params))
|
||||||
37
contrib/boto_v6/__init__.py
Normal file
37
contrib/boto_v6/__init__.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||||||
|
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
def connect_ec2(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||||
|
"""
|
||||||
|
:type aws_access_key_id: string
|
||||||
|
:param aws_access_key_id: Your AWS Access Key ID
|
||||||
|
|
||||||
|
:type aws_secret_access_key: string
|
||||||
|
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||||
|
|
||||||
|
:rtype: :class:`boto.ec2.connection.EC2Connection`
|
||||||
|
:return: A connection to Amazon's EC2
|
||||||
|
"""
|
||||||
|
from boto_v6.ec2.connection import EC2ConnectionV6
|
||||||
|
return EC2ConnectionV6(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||||
0
contrib/boto_v6/ec2/__init__.py
Normal file
0
contrib/boto_v6/ec2/__init__.py
Normal file
41
contrib/boto_v6/ec2/connection.py
Normal file
41
contrib/boto_v6/ec2/connection.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
'''
|
||||||
|
Created on 2010/12/20
|
||||||
|
|
||||||
|
@author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
|
||||||
|
'''
|
||||||
|
import boto
|
||||||
|
import boto.ec2
|
||||||
|
from boto_v6.ec2.instance import ReservationV6
|
||||||
|
|
||||||
|
|
||||||
|
class EC2ConnectionV6(boto.ec2.EC2Connection):
|
||||||
|
'''
|
||||||
|
EC2Connection for OpenStack IPV6 mode
|
||||||
|
'''
|
||||||
|
def get_all_instances(self, instance_ids=None, filters=None):
|
||||||
|
"""
|
||||||
|
Retrieve all the instances associated with your account.
|
||||||
|
|
||||||
|
:type instance_ids: list
|
||||||
|
:param instance_ids: A list of strings of instance IDs
|
||||||
|
|
||||||
|
:type filters: dict
|
||||||
|
:param filters: Optional filters that can be used to limit
|
||||||
|
the results returned. Filters are provided
|
||||||
|
in the form of a dictionary consisting of
|
||||||
|
filter names as the key and filter values
|
||||||
|
as the value. The set of allowable filter
|
||||||
|
names/values is dependent on the request
|
||||||
|
being performed. Check the EC2 API guide
|
||||||
|
for details.
|
||||||
|
|
||||||
|
:rtype: list
|
||||||
|
:return: A list of :class:`boto.ec2.instance.Reservation`
|
||||||
|
"""
|
||||||
|
params = {}
|
||||||
|
if instance_ids:
|
||||||
|
self.build_list_params(params, instance_ids, 'InstanceId')
|
||||||
|
if filters:
|
||||||
|
self.build_filter_params(params, filters)
|
||||||
|
return self.get_list('DescribeInstancesV6', params,
|
||||||
|
[('item', ReservationV6)])
|
||||||
37
contrib/boto_v6/ec2/instance.py
Normal file
37
contrib/boto_v6/ec2/instance.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
'''
|
||||||
|
Created on 2010/12/20
|
||||||
|
|
||||||
|
@author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
|
||||||
|
'''
|
||||||
|
import boto
|
||||||
|
from boto.resultset import ResultSet
|
||||||
|
from boto.ec2.instance import Reservation
|
||||||
|
from boto.ec2.instance import Group
|
||||||
|
from boto.ec2.instance import Instance
|
||||||
|
|
||||||
|
|
||||||
|
class ReservationV6(Reservation):
|
||||||
|
def startElement(self, name, attrs, connection):
|
||||||
|
if name == 'instancesSet':
|
||||||
|
self.instances = ResultSet([('item', InstanceV6)])
|
||||||
|
return self.instances
|
||||||
|
elif name == 'groupSet':
|
||||||
|
self.groups = ResultSet([('item', Group)])
|
||||||
|
return self.groups
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceV6(Instance):
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
Instance.__init__(self, connection)
|
||||||
|
self.dns_name_v6 = None
|
||||||
|
|
||||||
|
def endElement(self, name, value, connection):
|
||||||
|
Instance.endElement(self, name, value, connection)
|
||||||
|
if name == 'dnsNameV6':
|
||||||
|
self.dns_name_v6 = value
|
||||||
|
|
||||||
|
def _update(self, updated):
|
||||||
|
self.__dict__.update(updated.__dict__)
|
||||||
|
self.dns_name_v6 = updated.dns_name_v6
|
||||||
@@ -83,9 +83,17 @@ if [ "$CMD" == "install" ]; then
|
|||||||
sudo /etc/init.d/iscsitarget restart
|
sudo /etc/init.d/iscsitarget restart
|
||||||
sudo modprobe kvm
|
sudo modprobe kvm
|
||||||
sudo /etc/init.d/libvirt-bin restart
|
sudo /etc/init.d/libvirt-bin restart
|
||||||
|
sudo modprobe nbd
|
||||||
sudo apt-get install -y python-twisted python-sqlalchemy python-mox python-greenlet python-carrot
|
sudo apt-get install -y python-twisted python-sqlalchemy python-mox python-greenlet python-carrot
|
||||||
sudo apt-get install -y python-daemon python-eventlet python-gflags python-tornado python-ipy
|
sudo apt-get install -y python-daemon python-eventlet python-gflags python-ipy
|
||||||
sudo apt-get install -y python-libvirt python-libxml2 python-routes
|
sudo apt-get install -y python-libvirt python-libxml2 python-routes python-cheetah
|
||||||
|
#For IPV6
|
||||||
|
sudo apt-get install -y python-netaddr
|
||||||
|
sudo apt-get install -y radvd
|
||||||
|
#(Nati) Note that this configuration is only needed for nova-network node.
|
||||||
|
sudo bash -c "echo 1 > /proc/sys/net/ipv6/conf/all/forwarding"
|
||||||
|
sudo bash -c "echo 0 > /proc/sys/net/ipv6/conf/all/accept_ra"
|
||||||
|
|
||||||
if [ "$USE_MYSQL" == 1 ]; then
|
if [ "$USE_MYSQL" == 1 ]; then
|
||||||
cat <<MYSQL_PRESEED | debconf-set-selections
|
cat <<MYSQL_PRESEED | debconf-set-selections
|
||||||
mysql-server-5.1 mysql-server/root_password password $MYSQL_PASS
|
mysql-server-5.1 mysql-server/root_password password $MYSQL_PASS
|
||||||
@@ -107,6 +115,8 @@ function screen_it {
|
|||||||
|
|
||||||
if [ "$CMD" == "run" ]; then
|
if [ "$CMD" == "run" ]; then
|
||||||
killall dnsmasq
|
killall dnsmasq
|
||||||
|
#For IPv6
|
||||||
|
killall radvd
|
||||||
screen -d -m -S nova -t nova
|
screen -d -m -S nova -t nova
|
||||||
sleep 1
|
sleep 1
|
||||||
if [ "$USE_MYSQL" == 1 ]; then
|
if [ "$USE_MYSQL" == 1 ]; then
|
||||||
|
|||||||
232
nova/api/direct.py
Normal file
232
nova/api/direct.py
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Public HTTP interface that allows services to self-register.
|
||||||
|
|
||||||
|
The general flow of a request is:
|
||||||
|
- Request is parsed into WSGI bits.
|
||||||
|
- Some middleware checks authentication.
|
||||||
|
- Routing takes place based on the URL to find a controller.
|
||||||
|
(/controller/method)
|
||||||
|
- Parameters are parsed from the request and passed to a method on the
|
||||||
|
controller as keyword arguments.
|
||||||
|
- Optionally 'json' is decoded to provide all the parameters.
|
||||||
|
- Actual work is done and a result is returned.
|
||||||
|
- That result is turned into json and returned.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
import routes
|
||||||
|
import webob
|
||||||
|
|
||||||
|
from nova import context
|
||||||
|
from nova import flags
|
||||||
|
from nova import utils
|
||||||
|
from nova import wsgi
|
||||||
|
|
||||||
|
|
||||||
|
ROUTES = {}
|
||||||
|
|
||||||
|
|
||||||
|
def register_service(path, handle):
|
||||||
|
ROUTES[path] = handle
|
||||||
|
|
||||||
|
|
||||||
|
class Router(wsgi.Router):
|
||||||
|
def __init__(self, mapper=None):
|
||||||
|
if mapper is None:
|
||||||
|
mapper = routes.Mapper()
|
||||||
|
|
||||||
|
self._load_registered_routes(mapper)
|
||||||
|
super(Router, self).__init__(mapper=mapper)
|
||||||
|
|
||||||
|
def _load_registered_routes(self, mapper):
|
||||||
|
for route in ROUTES:
|
||||||
|
mapper.connect('/%s/{action}' % route,
|
||||||
|
controller=ServiceWrapper(ROUTES[route]))
|
||||||
|
|
||||||
|
|
||||||
|
class DelegatedAuthMiddleware(wsgi.Middleware):
|
||||||
|
def process_request(self, request):
|
||||||
|
os_user = request.headers['X-OpenStack-User']
|
||||||
|
os_project = request.headers['X-OpenStack-Project']
|
||||||
|
context_ref = context.RequestContext(user=os_user, project=os_project)
|
||||||
|
request.environ['openstack.context'] = context_ref
|
||||||
|
|
||||||
|
|
||||||
|
class JsonParamsMiddleware(wsgi.Middleware):
|
||||||
|
def process_request(self, request):
|
||||||
|
if 'json' not in request.params:
|
||||||
|
return
|
||||||
|
|
||||||
|
params_json = request.params['json']
|
||||||
|
params_parsed = utils.loads(params_json)
|
||||||
|
params = {}
|
||||||
|
for k, v in params_parsed.iteritems():
|
||||||
|
if k in ('self', 'context'):
|
||||||
|
continue
|
||||||
|
if k.startswith('_'):
|
||||||
|
continue
|
||||||
|
params[k] = v
|
||||||
|
|
||||||
|
request.environ['openstack.params'] = params
|
||||||
|
|
||||||
|
|
||||||
|
class PostParamsMiddleware(wsgi.Middleware):
|
||||||
|
def process_request(self, request):
|
||||||
|
params_parsed = request.params
|
||||||
|
params = {}
|
||||||
|
for k, v in params_parsed.iteritems():
|
||||||
|
if k in ('self', 'context'):
|
||||||
|
continue
|
||||||
|
if k.startswith('_'):
|
||||||
|
continue
|
||||||
|
params[k] = v
|
||||||
|
|
||||||
|
request.environ['openstack.params'] = params
|
||||||
|
|
||||||
|
|
||||||
|
class Reflection(object):
|
||||||
|
"""Reflection methods to list available methods."""
|
||||||
|
def __init__(self):
|
||||||
|
self._methods = {}
|
||||||
|
self._controllers = {}
|
||||||
|
|
||||||
|
def _gather_methods(self):
|
||||||
|
methods = {}
|
||||||
|
controllers = {}
|
||||||
|
for route, handler in ROUTES.iteritems():
|
||||||
|
controllers[route] = handler.__doc__.split('\n')[0]
|
||||||
|
for k in dir(handler):
|
||||||
|
if k.startswith('_'):
|
||||||
|
continue
|
||||||
|
f = getattr(handler, k)
|
||||||
|
if not callable(f):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# bunch of ugly formatting stuff
|
||||||
|
argspec = inspect.getargspec(f)
|
||||||
|
args = [x for x in argspec[0]
|
||||||
|
if x != 'self' and x != 'context']
|
||||||
|
defaults = argspec[3] and argspec[3] or []
|
||||||
|
args_r = list(reversed(args))
|
||||||
|
defaults_r = list(reversed(defaults))
|
||||||
|
|
||||||
|
args_out = []
|
||||||
|
while args_r:
|
||||||
|
if defaults_r:
|
||||||
|
args_out.append((args_r.pop(0),
|
||||||
|
repr(defaults_r.pop(0))))
|
||||||
|
else:
|
||||||
|
args_out.append((str(args_r.pop(0)),))
|
||||||
|
|
||||||
|
# if the method accepts keywords
|
||||||
|
if argspec[2]:
|
||||||
|
args_out.insert(0, ('**%s' % argspec[2],))
|
||||||
|
|
||||||
|
methods['/%s/%s' % (route, k)] = {
|
||||||
|
'short_doc': f.__doc__.split('\n')[0],
|
||||||
|
'doc': f.__doc__,
|
||||||
|
'name': k,
|
||||||
|
'args': list(reversed(args_out))}
|
||||||
|
|
||||||
|
self._methods = methods
|
||||||
|
self._controllers = controllers
|
||||||
|
|
||||||
|
def get_controllers(self, context):
|
||||||
|
"""List available controllers."""
|
||||||
|
if not self._controllers:
|
||||||
|
self._gather_methods()
|
||||||
|
|
||||||
|
return self._controllers
|
||||||
|
|
||||||
|
def get_methods(self, context):
|
||||||
|
"""List available methods."""
|
||||||
|
if not self._methods:
|
||||||
|
self._gather_methods()
|
||||||
|
|
||||||
|
method_list = self._methods.keys()
|
||||||
|
method_list.sort()
|
||||||
|
methods = {}
|
||||||
|
for k in method_list:
|
||||||
|
methods[k] = self._methods[k]['short_doc']
|
||||||
|
return methods
|
||||||
|
|
||||||
|
def get_method_info(self, context, method):
|
||||||
|
"""Get detailed information about a method."""
|
||||||
|
if not self._methods:
|
||||||
|
self._gather_methods()
|
||||||
|
return self._methods[method]
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceWrapper(wsgi.Controller):
|
||||||
|
def __init__(self, service_handle):
|
||||||
|
self.service_handle = service_handle
|
||||||
|
|
||||||
|
@webob.dec.wsgify
|
||||||
|
def __call__(self, req):
|
||||||
|
arg_dict = req.environ['wsgiorg.routing_args'][1]
|
||||||
|
action = arg_dict['action']
|
||||||
|
del arg_dict['action']
|
||||||
|
|
||||||
|
context = req.environ['openstack.context']
|
||||||
|
# allow middleware up the stack to override the params
|
||||||
|
params = {}
|
||||||
|
if 'openstack.params' in req.environ:
|
||||||
|
params = req.environ['openstack.params']
|
||||||
|
|
||||||
|
# TODO(termie): do some basic normalization on methods
|
||||||
|
method = getattr(self.service_handle, action)
|
||||||
|
|
||||||
|
result = method(context, **params)
|
||||||
|
if type(result) is dict or type(result) is list:
|
||||||
|
return self._serialize(result, req)
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class Proxy(object):
|
||||||
|
"""Pretend a Direct API endpoint is an object."""
|
||||||
|
def __init__(self, app, prefix=None):
|
||||||
|
self.app = app
|
||||||
|
self.prefix = prefix
|
||||||
|
|
||||||
|
def __do_request(self, path, context, **kwargs):
|
||||||
|
req = webob.Request.blank(path)
|
||||||
|
req.method = 'POST'
|
||||||
|
req.body = urllib.urlencode({'json': utils.dumps(kwargs)})
|
||||||
|
req.environ['openstack.context'] = context
|
||||||
|
resp = req.get_response(self.app)
|
||||||
|
try:
|
||||||
|
return utils.loads(resp.body)
|
||||||
|
except Exception:
|
||||||
|
return resp.body
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
if self.prefix is None:
|
||||||
|
return self.__class__(self.app, prefix=key)
|
||||||
|
|
||||||
|
def _wrapper(context, **kwargs):
|
||||||
|
return self.__do_request('/%s/%s' % (self.prefix, key),
|
||||||
|
context,
|
||||||
|
**kwargs)
|
||||||
|
_wrapper.func_name = key
|
||||||
|
return _wrapper
|
||||||
@@ -26,16 +26,17 @@ import base64
|
|||||||
import datetime
|
import datetime
|
||||||
import IPy
|
import IPy
|
||||||
import os
|
import os
|
||||||
|
import urllib
|
||||||
|
|
||||||
from nova import compute
|
from nova import compute
|
||||||
from nova import context
|
from nova import context
|
||||||
|
|
||||||
from nova import crypto
|
from nova import crypto
|
||||||
from nova import db
|
from nova import db
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
from nova import network
|
from nova import network
|
||||||
from nova import rpc
|
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova import volume
|
from nova import volume
|
||||||
from nova.compute import instance_types
|
from nova.compute import instance_types
|
||||||
@@ -91,8 +92,11 @@ class CloudController(object):
|
|||||||
self.image_service = utils.import_object(FLAGS.image_service)
|
self.image_service = utils.import_object(FLAGS.image_service)
|
||||||
self.network_api = network.API()
|
self.network_api = network.API()
|
||||||
self.volume_api = volume.API()
|
self.volume_api = volume.API()
|
||||||
self.compute_api = compute.API(self.image_service, self.network_api,
|
self.compute_api = compute.API(
|
||||||
self.volume_api)
|
network_api=self.network_api,
|
||||||
|
image_service=self.image_service,
|
||||||
|
volume_api=self.volume_api,
|
||||||
|
hostname_factory=id_to_ec2_id)
|
||||||
self.setup()
|
self.setup()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -128,15 +132,6 @@ class CloudController(object):
|
|||||||
result[key] = [line]
|
result[key] = [line]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _trigger_refresh_security_group(self, context, security_group):
|
|
||||||
nodes = set([instance['host'] for instance in security_group.instances
|
|
||||||
if instance['host'] is not None])
|
|
||||||
for node in nodes:
|
|
||||||
rpc.cast(context,
|
|
||||||
'%s.%s' % (FLAGS.compute_topic, node),
|
|
||||||
{"method": "refresh_security_group",
|
|
||||||
"args": {"security_group_id": security_group.id}})
|
|
||||||
|
|
||||||
def _get_availability_zone_by_host(self, context, host):
|
def _get_availability_zone_by_host(self, context, host):
|
||||||
services = db.service_get_all_by_host(context, host)
|
services = db.service_get_all_by_host(context, host)
|
||||||
if len(services) > 0:
|
if len(services) > 0:
|
||||||
@@ -374,6 +369,7 @@ class CloudController(object):
|
|||||||
values['group_id'] = source_security_group['id']
|
values['group_id'] = source_security_group['id']
|
||||||
elif cidr_ip:
|
elif cidr_ip:
|
||||||
# If this fails, it throws an exception. This is what we want.
|
# If this fails, it throws an exception. This is what we want.
|
||||||
|
cidr_ip = urllib.unquote(cidr_ip).decode()
|
||||||
IPy.IP(cidr_ip)
|
IPy.IP(cidr_ip)
|
||||||
values['cidr'] = cidr_ip
|
values['cidr'] = cidr_ip
|
||||||
else:
|
else:
|
||||||
@@ -519,13 +515,8 @@ class CloudController(object):
|
|||||||
# instance_id is passed in as a list of instances
|
# instance_id is passed in as a list of instances
|
||||||
ec2_id = instance_id[0]
|
ec2_id = instance_id[0]
|
||||||
instance_id = ec2_id_to_id(ec2_id)
|
instance_id = ec2_id_to_id(ec2_id)
|
||||||
instance_ref = self.compute_api.get(context, instance_id)
|
output = self.compute_api.get_console_output(
|
||||||
output = rpc.call(context,
|
context, instance_id=instance_id)
|
||||||
'%s.%s' % (FLAGS.compute_topic,
|
|
||||||
instance_ref['host']),
|
|
||||||
{"method": "get_console_output",
|
|
||||||
"args": {"instance_id": instance_ref['id']}})
|
|
||||||
|
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
return {"InstanceId": ec2_id,
|
return {"InstanceId": ec2_id,
|
||||||
"Timestamp": now,
|
"Timestamp": now,
|
||||||
@@ -593,7 +584,7 @@ class CloudController(object):
|
|||||||
|
|
||||||
def delete_volume(self, context, volume_id, **kwargs):
|
def delete_volume(self, context, volume_id, **kwargs):
|
||||||
volume_id = ec2_id_to_id(volume_id)
|
volume_id = ec2_id_to_id(volume_id)
|
||||||
self.volume_api.delete(context, volume_id)
|
self.volume_api.delete(context, volume_id=volume_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def update_volume(self, context, volume_id, **kwargs):
|
def update_volume(self, context, volume_id, **kwargs):
|
||||||
@@ -610,9 +601,12 @@ class CloudController(object):
|
|||||||
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
|
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
|
||||||
volume_id = ec2_id_to_id(volume_id)
|
volume_id = ec2_id_to_id(volume_id)
|
||||||
instance_id = ec2_id_to_id(instance_id)
|
instance_id = ec2_id_to_id(instance_id)
|
||||||
LOG.audit(_("Attach volume %s to instacne %s at %s"), volume_id,
|
LOG.audit(_("Attach volume %s to instance %s at %s"), volume_id,
|
||||||
instance_id, device, context=context)
|
instance_id, device, context=context)
|
||||||
self.compute_api.attach_volume(context, instance_id, volume_id, device)
|
self.compute_api.attach_volume(context,
|
||||||
|
instance_id=instance_id,
|
||||||
|
volume_id=volume_id,
|
||||||
|
device=device)
|
||||||
volume = self.volume_api.get(context, volume_id)
|
volume = self.volume_api.get(context, volume_id)
|
||||||
return {'attachTime': volume['attach_time'],
|
return {'attachTime': volume['attach_time'],
|
||||||
'device': volume['mountpoint'],
|
'device': volume['mountpoint'],
|
||||||
@@ -625,7 +619,7 @@ class CloudController(object):
|
|||||||
volume_id = ec2_id_to_id(volume_id)
|
volume_id = ec2_id_to_id(volume_id)
|
||||||
LOG.audit(_("Detach volume %s"), volume_id, context=context)
|
LOG.audit(_("Detach volume %s"), volume_id, context=context)
|
||||||
volume = self.volume_api.get(context, volume_id)
|
volume = self.volume_api.get(context, volume_id)
|
||||||
instance = self.compute_api.detach_volume(context, volume_id)
|
instance = self.compute_api.detach_volume(context, volume_id=volume_id)
|
||||||
return {'attachTime': volume['attach_time'],
|
return {'attachTime': volume['attach_time'],
|
||||||
'device': volume['mountpoint'],
|
'device': volume['mountpoint'],
|
||||||
'instanceId': id_to_ec2_id(instance['id']),
|
'instanceId': id_to_ec2_id(instance['id']),
|
||||||
@@ -643,6 +637,10 @@ class CloudController(object):
|
|||||||
def describe_instances(self, context, **kwargs):
|
def describe_instances(self, context, **kwargs):
|
||||||
return self._format_describe_instances(context, **kwargs)
|
return self._format_describe_instances(context, **kwargs)
|
||||||
|
|
||||||
|
def describe_instances_v6(self, context, **kwargs):
|
||||||
|
kwargs['use_v6'] = True
|
||||||
|
return self._format_describe_instances(context, **kwargs)
|
||||||
|
|
||||||
def _format_describe_instances(self, context, **kwargs):
|
def _format_describe_instances(self, context, **kwargs):
|
||||||
return {'reservationSet': self._format_instances(context, **kwargs)}
|
return {'reservationSet': self._format_instances(context, **kwargs)}
|
||||||
|
|
||||||
@@ -652,6 +650,10 @@ class CloudController(object):
|
|||||||
return i[0]
|
return i[0]
|
||||||
|
|
||||||
def _format_instances(self, context, instance_id=None, **kwargs):
|
def _format_instances(self, context, instance_id=None, **kwargs):
|
||||||
|
# TODO(termie): this method is poorly named as its name does not imply
|
||||||
|
# that it will be making a variety of database calls
|
||||||
|
# rather than simply formatting a bunch of instances that
|
||||||
|
# were handed to it
|
||||||
reservations = {}
|
reservations = {}
|
||||||
# NOTE(vish): instance_id is an optional list of ids to filter by
|
# NOTE(vish): instance_id is an optional list of ids to filter by
|
||||||
if instance_id:
|
if instance_id:
|
||||||
@@ -678,10 +680,16 @@ class CloudController(object):
|
|||||||
if instance['fixed_ip']['floating_ips']:
|
if instance['fixed_ip']['floating_ips']:
|
||||||
fixed = instance['fixed_ip']
|
fixed = instance['fixed_ip']
|
||||||
floating_addr = fixed['floating_ips'][0]['address']
|
floating_addr = fixed['floating_ips'][0]['address']
|
||||||
|
if instance['fixed_ip']['network'] and 'use_v6' in kwargs:
|
||||||
|
i['dnsNameV6'] = utils.to_global_ipv6(
|
||||||
|
instance['fixed_ip']['network']['cidr_v6'],
|
||||||
|
instance['mac_address'])
|
||||||
|
|
||||||
i['privateDnsName'] = fixed_addr
|
i['privateDnsName'] = fixed_addr
|
||||||
i['publicDnsName'] = floating_addr
|
i['publicDnsName'] = floating_addr
|
||||||
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
|
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
|
||||||
i['keyName'] = instance['key_name']
|
i['keyName'] = instance['key_name']
|
||||||
|
|
||||||
if context.user.is_admin():
|
if context.user.is_admin():
|
||||||
i['keyName'] = '%s (%s, %s)' % (i['keyName'],
|
i['keyName'] = '%s (%s, %s)' % (i['keyName'],
|
||||||
instance['project_id'],
|
instance['project_id'],
|
||||||
@@ -746,7 +754,9 @@ class CloudController(object):
|
|||||||
LOG.audit(_("Associate address %s to instance %s"), public_ip,
|
LOG.audit(_("Associate address %s to instance %s"), public_ip,
|
||||||
instance_id, context=context)
|
instance_id, context=context)
|
||||||
instance_id = ec2_id_to_id(instance_id)
|
instance_id = ec2_id_to_id(instance_id)
|
||||||
self.compute_api.associate_floating_ip(context, instance_id, public_ip)
|
self.compute_api.associate_floating_ip(context,
|
||||||
|
instance_id=instance_id,
|
||||||
|
address=public_ip)
|
||||||
return {'associateResponse': ["Address associated."]}
|
return {'associateResponse': ["Address associated."]}
|
||||||
|
|
||||||
def disassociate_address(self, context, public_ip, **kwargs):
|
def disassociate_address(self, context, public_ip, **kwargs):
|
||||||
@@ -757,8 +767,9 @@ class CloudController(object):
|
|||||||
def run_instances(self, context, **kwargs):
|
def run_instances(self, context, **kwargs):
|
||||||
max_count = int(kwargs.get('max_count', 1))
|
max_count = int(kwargs.get('max_count', 1))
|
||||||
instances = self.compute_api.create(context,
|
instances = self.compute_api.create(context,
|
||||||
instance_types.get_by_type(kwargs.get('instance_type', None)),
|
instance_type=instance_types.get_by_type(
|
||||||
kwargs['image_id'],
|
kwargs.get('instance_type', None)),
|
||||||
|
image_id=kwargs['image_id'],
|
||||||
min_count=int(kwargs.get('min_count', max_count)),
|
min_count=int(kwargs.get('min_count', max_count)),
|
||||||
max_count=max_count,
|
max_count=max_count,
|
||||||
kernel_id=kwargs.get('kernel_id', None),
|
kernel_id=kwargs.get('kernel_id', None),
|
||||||
@@ -769,8 +780,7 @@ class CloudController(object):
|
|||||||
user_data=kwargs.get('user_data'),
|
user_data=kwargs.get('user_data'),
|
||||||
security_group=kwargs.get('security_group'),
|
security_group=kwargs.get('security_group'),
|
||||||
availability_zone=kwargs.get('placement', {}).get(
|
availability_zone=kwargs.get('placement', {}).get(
|
||||||
'AvailabilityZone'),
|
'AvailabilityZone'))
|
||||||
generate_hostname=id_to_ec2_id)
|
|
||||||
return self._format_run_instances(context,
|
return self._format_run_instances(context,
|
||||||
instances[0]['reservation_id'])
|
instances[0]['reservation_id'])
|
||||||
|
|
||||||
@@ -780,7 +790,7 @@ class CloudController(object):
|
|||||||
LOG.debug(_("Going to start terminating instances"))
|
LOG.debug(_("Going to start terminating instances"))
|
||||||
for ec2_id in instance_id:
|
for ec2_id in instance_id:
|
||||||
instance_id = ec2_id_to_id(ec2_id)
|
instance_id = ec2_id_to_id(ec2_id)
|
||||||
self.compute_api.delete(context, instance_id)
|
self.compute_api.delete(context, instance_id=instance_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def reboot_instances(self, context, instance_id, **kwargs):
|
def reboot_instances(self, context, instance_id, **kwargs):
|
||||||
@@ -788,19 +798,19 @@ class CloudController(object):
|
|||||||
LOG.audit(_("Reboot instance %r"), instance_id, context=context)
|
LOG.audit(_("Reboot instance %r"), instance_id, context=context)
|
||||||
for ec2_id in instance_id:
|
for ec2_id in instance_id:
|
||||||
instance_id = ec2_id_to_id(ec2_id)
|
instance_id = ec2_id_to_id(ec2_id)
|
||||||
self.compute_api.reboot(context, instance_id)
|
self.compute_api.reboot(context, instance_id=instance_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def rescue_instance(self, context, instance_id, **kwargs):
|
def rescue_instance(self, context, instance_id, **kwargs):
|
||||||
"""This is an extension to the normal ec2_api"""
|
"""This is an extension to the normal ec2_api"""
|
||||||
instance_id = ec2_id_to_id(instance_id)
|
instance_id = ec2_id_to_id(instance_id)
|
||||||
self.compute_api.rescue(context, instance_id)
|
self.compute_api.rescue(context, instance_id=instance_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def unrescue_instance(self, context, instance_id, **kwargs):
|
def unrescue_instance(self, context, instance_id, **kwargs):
|
||||||
"""This is an extension to the normal ec2_api"""
|
"""This is an extension to the normal ec2_api"""
|
||||||
instance_id = ec2_id_to_id(instance_id)
|
instance_id = ec2_id_to_id(instance_id)
|
||||||
self.compute_api.unrescue(context, instance_id)
|
self.compute_api.unrescue(context, instance_id=instance_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def update_instance(self, context, ec2_id, **kwargs):
|
def update_instance(self, context, ec2_id, **kwargs):
|
||||||
@@ -811,7 +821,7 @@ class CloudController(object):
|
|||||||
changes[field] = kwargs[field]
|
changes[field] = kwargs[field]
|
||||||
if changes:
|
if changes:
|
||||||
instance_id = ec2_id_to_id(ec2_id)
|
instance_id = ec2_id_to_id(ec2_id)
|
||||||
self.compute_api.update(context, instance_id, **kwargs)
|
self.compute_api.update(context, instance_id=instance_id, **kwargs)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def describe_images(self, context, image_id=None, **kwargs):
|
def describe_images(self, context, image_id=None, **kwargs):
|
||||||
|
|||||||
@@ -165,15 +165,18 @@ class Controller(wsgi.Controller):
|
|||||||
if not inst_dict:
|
if not inst_dict:
|
||||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||||
|
|
||||||
|
ctxt = req.environ['nova.context']
|
||||||
update_dict = {}
|
update_dict = {}
|
||||||
if 'adminPass' in inst_dict['server']:
|
if 'adminPass' in inst_dict['server']:
|
||||||
update_dict['admin_pass'] = inst_dict['server']['adminPass']
|
update_dict['admin_pass'] = inst_dict['server']['adminPass']
|
||||||
|
try:
|
||||||
|
self.compute_api.set_admin_password(ctxt, id)
|
||||||
|
except exception.TimeoutException, e:
|
||||||
|
return exc.HTTPRequestTimeout()
|
||||||
if 'name' in inst_dict['server']:
|
if 'name' in inst_dict['server']:
|
||||||
update_dict['display_name'] = inst_dict['server']['name']
|
update_dict['display_name'] = inst_dict['server']['name']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.compute_api.update(req.environ['nova.context'], id,
|
self.compute_api.update(ctxt, id, **update_dict)
|
||||||
**update_dict)
|
|
||||||
except exception.NotFound:
|
except exception.NotFound:
|
||||||
return faults.Fault(exc.HTTPNotFound())
|
return faults.Fault(exc.HTTPNotFound())
|
||||||
return exc.HTTPNoContent()
|
return exc.HTTPNoContent()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Handles all requests relating to instances (guest vms).
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from nova import db
|
from nova import db
|
||||||
@@ -47,7 +48,8 @@ def generate_default_hostname(instance_id):
|
|||||||
class API(base.Base):
|
class API(base.Base):
|
||||||
"""API for interacting with the compute manager."""
|
"""API for interacting with the compute manager."""
|
||||||
|
|
||||||
def __init__(self, image_service=None, network_api=None, volume_api=None,
|
def __init__(self, image_service=None, network_api=None,
|
||||||
|
volume_api=None, hostname_factory=generate_default_hostname,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
if not image_service:
|
if not image_service:
|
||||||
image_service = utils.import_object(FLAGS.image_service)
|
image_service = utils.import_object(FLAGS.image_service)
|
||||||
@@ -58,9 +60,11 @@ class API(base.Base):
|
|||||||
if not volume_api:
|
if not volume_api:
|
||||||
volume_api = volume.API()
|
volume_api = volume.API()
|
||||||
self.volume_api = volume_api
|
self.volume_api = volume_api
|
||||||
|
self.hostname_factory = hostname_factory
|
||||||
super(API, self).__init__(**kwargs)
|
super(API, self).__init__(**kwargs)
|
||||||
|
|
||||||
def get_network_topic(self, context, instance_id):
|
def get_network_topic(self, context, instance_id):
|
||||||
|
"""Get the network topic for an instance."""
|
||||||
try:
|
try:
|
||||||
instance = self.get(context, instance_id)
|
instance = self.get(context, instance_id)
|
||||||
except exception.NotFound as e:
|
except exception.NotFound as e:
|
||||||
@@ -81,8 +85,7 @@ class API(base.Base):
|
|||||||
min_count=1, max_count=1,
|
min_count=1, max_count=1,
|
||||||
display_name='', display_description='',
|
display_name='', display_description='',
|
||||||
key_name=None, key_data=None, security_group='default',
|
key_name=None, key_data=None, security_group='default',
|
||||||
availability_zone=None, user_data=None,
|
availability_zone=None, user_data=None):
|
||||||
generate_hostname=generate_default_hostname):
|
|
||||||
"""Create the number of instances requested if quota and
|
"""Create the number of instances requested if quota and
|
||||||
other arguments check out ok."""
|
other arguments check out ok."""
|
||||||
|
|
||||||
@@ -172,9 +175,9 @@ class API(base.Base):
|
|||||||
security_group_id)
|
security_group_id)
|
||||||
|
|
||||||
# Set sane defaults if not specified
|
# Set sane defaults if not specified
|
||||||
updates = dict(hostname=generate_hostname(instance_id))
|
updates = dict(hostname=self.hostname_factory(instance_id))
|
||||||
if (not hasattr(instance, 'display_name')) or \
|
if (not hasattr(instance, 'display_name') or
|
||||||
instance.display_name == None:
|
instance.display_name == None):
|
||||||
updates['display_name'] = "Server %s" % instance_id
|
updates['display_name'] = "Server %s" % instance_id
|
||||||
|
|
||||||
instance = self.update(context, instance_id, **updates)
|
instance = self.update(context, instance_id, **updates)
|
||||||
@@ -192,7 +195,7 @@ class API(base.Base):
|
|||||||
for group_id in security_groups:
|
for group_id in security_groups:
|
||||||
self.trigger_security_group_members_refresh(elevated, group_id)
|
self.trigger_security_group_members_refresh(elevated, group_id)
|
||||||
|
|
||||||
return instances
|
return [dict(x.iteritems()) for x in instances]
|
||||||
|
|
||||||
def ensure_default_security_group(self, context):
|
def ensure_default_security_group(self, context):
|
||||||
""" Create security group for the security context if it
|
""" Create security group for the security context if it
|
||||||
@@ -277,10 +280,11 @@ class API(base.Base):
|
|||||||
:retval None
|
:retval None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.db.instance_update(context, instance_id, kwargs)
|
rv = self.db.instance_update(context, instance_id, kwargs)
|
||||||
|
return dict(rv.iteritems())
|
||||||
|
|
||||||
def delete(self, context, instance_id):
|
def delete(self, context, instance_id):
|
||||||
LOG.debug(_("Going to try and terminate %s"), instance_id)
|
LOG.debug(_("Going to try to terminate %s"), instance_id)
|
||||||
try:
|
try:
|
||||||
instance = self.get(context, instance_id)
|
instance = self.get(context, instance_id)
|
||||||
except exception.NotFound as e:
|
except exception.NotFound as e:
|
||||||
@@ -301,16 +305,15 @@ class API(base.Base):
|
|||||||
|
|
||||||
host = instance['host']
|
host = instance['host']
|
||||||
if host:
|
if host:
|
||||||
rpc.cast(context,
|
self._cast_compute_message('terminate_instance', context,
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
instance_id, host)
|
||||||
{"method": "terminate_instance",
|
|
||||||
"args": {"instance_id": instance_id}})
|
|
||||||
else:
|
else:
|
||||||
self.db.instance_destroy(context, instance_id)
|
self.db.instance_destroy(context, instance_id)
|
||||||
|
|
||||||
def get(self, context, instance_id):
|
def get(self, context, instance_id):
|
||||||
"""Get a single instance with the given ID."""
|
"""Get a single instance with the given ID."""
|
||||||
return self.db.instance_get_by_id(context, instance_id)
|
rv = self.db.instance_get_by_id(context, instance_id)
|
||||||
|
return dict(rv.iteritems())
|
||||||
|
|
||||||
def get_all(self, context, project_id=None, reservation_id=None,
|
def get_all(self, context, project_id=None, reservation_id=None,
|
||||||
fixed_ip=None):
|
fixed_ip=None):
|
||||||
@@ -319,7 +322,7 @@ class API(base.Base):
|
|||||||
an admin, it will retreive all instances in the system."""
|
an admin, it will retreive all instances in the system."""
|
||||||
if reservation_id is not None:
|
if reservation_id is not None:
|
||||||
return self.db.instance_get_all_by_reservation(context,
|
return self.db.instance_get_all_by_reservation(context,
|
||||||
reservation_id)
|
reservation_id)
|
||||||
if fixed_ip is not None:
|
if fixed_ip is not None:
|
||||||
return self.db.fixed_ip_get_instance(context, fixed_ip)
|
return self.db.fixed_ip_get_instance(context, fixed_ip)
|
||||||
if project_id or not context.is_admin:
|
if project_id or not context.is_admin:
|
||||||
@@ -332,56 +335,70 @@ class API(base.Base):
|
|||||||
project_id)
|
project_id)
|
||||||
return self.db.instance_get_all(context)
|
return self.db.instance_get_all(context)
|
||||||
|
|
||||||
|
def _cast_compute_message(self, method, context, instance_id, host=None,
|
||||||
|
params=None):
|
||||||
|
"""Generic handler for RPC casts to compute.
|
||||||
|
|
||||||
|
:param params: Optional dictionary of arguments to be passed to the
|
||||||
|
compute worker
|
||||||
|
|
||||||
|
:retval None
|
||||||
|
"""
|
||||||
|
if not params:
|
||||||
|
params = {}
|
||||||
|
if not host:
|
||||||
|
instance = self.get(context, instance_id)
|
||||||
|
host = instance['host']
|
||||||
|
queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
|
||||||
|
params['instance_id'] = instance_id
|
||||||
|
kwargs = {'method': method, 'args': params}
|
||||||
|
rpc.cast(context, queue, kwargs)
|
||||||
|
|
||||||
|
def _call_compute_message(self, method, context, instance_id, host=None,
|
||||||
|
params=None):
|
||||||
|
"""Generic handler for RPC calls to compute.
|
||||||
|
|
||||||
|
:param params: Optional dictionary of arguments to be passed to the
|
||||||
|
compute worker
|
||||||
|
|
||||||
|
:retval Result returned by compute worker
|
||||||
|
"""
|
||||||
|
if not params:
|
||||||
|
params = {}
|
||||||
|
if not host:
|
||||||
|
instance = self.get(context, instance_id)
|
||||||
|
host = instance["host"]
|
||||||
|
queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
|
||||||
|
params['instance_id'] = instance_id
|
||||||
|
kwargs = {'method': method, 'args': params}
|
||||||
|
return rpc.call(context, queue, kwargs)
|
||||||
|
|
||||||
def snapshot(self, context, instance_id, name):
|
def snapshot(self, context, instance_id, name):
|
||||||
"""Snapshot the given instance."""
|
"""Snapshot the given instance."""
|
||||||
instance = self.get(context, instance_id)
|
|
||||||
host = instance['host']
|
|
||||||
|
|
||||||
data = {'name': name, 'is_public': False}
|
data = {'name': name, 'is_public': False}
|
||||||
image_meta = self.image_service.create(context, data)
|
image_meta = self.image_service.create(context, data)
|
||||||
rpc.cast(context,
|
params = {'image_id': image_meta['id']}
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
self._cast_compute_message('snapshot_instance', context, instance_id,
|
||||||
{"method": "snapshot_instance",
|
params=params)
|
||||||
"args": {"instance_id": instance['id'],
|
|
||||||
"image_id": image_meta['id']}})
|
|
||||||
|
|
||||||
return image_meta
|
|
||||||
|
|
||||||
def reboot(self, context, instance_id):
|
def reboot(self, context, instance_id):
|
||||||
"""Reboot the given instance."""
|
"""Reboot the given instance."""
|
||||||
instance = self.get(context, instance_id)
|
self._cast_compute_message('reboot_instance', context, instance_id)
|
||||||
host = instance['host']
|
|
||||||
rpc.cast(context,
|
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
|
||||||
{"method": "reboot_instance",
|
|
||||||
"args": {"instance_id": instance_id}})
|
|
||||||
|
|
||||||
def pause(self, context, instance_id):
|
def pause(self, context, instance_id):
|
||||||
"""Pause the given instance."""
|
"""Pause the given instance."""
|
||||||
instance = self.get(context, instance_id)
|
self._cast_compute_message('pause_instance', context, instance_id)
|
||||||
host = instance['host']
|
|
||||||
rpc.cast(context,
|
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
|
||||||
{"method": "pause_instance",
|
|
||||||
"args": {"instance_id": instance_id}})
|
|
||||||
|
|
||||||
def unpause(self, context, instance_id):
|
def unpause(self, context, instance_id):
|
||||||
"""Unpause the given instance."""
|
"""Unpause the given instance."""
|
||||||
instance = self.get(context, instance_id)
|
self._cast_compute_message('unpause_instance', context, instance_id)
|
||||||
host = instance['host']
|
|
||||||
rpc.cast(context,
|
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
|
||||||
{"method": "unpause_instance",
|
|
||||||
"args": {"instance_id": instance_id}})
|
|
||||||
|
|
||||||
def get_diagnostics(self, context, instance_id):
|
def get_diagnostics(self, context, instance_id):
|
||||||
"""Retrieve diagnostics for the given instance."""
|
"""Retrieve diagnostics for the given instance."""
|
||||||
instance = self.get(context, instance_id)
|
return self._call_compute_message(
|
||||||
host = instance["host"]
|
"get_diagnostics",
|
||||||
return rpc.call(context,
|
context,
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
instance_id)
|
||||||
{"method": "get_diagnostics",
|
|
||||||
"args": {"instance_id": instance_id}})
|
|
||||||
|
|
||||||
def get_actions(self, context, instance_id):
|
def get_actions(self, context, instance_id):
|
||||||
"""Retrieve actions for the given instance."""
|
"""Retrieve actions for the given instance."""
|
||||||
@@ -389,89 +406,54 @@ class API(base.Base):
|
|||||||
|
|
||||||
def suspend(self, context, instance_id):
|
def suspend(self, context, instance_id):
|
||||||
"""suspend the instance with instance_id"""
|
"""suspend the instance with instance_id"""
|
||||||
instance = self.get(context, instance_id)
|
self._cast_compute_message('suspend_instance', context, instance_id)
|
||||||
host = instance['host']
|
|
||||||
rpc.cast(context,
|
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
|
||||||
{"method": "suspend_instance",
|
|
||||||
"args": {"instance_id": instance_id}})
|
|
||||||
|
|
||||||
def resume(self, context, instance_id):
|
def resume(self, context, instance_id):
|
||||||
"""resume the instance with instance_id"""
|
"""resume the instance with instance_id"""
|
||||||
instance = self.get(context, instance_id)
|
self._cast_compute_message('resume_instance', context, instance_id)
|
||||||
host = instance['host']
|
|
||||||
rpc.cast(context,
|
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
|
||||||
{"method": "resume_instance",
|
|
||||||
"args": {"instance_id": instance_id}})
|
|
||||||
|
|
||||||
def rescue(self, context, instance_id):
|
def rescue(self, context, instance_id):
|
||||||
"""Rescue the given instance."""
|
"""Rescue the given instance."""
|
||||||
instance = self.get(context, instance_id)
|
self._cast_compute_message('rescue_instance', context, instance_id)
|
||||||
host = instance['host']
|
|
||||||
rpc.cast(context,
|
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
|
||||||
{"method": "rescue_instance",
|
|
||||||
"args": {"instance_id": instance_id}})
|
|
||||||
|
|
||||||
def unrescue(self, context, instance_id):
|
def unrescue(self, context, instance_id):
|
||||||
"""Unrescue the given instance."""
|
"""Unrescue the given instance."""
|
||||||
instance = self.get(context, instance_id)
|
self._cast_compute_message('unrescue_instance', context, instance_id)
|
||||||
host = instance['host']
|
|
||||||
rpc.cast(context,
|
def set_admin_password(self, context, instance_id):
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
"""Set the root/admin password for the given instance."""
|
||||||
{"method": "unrescue_instance",
|
self._cast_compute_message('set_admin_password', context, instance_id)
|
||||||
"args": {"instance_id": instance['id']}})
|
|
||||||
|
|
||||||
def get_ajax_console(self, context, instance_id):
|
def get_ajax_console(self, context, instance_id):
|
||||||
"""Get a url to an AJAX Console"""
|
"""Get a url to an AJAX Console"""
|
||||||
|
|
||||||
instance = self.get(context, instance_id)
|
instance = self.get(context, instance_id)
|
||||||
|
output = self._call_compute_message('get_ajax_console',
|
||||||
output = rpc.call(context,
|
context,
|
||||||
'%s.%s' % (FLAGS.compute_topic,
|
instance_id)
|
||||||
instance['host']),
|
|
||||||
{'method': 'get_ajax_console',
|
|
||||||
'args': {'instance_id': instance['id']}})
|
|
||||||
|
|
||||||
rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic,
|
rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic,
|
||||||
{'method': 'authorize_ajax_console',
|
{'method': 'authorize_ajax_console',
|
||||||
'args': {'token': output['token'], 'host': output['host'],
|
'args': {'token': output['token'], 'host': output['host'],
|
||||||
'port': output['port']}})
|
'port': output['port']}})
|
||||||
|
|
||||||
return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url,
|
return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url,
|
||||||
output['token'])}
|
output['token'])}
|
||||||
|
|
||||||
def lock(self, context, instance_id):
|
def get_console_output(self, context, instance_id):
|
||||||
"""
|
"""Get console output for an an instance"""
|
||||||
lock the instance with instance_id
|
return self._call_compute_message('get_console_output',
|
||||||
|
context,
|
||||||
|
instance_id)
|
||||||
|
|
||||||
"""
|
def lock(self, context, instance_id):
|
||||||
instance = self.get_instance(context, instance_id)
|
"""lock the instance with instance_id"""
|
||||||
host = instance['host']
|
self._cast_compute_message('lock_instance', context, instance_id)
|
||||||
rpc.cast(context,
|
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
|
||||||
{"method": "lock_instance",
|
|
||||||
"args": {"instance_id": instance['id']}})
|
|
||||||
|
|
||||||
def unlock(self, context, instance_id):
|
def unlock(self, context, instance_id):
|
||||||
"""
|
"""unlock the instance with instance_id"""
|
||||||
unlock the instance with instance_id
|
self._cast_compute_message('unlock_instance', context, instance_id)
|
||||||
|
|
||||||
"""
|
|
||||||
instance = self.get_instance(context, instance_id)
|
|
||||||
host = instance['host']
|
|
||||||
rpc.cast(context,
|
|
||||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
|
||||||
{"method": "unlock_instance",
|
|
||||||
"args": {"instance_id": instance['id']}})
|
|
||||||
|
|
||||||
def get_lock(self, context, instance_id):
|
def get_lock(self, context, instance_id):
|
||||||
"""
|
"""return the boolean state of (instance with instance_id)'s lock"""
|
||||||
return the boolean state of (instance with instance_id)'s lock
|
instance = self.get(context, instance_id)
|
||||||
|
|
||||||
"""
|
|
||||||
instance = self.get_instance(context, instance_id)
|
|
||||||
return instance['locked']
|
return instance['locked']
|
||||||
|
|
||||||
def attach_volume(self, context, instance_id, volume_id, device):
|
def attach_volume(self, context, instance_id, volume_id, device):
|
||||||
|
|||||||
@@ -1,205 +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.
|
|
||||||
"""
|
|
||||||
Utility methods to resize, repartition, and modify disk images.
|
|
||||||
|
|
||||||
Includes injection of SSH PGP keys into authorized_keys file.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from nova import exception
|
|
||||||
from nova import flags
|
|
||||||
from nova import log as logging
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('nova.compute.disk')
|
|
||||||
FLAGS = flags.FLAGS
|
|
||||||
flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10,
|
|
||||||
'minimum size in bytes of root partition')
|
|
||||||
flags.DEFINE_integer('block_size', 1024 * 1024 * 256,
|
|
||||||
'block_size to use for dd')
|
|
||||||
|
|
||||||
|
|
||||||
def partition(infile, outfile, local_bytes=0, resize=True,
|
|
||||||
local_type='ext2', execute=None):
|
|
||||||
"""
|
|
||||||
Turns a partition (infile) into a bootable drive image (outfile).
|
|
||||||
|
|
||||||
The first 63 sectors (0-62) of the resulting image is a master boot record.
|
|
||||||
Infile becomes the first primary partition.
|
|
||||||
If local bytes is specified, a second primary partition is created and
|
|
||||||
formatted as ext2.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
In the diagram below, dashes represent drive sectors.
|
|
||||||
+-----+------. . .-------+------. . .------+
|
|
||||||
| 0 a| b c|d e|
|
|
||||||
+-----+------. . .-------+------. . .------+
|
|
||||||
| mbr | primary partiton | local partition |
|
|
||||||
+-----+------. . .-------+------. . .------+
|
|
||||||
|
|
||||||
"""
|
|
||||||
sector_size = 512
|
|
||||||
file_size = os.path.getsize(infile)
|
|
||||||
if resize and file_size < FLAGS.minimum_root_size:
|
|
||||||
last_sector = FLAGS.minimum_root_size / sector_size - 1
|
|
||||||
execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
|
|
||||||
% (infile, last_sector, sector_size))
|
|
||||||
execute('e2fsck -fp %s' % infile, check_exit_code=False)
|
|
||||||
execute('resize2fs %s' % infile)
|
|
||||||
file_size = FLAGS.minimum_root_size
|
|
||||||
elif file_size % sector_size != 0:
|
|
||||||
LOG.warn(_("Input partition size not evenly divisible by"
|
|
||||||
" sector size: %d / %d"), file_size, sector_size)
|
|
||||||
primary_sectors = file_size / sector_size
|
|
||||||
if local_bytes % sector_size != 0:
|
|
||||||
LOG.warn(_("Bytes for local storage not evenly divisible"
|
|
||||||
" by sector size: %d / %d"), local_bytes, sector_size)
|
|
||||||
local_sectors = local_bytes / sector_size
|
|
||||||
|
|
||||||
mbr_last = 62 # a
|
|
||||||
primary_first = mbr_last + 1 # b
|
|
||||||
primary_last = primary_first + primary_sectors - 1 # c
|
|
||||||
local_first = primary_last + 1 # d
|
|
||||||
local_last = local_first + local_sectors - 1 # e
|
|
||||||
last_sector = local_last # e
|
|
||||||
|
|
||||||
# create an empty file
|
|
||||||
execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
|
|
||||||
% (outfile, mbr_last, sector_size))
|
|
||||||
|
|
||||||
# make mbr partition
|
|
||||||
execute('parted --script %s mklabel msdos' % outfile)
|
|
||||||
|
|
||||||
# append primary file
|
|
||||||
execute('dd if=%s of=%s bs=%s conv=notrunc,fsync oflag=append'
|
|
||||||
% (infile, outfile, FLAGS.block_size))
|
|
||||||
|
|
||||||
# make primary partition
|
|
||||||
execute('parted --script %s mkpart primary %ds %ds'
|
|
||||||
% (outfile, primary_first, primary_last))
|
|
||||||
|
|
||||||
if local_bytes > 0:
|
|
||||||
# make the file bigger
|
|
||||||
execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
|
|
||||||
% (outfile, last_sector, sector_size))
|
|
||||||
# make and format local partition
|
|
||||||
execute('parted --script %s mkpartfs primary %s %ds %ds'
|
|
||||||
% (outfile, local_type, local_first, local_last))
|
|
||||||
|
|
||||||
|
|
||||||
def extend(image, size, execute):
|
|
||||||
file_size = os.path.getsize(image)
|
|
||||||
if file_size >= size:
|
|
||||||
return
|
|
||||||
return execute('truncate -s size %s' % (image,))
|
|
||||||
|
|
||||||
|
|
||||||
def inject_data(image, key=None, net=None, partition=None, execute=None):
|
|
||||||
"""Injects a ssh key and optionally net data into a disk image.
|
|
||||||
|
|
||||||
it will mount the image as a fully partitioned disk and attempt to inject
|
|
||||||
into the specified partition number.
|
|
||||||
|
|
||||||
If partition is not specified it mounts the image as a single partition.
|
|
||||||
|
|
||||||
"""
|
|
||||||
out, err = execute('sudo losetup --find --show %s' % image)
|
|
||||||
if err:
|
|
||||||
raise exception.Error(_('Could not attach image to loopback: %s')
|
|
||||||
% err)
|
|
||||||
device = out.strip()
|
|
||||||
try:
|
|
||||||
if not partition is None:
|
|
||||||
# create partition
|
|
||||||
out, err = execute('sudo kpartx -a %s' % device)
|
|
||||||
if err:
|
|
||||||
raise exception.Error(_('Failed to load partition: %s') % err)
|
|
||||||
mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1],
|
|
||||||
partition)
|
|
||||||
else:
|
|
||||||
mapped_device = device
|
|
||||||
|
|
||||||
# We can only loopback mount raw images. If the device isn't there,
|
|
||||||
# it's normally because it's a .vmdk or a .vdi etc
|
|
||||||
if not os.path.exists(mapped_device):
|
|
||||||
raise exception.Error('Mapped device was not found (we can'
|
|
||||||
' only inject raw disk images): %s' %
|
|
||||||
mapped_device)
|
|
||||||
|
|
||||||
# Configure ext2fs so that it doesn't auto-check every N boots
|
|
||||||
out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)
|
|
||||||
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
# mount loopback to dir
|
|
||||||
out, err = execute(
|
|
||||||
'sudo mount %s %s' % (mapped_device, tmpdir))
|
|
||||||
if err:
|
|
||||||
raise exception.Error(_('Failed to mount filesystem: %s')
|
|
||||||
% err)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if key:
|
|
||||||
# inject key file
|
|
||||||
_inject_key_into_fs(key, tmpdir, execute=execute)
|
|
||||||
if net:
|
|
||||||
_inject_net_into_fs(net, tmpdir, execute=execute)
|
|
||||||
finally:
|
|
||||||
# unmount device
|
|
||||||
execute('sudo umount %s' % mapped_device)
|
|
||||||
finally:
|
|
||||||
# remove temporary directory
|
|
||||||
execute('rmdir %s' % tmpdir)
|
|
||||||
if not partition is None:
|
|
||||||
# remove partitions
|
|
||||||
execute('sudo kpartx -d %s' % device)
|
|
||||||
finally:
|
|
||||||
# remove loopback
|
|
||||||
execute('sudo losetup --detach %s' % device)
|
|
||||||
|
|
||||||
|
|
||||||
def _inject_key_into_fs(key, fs, execute=None):
|
|
||||||
"""Add the given public ssh key to root's authorized_keys.
|
|
||||||
|
|
||||||
key is an ssh key string.
|
|
||||||
fs is the path to the base of the filesystem into which to inject the key.
|
|
||||||
"""
|
|
||||||
sshdir = os.path.join(fs, 'root', '.ssh')
|
|
||||||
execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter
|
|
||||||
execute('sudo chown root %s' % sshdir)
|
|
||||||
execute('sudo chmod 700 %s' % sshdir)
|
|
||||||
keyfile = os.path.join(sshdir, 'authorized_keys')
|
|
||||||
execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n')
|
|
||||||
|
|
||||||
|
|
||||||
def _inject_net_into_fs(net, fs, execute=None):
|
|
||||||
"""Inject /etc/network/interfaces into the filesystem rooted at fs.
|
|
||||||
|
|
||||||
net is the contents of /etc/network/interfaces.
|
|
||||||
"""
|
|
||||||
netdir = os.path.join(os.path.join(fs, 'etc'), 'network')
|
|
||||||
execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter
|
|
||||||
execute('sudo chown root:root %s' % netdir)
|
|
||||||
execute('sudo chmod 755 %s' % netdir)
|
|
||||||
netfile = os.path.join(netdir, 'interfaces')
|
|
||||||
execute('sudo tee %s' % netfile, net)
|
|
||||||
@@ -35,6 +35,8 @@ terminating it.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import random
|
||||||
|
import string
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
import functools
|
import functools
|
||||||
@@ -54,6 +56,8 @@ flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection',
|
|||||||
'Driver to use for controlling virtualization')
|
'Driver to use for controlling virtualization')
|
||||||
flags.DEFINE_string('stub_network', False,
|
flags.DEFINE_string('stub_network', False,
|
||||||
'Stub network related code')
|
'Stub network related code')
|
||||||
|
flags.DEFINE_integer('password_length', 12,
|
||||||
|
'Length of generated admin passwords')
|
||||||
flags.DEFINE_string('console_host', socket.gethostname(),
|
flags.DEFINE_string('console_host', socket.gethostname(),
|
||||||
'Console proxy host to use to connect to instances on'
|
'Console proxy host to use to connect to instances on'
|
||||||
'this host.')
|
'this host.')
|
||||||
@@ -309,6 +313,35 @@ class ComputeManager(manager.Manager):
|
|||||||
|
|
||||||
self.driver.snapshot(instance_ref, image_id)
|
self.driver.snapshot(instance_ref, image_id)
|
||||||
|
|
||||||
|
@exception.wrap_exception
|
||||||
|
@checks_instance_lock
|
||||||
|
def set_admin_password(self, context, instance_id, new_pass=None):
|
||||||
|
"""Set the root/admin password for an instance on this server."""
|
||||||
|
context = context.elevated()
|
||||||
|
instance_ref = self.db.instance_get(context, instance_id)
|
||||||
|
if instance_ref['state'] != power_state.RUNNING:
|
||||||
|
logging.warn('trying to reset the password on a non-running '
|
||||||
|
'instance: %s (state: %s expected: %s)',
|
||||||
|
instance_ref['id'],
|
||||||
|
instance_ref['state'],
|
||||||
|
power_state.RUNNING)
|
||||||
|
|
||||||
|
logging.debug('instance %s: setting admin password',
|
||||||
|
instance_ref['name'])
|
||||||
|
if new_pass is None:
|
||||||
|
# Generate a random password
|
||||||
|
new_pass = self._generate_password(FLAGS.password_length)
|
||||||
|
|
||||||
|
self.driver.set_admin_password(instance_ref, new_pass)
|
||||||
|
self._update_state(context, instance_id)
|
||||||
|
|
||||||
|
def _generate_password(self, length=20):
|
||||||
|
"""Generate a random sequence of letters and digits
|
||||||
|
to be used as a password.
|
||||||
|
"""
|
||||||
|
chrs = string.letters + string.digits
|
||||||
|
return "".join([random.choice(chrs) for i in xrange(length)])
|
||||||
|
|
||||||
@exception.wrap_exception
|
@exception.wrap_exception
|
||||||
@checks_instance_lock
|
@checks_instance_lock
|
||||||
def rescue_instance(self, context, instance_id):
|
def rescue_instance(self, context, instance_id):
|
||||||
|
|||||||
@@ -299,6 +299,10 @@ def fixed_ip_get_instance(context, address):
|
|||||||
return IMPL.fixed_ip_get_instance(context, address)
|
return IMPL.fixed_ip_get_instance(context, address)
|
||||||
|
|
||||||
|
|
||||||
|
def fixed_ip_get_instance_v6(context, address):
|
||||||
|
return IMPL.fixed_ip_get_instance_v6(context, address)
|
||||||
|
|
||||||
|
|
||||||
def fixed_ip_get_network(context, address):
|
def fixed_ip_get_network(context, address):
|
||||||
"""Get a network for a fixed ip by address."""
|
"""Get a network for a fixed ip by address."""
|
||||||
return IMPL.fixed_ip_get_network(context, address)
|
return IMPL.fixed_ip_get_network(context, address)
|
||||||
@@ -357,6 +361,10 @@ def instance_get_fixed_address(context, instance_id):
|
|||||||
return IMPL.instance_get_fixed_address(context, instance_id)
|
return IMPL.instance_get_fixed_address(context, instance_id)
|
||||||
|
|
||||||
|
|
||||||
|
def instance_get_fixed_address_v6(context, instance_id):
|
||||||
|
return IMPL.instance_get_fixed_address_v6(context, instance_id)
|
||||||
|
|
||||||
|
|
||||||
def instance_get_floating_address(context, instance_id):
|
def instance_get_floating_address(context, instance_id):
|
||||||
"""Get the first floating ip address of an instance."""
|
"""Get the first floating ip address of an instance."""
|
||||||
return IMPL.instance_get_floating_address(context, instance_id)
|
return IMPL.instance_get_floating_address(context, instance_id)
|
||||||
@@ -552,6 +560,10 @@ def project_get_network(context, project_id, associate=True):
|
|||||||
return IMPL.project_get_network(context, project_id)
|
return IMPL.project_get_network(context, project_id)
|
||||||
|
|
||||||
|
|
||||||
|
def project_get_network_v6(context, project_id):
|
||||||
|
return IMPL.project_get_network_v6(context, project_id)
|
||||||
|
|
||||||
|
|
||||||
###################
|
###################
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -606,6 +606,17 @@ def fixed_ip_get_instance(context, address):
|
|||||||
return fixed_ip_ref.instance
|
return fixed_ip_ref.instance
|
||||||
|
|
||||||
|
|
||||||
|
@require_context
|
||||||
|
def fixed_ip_get_instance_v6(context, address):
|
||||||
|
session = get_session()
|
||||||
|
mac = utils.to_mac(address)
|
||||||
|
|
||||||
|
result = session.query(models.Instance).\
|
||||||
|
filter_by(mac_address=mac).\
|
||||||
|
first()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@require_admin_context
|
@require_admin_context
|
||||||
def fixed_ip_get_network(context, address):
|
def fixed_ip_get_network(context, address):
|
||||||
fixed_ip_ref = fixed_ip_get_by_address(context, address)
|
fixed_ip_ref = fixed_ip_get_by_address(context, address)
|
||||||
@@ -764,6 +775,7 @@ def instance_get_by_id(context, instance_id):
|
|||||||
|
|
||||||
if is_admin_context(context):
|
if is_admin_context(context):
|
||||||
result = session.query(models.Instance).\
|
result = session.query(models.Instance).\
|
||||||
|
options(joinedload_all('fixed_ip.floating_ips')).\
|
||||||
options(joinedload('security_groups')).\
|
options(joinedload('security_groups')).\
|
||||||
options(joinedload_all('fixed_ip.floating_ips')).\
|
options(joinedload_all('fixed_ip.floating_ips')).\
|
||||||
filter_by(id=instance_id).\
|
filter_by(id=instance_id).\
|
||||||
@@ -793,6 +805,17 @@ def instance_get_fixed_address(context, instance_id):
|
|||||||
return instance_ref.fixed_ip['address']
|
return instance_ref.fixed_ip['address']
|
||||||
|
|
||||||
|
|
||||||
|
@require_context
|
||||||
|
def instance_get_fixed_address_v6(context, instance_id):
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
instance_ref = instance_get(context, instance_id, session=session)
|
||||||
|
network_ref = network_get_by_instance(context, instance_id)
|
||||||
|
prefix = network_ref.cidr_v6
|
||||||
|
mac = instance_ref.mac_address
|
||||||
|
return utils.to_global_ipv6(prefix, mac)
|
||||||
|
|
||||||
|
|
||||||
@require_context
|
@require_context
|
||||||
def instance_get_floating_address(context, instance_id):
|
def instance_get_floating_address(context, instance_id):
|
||||||
session = get_session()
|
session = get_session()
|
||||||
@@ -1130,6 +1153,11 @@ def project_get_network(context, project_id, associate=True):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@require_context
|
||||||
|
def project_get_network_v6(context, project_id):
|
||||||
|
return project_get_network(context, project_id)
|
||||||
|
|
||||||
|
|
||||||
###################
|
###################
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,14 @@ class NovaBase(object):
|
|||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
def iteritems(self):
|
def iteritems(self):
|
||||||
"""Make the model object behave like a dict"""
|
"""Make the model object behave like a dict.
|
||||||
return iter(self)
|
|
||||||
|
Includes attributes from joins."""
|
||||||
|
local = dict(self)
|
||||||
|
joined = dict([(k, v) for k, v in self.__dict__.iteritems()
|
||||||
|
if not k[0] == '_'])
|
||||||
|
local.update(joined)
|
||||||
|
return local.iteritems()
|
||||||
|
|
||||||
|
|
||||||
# TODO(vish): Store images in the database instead of file system
|
# TODO(vish): Store images in the database instead of file system
|
||||||
@@ -411,6 +417,10 @@ class Network(BASE, NovaBase):
|
|||||||
|
|
||||||
injected = Column(Boolean, default=False)
|
injected = Column(Boolean, default=False)
|
||||||
cidr = Column(String(255), unique=True)
|
cidr = Column(String(255), unique=True)
|
||||||
|
cidr_v6 = Column(String(255), unique=True)
|
||||||
|
|
||||||
|
ra_server = Column(String(255))
|
||||||
|
|
||||||
netmask = Column(String(255))
|
netmask = Column(String(255))
|
||||||
bridge = Column(String(255))
|
bridge = Column(String(255))
|
||||||
gateway = Column(String(255))
|
gateway = Column(String(255))
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ class InvalidInputException(Error):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutException(Error):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def wrap_exception(f):
|
def wrap_exception(f):
|
||||||
def _wrap(*args, **kw):
|
def _wrap(*args, **kw):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ flags.DEFINE_string('routing_source_ip', '$my_ip',
|
|||||||
'Public IP of network host')
|
'Public IP of network host')
|
||||||
flags.DEFINE_bool('use_nova_chains', False,
|
flags.DEFINE_bool('use_nova_chains', False,
|
||||||
'use the nova_ routing chains instead of default')
|
'use the nova_ routing chains instead of default')
|
||||||
|
|
||||||
flags.DEFINE_string('dns_server', None,
|
flags.DEFINE_string('dns_server', None,
|
||||||
'if set, uses specific dns server for dnsmasq')
|
'if set, uses specific dns server for dnsmasq')
|
||||||
flags.DEFINE_string('dmz_cidr', '10.128.0.0/24',
|
flags.DEFINE_string('dmz_cidr', '10.128.0.0/24',
|
||||||
@@ -196,6 +197,10 @@ def ensure_bridge(bridge, interface, net_attrs=None):
|
|||||||
net_attrs['gateway'],
|
net_attrs['gateway'],
|
||||||
net_attrs['broadcast'],
|
net_attrs['broadcast'],
|
||||||
net_attrs['netmask']))
|
net_attrs['netmask']))
|
||||||
|
if(FLAGS.use_ipv6):
|
||||||
|
_execute("sudo ifconfig %s add %s up" % \
|
||||||
|
(bridge,
|
||||||
|
net_attrs['cidr_v6']))
|
||||||
else:
|
else:
|
||||||
_execute("sudo ifconfig %s up" % bridge)
|
_execute("sudo ifconfig %s up" % bridge)
|
||||||
if FLAGS.use_nova_chains:
|
if FLAGS.use_nova_chains:
|
||||||
@@ -262,6 +267,50 @@ def update_dhcp(context, network_id):
|
|||||||
_execute(command, addl_env=env)
|
_execute(command, addl_env=env)
|
||||||
|
|
||||||
|
|
||||||
|
def update_ra(context, network_id):
|
||||||
|
network_ref = db.network_get(context, network_id)
|
||||||
|
|
||||||
|
conffile = _ra_file(network_ref['bridge'], 'conf')
|
||||||
|
with open(conffile, 'w') as f:
|
||||||
|
conf_str = """
|
||||||
|
interface %s
|
||||||
|
{
|
||||||
|
AdvSendAdvert on;
|
||||||
|
MinRtrAdvInterval 3;
|
||||||
|
MaxRtrAdvInterval 10;
|
||||||
|
prefix %s
|
||||||
|
{
|
||||||
|
AdvOnLink on;
|
||||||
|
AdvAutonomous on;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
""" % (network_ref['bridge'], network_ref['cidr_v6'])
|
||||||
|
f.write(conf_str)
|
||||||
|
|
||||||
|
# Make sure radvd can actually read it (it setuid()s to "nobody")
|
||||||
|
os.chmod(conffile, 0644)
|
||||||
|
|
||||||
|
pid = _ra_pid_for(network_ref['bridge'])
|
||||||
|
|
||||||
|
# if radvd is already running, then tell it to reload
|
||||||
|
if pid:
|
||||||
|
out, _err = _execute('cat /proc/%d/cmdline'
|
||||||
|
% pid, check_exit_code=False)
|
||||||
|
if conffile in out:
|
||||||
|
try:
|
||||||
|
_execute('sudo kill -HUP %d' % pid)
|
||||||
|
return
|
||||||
|
except Exception as exc: # pylint: disable-msg=W0703
|
||||||
|
LOG.debug(_("Hupping radvd threw %s"), exc)
|
||||||
|
else:
|
||||||
|
LOG.debug(_("Pid %d is stale, relaunching radvd"), pid)
|
||||||
|
command = _ra_cmd(network_ref)
|
||||||
|
_execute(command)
|
||||||
|
db.network_update(context, network_id,
|
||||||
|
{"ra_server":
|
||||||
|
utils.get_my_linklocal(network_ref['bridge'])})
|
||||||
|
|
||||||
|
|
||||||
def _host_dhcp(fixed_ip_ref):
|
def _host_dhcp(fixed_ip_ref):
|
||||||
"""Return a host string for an address"""
|
"""Return a host string for an address"""
|
||||||
instance_ref = fixed_ip_ref['instance']
|
instance_ref = fixed_ip_ref['instance']
|
||||||
@@ -323,6 +372,15 @@ def _dnsmasq_cmd(net):
|
|||||||
return ''.join(cmd)
|
return ''.join(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def _ra_cmd(net):
|
||||||
|
"""Builds radvd command"""
|
||||||
|
cmd = ['sudo -E radvd',
|
||||||
|
# ' -u nobody',
|
||||||
|
' -C %s' % _ra_file(net['bridge'], 'conf'),
|
||||||
|
' -p %s' % _ra_file(net['bridge'], 'pid')]
|
||||||
|
return ''.join(cmd)
|
||||||
|
|
||||||
|
|
||||||
def _stop_dnsmasq(network):
|
def _stop_dnsmasq(network):
|
||||||
"""Stops the dnsmasq instance for a given network"""
|
"""Stops the dnsmasq instance for a given network"""
|
||||||
pid = _dnsmasq_pid_for(network)
|
pid = _dnsmasq_pid_for(network)
|
||||||
@@ -344,6 +402,16 @@ def _dhcp_file(bridge, kind):
|
|||||||
kind))
|
kind))
|
||||||
|
|
||||||
|
|
||||||
|
def _ra_file(bridge, kind):
|
||||||
|
"""Return path to a pid or conf file for a bridge"""
|
||||||
|
|
||||||
|
if not os.path.exists(FLAGS.networks_path):
|
||||||
|
os.makedirs(FLAGS.networks_path)
|
||||||
|
return os.path.abspath("%s/nova-ra-%s.%s" % (FLAGS.networks_path,
|
||||||
|
bridge,
|
||||||
|
kind))
|
||||||
|
|
||||||
|
|
||||||
def _dnsmasq_pid_for(bridge):
|
def _dnsmasq_pid_for(bridge):
|
||||||
"""Returns the pid for prior dnsmasq instance for a bridge
|
"""Returns the pid for prior dnsmasq instance for a bridge
|
||||||
|
|
||||||
@@ -357,3 +425,18 @@ def _dnsmasq_pid_for(bridge):
|
|||||||
if os.path.exists(pid_file):
|
if os.path.exists(pid_file):
|
||||||
with open(pid_file, 'r') as f:
|
with open(pid_file, 'r') as f:
|
||||||
return int(f.read())
|
return int(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
def _ra_pid_for(bridge):
|
||||||
|
"""Returns the pid for prior radvd instance for a bridge
|
||||||
|
|
||||||
|
Returns None if no pid file exists
|
||||||
|
|
||||||
|
If machine has rebooted pid might be incorrect (caller should check)
|
||||||
|
"""
|
||||||
|
|
||||||
|
pid_file = _ra_file(bridge, 'pid')
|
||||||
|
|
||||||
|
if os.path.exists(pid_file):
|
||||||
|
with open(pid_file, 'r') as f:
|
||||||
|
return int(f.read())
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ flags.DEFINE_integer('network_size', 256,
|
|||||||
flags.DEFINE_string('floating_range', '4.4.4.0/24',
|
flags.DEFINE_string('floating_range', '4.4.4.0/24',
|
||||||
'Floating IP address block')
|
'Floating IP address block')
|
||||||
flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block')
|
flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block')
|
||||||
|
flags.DEFINE_string('fixed_range_v6', 'fd00::/48', 'Fixed IPv6 address block')
|
||||||
flags.DEFINE_integer('cnt_vpn_clients', 5,
|
flags.DEFINE_integer('cnt_vpn_clients', 5,
|
||||||
'Number of addresses reserved for vpn clients')
|
'Number of addresses reserved for vpn clients')
|
||||||
flags.DEFINE_string('network_driver', 'nova.network.linux_net',
|
flags.DEFINE_string('network_driver', 'nova.network.linux_net',
|
||||||
@@ -90,6 +91,9 @@ flags.DEFINE_bool('update_dhcp_on_disassociate', False,
|
|||||||
'Whether to update dhcp when fixed_ip is disassociated')
|
'Whether to update dhcp when fixed_ip is disassociated')
|
||||||
flags.DEFINE_integer('fixed_ip_disassociate_timeout', 600,
|
flags.DEFINE_integer('fixed_ip_disassociate_timeout', 600,
|
||||||
'Seconds after which a deallocated ip is disassociated')
|
'Seconds after which a deallocated ip is disassociated')
|
||||||
|
|
||||||
|
flags.DEFINE_bool('use_ipv6', True,
|
||||||
|
'use the ipv6')
|
||||||
flags.DEFINE_string('network_host', socket.gethostname(),
|
flags.DEFINE_string('network_host', socket.gethostname(),
|
||||||
'Network host to use for ip allocation in flat modes')
|
'Network host to use for ip allocation in flat modes')
|
||||||
flags.DEFINE_bool('fake_call', False,
|
flags.DEFINE_bool('fake_call', False,
|
||||||
@@ -235,8 +239,8 @@ class NetworkManager(manager.Manager):
|
|||||||
"""Get the network host for the current context."""
|
"""Get the network host for the current context."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def create_networks(self, context, num_networks, network_size,
|
def create_networks(self, context, cidr, num_networks, network_size,
|
||||||
*args, **kwargs):
|
cidr_v6, *args, **kwargs):
|
||||||
"""Create networks based on parameters."""
|
"""Create networks based on parameters."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@@ -321,9 +325,11 @@ class FlatManager(NetworkManager):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def create_networks(self, context, cidr, num_networks, network_size,
|
def create_networks(self, context, cidr, num_networks, network_size,
|
||||||
*args, **kwargs):
|
cidr_v6, *args, **kwargs):
|
||||||
"""Create networks based on parameters."""
|
"""Create networks based on parameters."""
|
||||||
fixed_net = IPy.IP(cidr)
|
fixed_net = IPy.IP(cidr)
|
||||||
|
fixed_net_v6 = IPy.IP(cidr_v6)
|
||||||
|
significant_bits_v6 = 64
|
||||||
for index in range(num_networks):
|
for index in range(num_networks):
|
||||||
start = index * network_size
|
start = index * network_size
|
||||||
significant_bits = 32 - int(math.log(network_size, 2))
|
significant_bits = 32 - int(math.log(network_size, 2))
|
||||||
@@ -336,7 +342,13 @@ class FlatManager(NetworkManager):
|
|||||||
net['gateway'] = str(project_net[1])
|
net['gateway'] = str(project_net[1])
|
||||||
net['broadcast'] = str(project_net.broadcast())
|
net['broadcast'] = str(project_net.broadcast())
|
||||||
net['dhcp_start'] = str(project_net[2])
|
net['dhcp_start'] = str(project_net[2])
|
||||||
|
|
||||||
|
if(FLAGS.use_ipv6):
|
||||||
|
cidr_v6 = "%s/%s" % (fixed_net_v6[0], significant_bits_v6)
|
||||||
|
net['cidr_v6'] = cidr_v6
|
||||||
|
|
||||||
network_ref = self.db.network_create_safe(context, net)
|
network_ref = self.db.network_create_safe(context, net)
|
||||||
|
|
||||||
if network_ref:
|
if network_ref:
|
||||||
self._create_fixed_ips(context, network_ref['id'])
|
self._create_fixed_ips(context, network_ref['id'])
|
||||||
|
|
||||||
@@ -482,12 +494,16 @@ class VlanManager(NetworkManager):
|
|||||||
network_ref['bridge'])
|
network_ref['bridge'])
|
||||||
|
|
||||||
def create_networks(self, context, cidr, num_networks, network_size,
|
def create_networks(self, context, cidr, num_networks, network_size,
|
||||||
vlan_start, vpn_start):
|
vlan_start, vpn_start, cidr_v6):
|
||||||
"""Create networks based on parameters."""
|
"""Create networks based on parameters."""
|
||||||
fixed_net = IPy.IP(cidr)
|
fixed_net = IPy.IP(cidr)
|
||||||
|
fixed_net_v6 = IPy.IP(cidr_v6)
|
||||||
|
network_size_v6 = 1 << 64
|
||||||
|
significant_bits_v6 = 64
|
||||||
for index in range(num_networks):
|
for index in range(num_networks):
|
||||||
vlan = vlan_start + index
|
vlan = vlan_start + index
|
||||||
start = index * network_size
|
start = index * network_size
|
||||||
|
start_v6 = index * network_size_v6
|
||||||
significant_bits = 32 - int(math.log(network_size, 2))
|
significant_bits = 32 - int(math.log(network_size, 2))
|
||||||
cidr = "%s/%s" % (fixed_net[start], significant_bits)
|
cidr = "%s/%s" % (fixed_net[start], significant_bits)
|
||||||
project_net = IPy.IP(cidr)
|
project_net = IPy.IP(cidr)
|
||||||
@@ -500,6 +516,11 @@ class VlanManager(NetworkManager):
|
|||||||
net['dhcp_start'] = str(project_net[3])
|
net['dhcp_start'] = str(project_net[3])
|
||||||
net['vlan'] = vlan
|
net['vlan'] = vlan
|
||||||
net['bridge'] = 'br%s' % vlan
|
net['bridge'] = 'br%s' % vlan
|
||||||
|
if(FLAGS.use_ipv6):
|
||||||
|
cidr_v6 = "%s/%s" % (fixed_net_v6[start_v6],
|
||||||
|
significant_bits_v6)
|
||||||
|
net['cidr_v6'] = cidr_v6
|
||||||
|
|
||||||
# NOTE(vish): This makes ports unique accross the cloud, a more
|
# NOTE(vish): This makes ports unique accross the cloud, a more
|
||||||
# robust solution would be to make them unique per ip
|
# robust solution would be to make them unique per ip
|
||||||
net['vpn_public_port'] = vpn_start + index
|
net['vpn_public_port'] = vpn_start + index
|
||||||
@@ -538,6 +559,7 @@ class VlanManager(NetworkManager):
|
|||||||
self.driver.ensure_vlan_bridge(network_ref['vlan'],
|
self.driver.ensure_vlan_bridge(network_ref['vlan'],
|
||||||
network_ref['bridge'],
|
network_ref['bridge'],
|
||||||
network_ref)
|
network_ref)
|
||||||
|
|
||||||
# NOTE(vish): only ensure this forward if the address hasn't been set
|
# NOTE(vish): only ensure this forward if the address hasn't been set
|
||||||
# manually.
|
# manually.
|
||||||
if address == FLAGS.vpn_ip:
|
if address == FLAGS.vpn_ip:
|
||||||
@@ -546,6 +568,8 @@ class VlanManager(NetworkManager):
|
|||||||
network_ref['vpn_private_address'])
|
network_ref['vpn_private_address'])
|
||||||
if not FLAGS.fake_network:
|
if not FLAGS.fake_network:
|
||||||
self.driver.update_dhcp(context, network_id)
|
self.driver.update_dhcp(context, network_id)
|
||||||
|
if(FLAGS.use_ipv6):
|
||||||
|
self.driver.update_ra(context, network_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _bottom_reserved_ips(self):
|
def _bottom_reserved_ips(self):
|
||||||
|
|||||||
99
nova/test.py
99
nova/test.py
@@ -23,14 +23,10 @@ and some black magic for inline callbacks.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import mox
|
import mox
|
||||||
import stubout
|
import stubout
|
||||||
from twisted.internet import defer
|
|
||||||
from twisted.trial import unittest as trial_unittest
|
|
||||||
|
|
||||||
from nova import context
|
from nova import context
|
||||||
from nova import db
|
from nova import db
|
||||||
@@ -74,7 +70,8 @@ class TestCase(unittest.TestCase):
|
|||||||
FLAGS.fixed_range,
|
FLAGS.fixed_range,
|
||||||
5, 16,
|
5, 16,
|
||||||
FLAGS.vlan_start,
|
FLAGS.vlan_start,
|
||||||
FLAGS.vpn_start)
|
FLAGS.vpn_start,
|
||||||
|
FLAGS.fixed_range_v6)
|
||||||
|
|
||||||
# emulate some of the mox stuff, we can't use the metaclass
|
# emulate some of the mox stuff, we can't use the metaclass
|
||||||
# because it screws with our generators
|
# because it screws with our generators
|
||||||
@@ -139,95 +136,3 @@ class TestCase(unittest.TestCase):
|
|||||||
|
|
||||||
_wrapped.func_name = self.originalAttach.func_name
|
_wrapped.func_name = self.originalAttach.func_name
|
||||||
rpc.Consumer.attach_to_eventlet = _wrapped
|
rpc.Consumer.attach_to_eventlet = _wrapped
|
||||||
|
|
||||||
|
|
||||||
class TrialTestCase(trial_unittest.TestCase):
|
|
||||||
"""Test case base class for all unit tests"""
|
|
||||||
def setUp(self):
|
|
||||||
"""Run before each test method to initialize test environment"""
|
|
||||||
super(TrialTestCase, self).setUp()
|
|
||||||
# NOTE(vish): We need a better method for creating fixtures for tests
|
|
||||||
# now that we have some required db setup for the system
|
|
||||||
# to work properly.
|
|
||||||
self.start = datetime.datetime.utcnow()
|
|
||||||
ctxt = context.get_admin_context()
|
|
||||||
if db.network_count(ctxt) != 5:
|
|
||||||
network_manager.VlanManager().create_networks(ctxt,
|
|
||||||
FLAGS.fixed_range,
|
|
||||||
5, 16,
|
|
||||||
FLAGS.vlan_start,
|
|
||||||
FLAGS.vpn_start)
|
|
||||||
|
|
||||||
# emulate some of the mox stuff, we can't use the metaclass
|
|
||||||
# because it screws with our generators
|
|
||||||
self.mox = mox.Mox()
|
|
||||||
self.stubs = stubout.StubOutForTesting()
|
|
||||||
self.flag_overrides = {}
|
|
||||||
self.injected = []
|
|
||||||
self._original_flags = FLAGS.FlagValuesDict()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Runs after each test method to finalize/tear down test
|
|
||||||
environment."""
|
|
||||||
try:
|
|
||||||
self.mox.UnsetStubs()
|
|
||||||
self.stubs.UnsetAll()
|
|
||||||
self.stubs.SmartUnsetAll()
|
|
||||||
self.mox.VerifyAll()
|
|
||||||
# NOTE(vish): Clean up any ips associated during the test.
|
|
||||||
ctxt = context.get_admin_context()
|
|
||||||
db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host,
|
|
||||||
self.start)
|
|
||||||
db.network_disassociate_all(ctxt)
|
|
||||||
for x in self.injected:
|
|
||||||
try:
|
|
||||||
x.stop()
|
|
||||||
except AssertionError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if FLAGS.fake_rabbit:
|
|
||||||
fakerabbit.reset_all()
|
|
||||||
|
|
||||||
db.security_group_destroy_all(ctxt)
|
|
||||||
super(TrialTestCase, self).tearDown()
|
|
||||||
finally:
|
|
||||||
self.reset_flags()
|
|
||||||
|
|
||||||
def flags(self, **kw):
|
|
||||||
"""Override flag variables for a test"""
|
|
||||||
for k, v in kw.iteritems():
|
|
||||||
if k in self.flag_overrides:
|
|
||||||
self.reset_flags()
|
|
||||||
raise Exception(
|
|
||||||
'trying to override already overriden flag: %s' % k)
|
|
||||||
self.flag_overrides[k] = getattr(FLAGS, k)
|
|
||||||
setattr(FLAGS, k, v)
|
|
||||||
|
|
||||||
def reset_flags(self):
|
|
||||||
"""Resets all flag variables for the test. Runs after each test"""
|
|
||||||
FLAGS.Reset()
|
|
||||||
for k, v in self._original_flags.iteritems():
|
|
||||||
setattr(FLAGS, k, v)
|
|
||||||
|
|
||||||
def run(self, result=None):
|
|
||||||
test_method = getattr(self, self._testMethodName)
|
|
||||||
setattr(self,
|
|
||||||
self._testMethodName,
|
|
||||||
self._maybeInlineCallbacks(test_method, result))
|
|
||||||
rv = super(TrialTestCase, self).run(result)
|
|
||||||
setattr(self, self._testMethodName, test_method)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def _maybeInlineCallbacks(self, func, result):
|
|
||||||
def _wrapped():
|
|
||||||
g = func()
|
|
||||||
if isinstance(g, defer.Deferred):
|
|
||||||
return g
|
|
||||||
if not hasattr(g, 'send'):
|
|
||||||
return defer.succeed(g)
|
|
||||||
|
|
||||||
inlined = defer.inlineCallbacks(func)
|
|
||||||
d = inlined()
|
|
||||||
return d
|
|
||||||
_wrapped.func_name = func.func_name
|
|
||||||
return _wrapped
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class FakeHttplibConnection(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class XmlConversionTestCase(test.TrialTestCase):
|
class XmlConversionTestCase(test.TestCase):
|
||||||
"""Unit test api xml conversion"""
|
"""Unit test api xml conversion"""
|
||||||
def test_number_conversion(self):
|
def test_number_conversion(self):
|
||||||
conv = apirequest._try_convert
|
conv = apirequest._try_convert
|
||||||
@@ -96,7 +96,7 @@ class XmlConversionTestCase(test.TrialTestCase):
|
|||||||
self.assertEqual(conv('-0'), 0)
|
self.assertEqual(conv('-0'), 0)
|
||||||
|
|
||||||
|
|
||||||
class ApiEc2TestCase(test.TrialTestCase):
|
class ApiEc2TestCase(test.TestCase):
|
||||||
"""Unit test for the cloud controller on an EC2 API"""
|
"""Unit test for the cloud controller on an EC2 API"""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ApiEc2TestCase, self).setUp()
|
super(ApiEc2TestCase, self).setUp()
|
||||||
@@ -265,6 +265,72 @@ class ApiEc2TestCase(test.TrialTestCase):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def test_authorize_revoke_security_group_cidr_v6(self):
|
||||||
|
"""
|
||||||
|
Test that we can add and remove CIDR based rules
|
||||||
|
to a security group for IPv6
|
||||||
|
"""
|
||||||
|
self.expect_http()
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
user = self.manager.create_user('fake', 'fake', 'fake')
|
||||||
|
project = self.manager.create_project('fake', 'fake', 'fake')
|
||||||
|
|
||||||
|
# At the moment, you need both of these to actually be netadmin
|
||||||
|
self.manager.add_role('fake', 'netadmin')
|
||||||
|
project.add_role('fake', 'netadmin')
|
||||||
|
|
||||||
|
security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd")
|
||||||
|
for x in range(random.randint(4, 8)))
|
||||||
|
|
||||||
|
group = self.ec2.create_security_group(security_group_name,
|
||||||
|
'test group')
|
||||||
|
|
||||||
|
self.expect_http()
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
group.connection = self.ec2
|
||||||
|
|
||||||
|
group.authorize('tcp', 80, 81, '::/0')
|
||||||
|
|
||||||
|
self.expect_http()
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
rv = self.ec2.get_all_security_groups()
|
||||||
|
# I don't bother checkng that we actually find it here,
|
||||||
|
# because the create/delete unit test further up should
|
||||||
|
# be good enough for that.
|
||||||
|
for group in rv:
|
||||||
|
if group.name == security_group_name:
|
||||||
|
self.assertEquals(len(group.rules), 1)
|
||||||
|
self.assertEquals(int(group.rules[0].from_port), 80)
|
||||||
|
self.assertEquals(int(group.rules[0].to_port), 81)
|
||||||
|
self.assertEquals(len(group.rules[0].grants), 1)
|
||||||
|
self.assertEquals(str(group.rules[0].grants[0]), '::/0')
|
||||||
|
|
||||||
|
self.expect_http()
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
group.connection = self.ec2
|
||||||
|
|
||||||
|
group.revoke('tcp', 80, 81, '::/0')
|
||||||
|
|
||||||
|
self.expect_http()
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
self.ec2.delete_security_group(security_group_name)
|
||||||
|
|
||||||
|
self.expect_http()
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
group.connection = self.ec2
|
||||||
|
|
||||||
|
rv = self.ec2.get_all_security_groups()
|
||||||
|
|
||||||
|
self.assertEqual(len(rv), 1)
|
||||||
|
self.assertEqual(rv[0].name, 'default')
|
||||||
|
|
||||||
|
self.manager.delete_project(project)
|
||||||
|
self.manager.delete_user(user)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
def test_authorize_revoke_security_group_foreign_group(self):
|
def test_authorize_revoke_security_group_foreign_group(self):
|
||||||
"""
|
"""
|
||||||
Test that we can grant and revoke another security group access
|
Test that we can grant and revoke another security group access
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import json
|
|||||||
from M2Crypto import BIO
|
from M2Crypto import BIO
|
||||||
from M2Crypto import RSA
|
from M2Crypto import RSA
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -50,6 +51,8 @@ IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images')
|
|||||||
os.makedirs(IMAGES_PATH)
|
os.makedirs(IMAGES_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(termie): these tests are rather fragile, they should at the lest be
|
||||||
|
# wiping database state after each run
|
||||||
class CloudTestCase(test.TestCase):
|
class CloudTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(CloudTestCase, self).setUp()
|
super(CloudTestCase, self).setUp()
|
||||||
@@ -287,6 +290,7 @@ class CloudTestCase(test.TestCase):
|
|||||||
db.service_destroy(self.context, comp1['id'])
|
db.service_destroy(self.context, comp1['id'])
|
||||||
|
|
||||||
def test_instance_update_state(self):
|
def test_instance_update_state(self):
|
||||||
|
# TODO(termie): what is this code even testing?
|
||||||
def instance(num):
|
def instance(num):
|
||||||
return {
|
return {
|
||||||
'reservation_id': 'r-1',
|
'reservation_id': 'r-1',
|
||||||
@@ -305,7 +309,8 @@ class CloudTestCase(test.TestCase):
|
|||||||
'state': 0x01,
|
'state': 0x01,
|
||||||
'user_data': ''}
|
'user_data': ''}
|
||||||
rv = self.cloud._format_describe_instances(self.context)
|
rv = self.cloud._format_describe_instances(self.context)
|
||||||
self.assert_(len(rv['reservationSet']) == 0)
|
logging.error(str(rv))
|
||||||
|
self.assertEqual(len(rv['reservationSet']), 0)
|
||||||
|
|
||||||
# simulate launch of 5 instances
|
# simulate launch of 5 instances
|
||||||
# self.cloud.instances['pending'] = {}
|
# self.cloud.instances['pending'] = {}
|
||||||
@@ -368,6 +373,7 @@ class CloudTestCase(test.TestCase):
|
|||||||
self.assertEqual('Foo Img', img.metadata['description'])
|
self.assertEqual('Foo Img', img.metadata['description'])
|
||||||
self._fake_set_image_description(self.context, 'ami-testing', '')
|
self._fake_set_image_description(self.context, 'ami-testing', '')
|
||||||
self.assertEqual('', img.metadata['description'])
|
self.assertEqual('', img.metadata['description'])
|
||||||
|
shutil.rmtree(pathdir)
|
||||||
|
|
||||||
def test_update_of_instance_display_fields(self):
|
def test_update_of_instance_display_fields(self):
|
||||||
inst = db.instance_create(self.context, {})
|
inst = db.instance_create(self.context, {})
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class ComputeTestCase(test.TestCase):
|
|||||||
ref = self.compute_api.create(self.context,
|
ref = self.compute_api.create(self.context,
|
||||||
FLAGS.default_instance_type, None, **instance)
|
FLAGS.default_instance_type, None, **instance)
|
||||||
try:
|
try:
|
||||||
self.assertNotEqual(ref[0].display_name, None)
|
self.assertNotEqual(ref[0]['display_name'], None)
|
||||||
finally:
|
finally:
|
||||||
db.instance_destroy(self.context, ref[0]['id'])
|
db.instance_destroy(self.context, ref[0]['id'])
|
||||||
|
|
||||||
@@ -86,10 +86,14 @@ class ComputeTestCase(test.TestCase):
|
|||||||
'user_id': self.user.id,
|
'user_id': self.user.id,
|
||||||
'project_id': self.project.id}
|
'project_id': self.project.id}
|
||||||
group = db.security_group_create(self.context, values)
|
group = db.security_group_create(self.context, values)
|
||||||
ref = self.compute_api.create(self.context,
|
ref = self.compute_api.create(
|
||||||
FLAGS.default_instance_type, None, security_group=['default'])
|
self.context,
|
||||||
|
instance_type=FLAGS.default_instance_type,
|
||||||
|
image_id=None,
|
||||||
|
security_group=['default'])
|
||||||
try:
|
try:
|
||||||
self.assertEqual(len(ref[0]['security_groups']), 1)
|
self.assertEqual(len(db.security_group_get_by_instance(
|
||||||
|
self.context, ref[0]['id'])), 1)
|
||||||
finally:
|
finally:
|
||||||
db.security_group_destroy(self.context, group['id'])
|
db.security_group_destroy(self.context, group['id'])
|
||||||
db.instance_destroy(self.context, ref[0]['id'])
|
db.instance_destroy(self.context, ref[0]['id'])
|
||||||
@@ -151,6 +155,13 @@ class ComputeTestCase(test.TestCase):
|
|||||||
self.compute.reboot_instance(self.context, instance_id)
|
self.compute.reboot_instance(self.context, instance_id)
|
||||||
self.compute.terminate_instance(self.context, instance_id)
|
self.compute.terminate_instance(self.context, instance_id)
|
||||||
|
|
||||||
|
def test_set_admin_password(self):
|
||||||
|
"""Ensure instance can have its admin password set"""
|
||||||
|
instance_id = self._create_instance()
|
||||||
|
self.compute.run_instance(self.context, instance_id)
|
||||||
|
self.compute.set_admin_password(self.context, instance_id)
|
||||||
|
self.compute.terminate_instance(self.context, instance_id)
|
||||||
|
|
||||||
def test_snapshot(self):
|
def test_snapshot(self):
|
||||||
"""Ensure instance can be snapshotted"""
|
"""Ensure instance can be snapshotted"""
|
||||||
instance_id = self._create_instance()
|
instance_id = self._create_instance()
|
||||||
|
|||||||
@@ -111,12 +111,14 @@ class ConsoleTestCase(test.TestCase):
|
|||||||
|
|
||||||
console_instances = [con['instance_id'] for con in pool.consoles]
|
console_instances = [con['instance_id'] for con in pool.consoles]
|
||||||
self.assert_(instance_id in console_instances)
|
self.assert_(instance_id in console_instances)
|
||||||
|
db.instance_destroy(self.context, instance_id)
|
||||||
|
|
||||||
def test_add_console_does_not_duplicate(self):
|
def test_add_console_does_not_duplicate(self):
|
||||||
instance_id = self._create_instance()
|
instance_id = self._create_instance()
|
||||||
cons1 = self.console.add_console(self.context, instance_id)
|
cons1 = self.console.add_console(self.context, instance_id)
|
||||||
cons2 = self.console.add_console(self.context, instance_id)
|
cons2 = self.console.add_console(self.context, instance_id)
|
||||||
self.assertEqual(cons1, cons2)
|
self.assertEqual(cons1, cons2)
|
||||||
|
db.instance_destroy(self.context, instance_id)
|
||||||
|
|
||||||
def test_remove_console(self):
|
def test_remove_console(self):
|
||||||
instance_id = self._create_instance()
|
instance_id = self._create_instance()
|
||||||
@@ -127,3 +129,4 @@ class ConsoleTestCase(test.TestCase):
|
|||||||
db.console_get,
|
db.console_get,
|
||||||
self.context,
|
self.context,
|
||||||
console_id)
|
console_id)
|
||||||
|
db.instance_destroy(self.context, instance_id)
|
||||||
|
|||||||
103
nova/tests/test_direct.py
Normal file
103
nova/tests/test_direct.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Tests for Direct API."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import webob
|
||||||
|
|
||||||
|
from nova import compute
|
||||||
|
from nova import context
|
||||||
|
from nova import exception
|
||||||
|
from nova import test
|
||||||
|
from nova import utils
|
||||||
|
from nova.api import direct
|
||||||
|
from nova.tests import test_cloud
|
||||||
|
|
||||||
|
|
||||||
|
class FakeService(object):
|
||||||
|
def echo(self, context, data):
|
||||||
|
return {'data': data}
|
||||||
|
|
||||||
|
def context(self, context):
|
||||||
|
return {'user': context.user_id,
|
||||||
|
'project': context.project_id}
|
||||||
|
|
||||||
|
|
||||||
|
class DirectTestCase(test.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(DirectTestCase, self).setUp()
|
||||||
|
direct.register_service('fake', FakeService())
|
||||||
|
self.router = direct.PostParamsMiddleware(
|
||||||
|
direct.JsonParamsMiddleware(
|
||||||
|
direct.Router()))
|
||||||
|
self.auth_router = direct.DelegatedAuthMiddleware(self.router)
|
||||||
|
self.context = context.RequestContext('user1', 'proj1')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
direct.ROUTES = {}
|
||||||
|
|
||||||
|
def test_delegated_auth(self):
|
||||||
|
req = webob.Request.blank('/fake/context')
|
||||||
|
req.headers['X-OpenStack-User'] = 'user1'
|
||||||
|
req.headers['X-OpenStack-Project'] = 'proj1'
|
||||||
|
resp = req.get_response(self.auth_router)
|
||||||
|
data = json.loads(resp.body)
|
||||||
|
self.assertEqual(data['user'], 'user1')
|
||||||
|
self.assertEqual(data['project'], 'proj1')
|
||||||
|
|
||||||
|
def test_json_params(self):
|
||||||
|
req = webob.Request.blank('/fake/echo')
|
||||||
|
req.environ['openstack.context'] = self.context
|
||||||
|
req.method = 'POST'
|
||||||
|
req.body = 'json=%s' % json.dumps({'data': 'foo'})
|
||||||
|
resp = req.get_response(self.router)
|
||||||
|
resp_parsed = json.loads(resp.body)
|
||||||
|
self.assertEqual(resp_parsed['data'], 'foo')
|
||||||
|
|
||||||
|
def test_post_params(self):
|
||||||
|
req = webob.Request.blank('/fake/echo')
|
||||||
|
req.environ['openstack.context'] = self.context
|
||||||
|
req.method = 'POST'
|
||||||
|
req.body = 'data=foo'
|
||||||
|
resp = req.get_response(self.router)
|
||||||
|
resp_parsed = json.loads(resp.body)
|
||||||
|
self.assertEqual(resp_parsed['data'], 'foo')
|
||||||
|
|
||||||
|
def test_proxy(self):
|
||||||
|
proxy = direct.Proxy(self.router)
|
||||||
|
rv = proxy.fake.echo(self.context, data='baz')
|
||||||
|
self.assertEqual(rv['data'], 'baz')
|
||||||
|
|
||||||
|
|
||||||
|
class DirectCloudTestCase(test_cloud.CloudTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(DirectCloudTestCase, self).setUp()
|
||||||
|
compute_handle = compute.API(image_service=self.cloud.image_service,
|
||||||
|
network_api=self.cloud.network_api,
|
||||||
|
volume_api=self.cloud.volume_api)
|
||||||
|
direct.register_service('compute', compute_handle)
|
||||||
|
self.router = direct.JsonParamsMiddleware(direct.Router())
|
||||||
|
proxy = direct.Proxy(self.router)
|
||||||
|
self.cloud.compute_api = proxy.compute
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(DirectCloudTestCase, self).tearDown()
|
||||||
|
direct.ROUTES = {}
|
||||||
@@ -9,7 +9,7 @@ def _fake_context():
|
|||||||
return context.RequestContext(1, 1)
|
return context.RequestContext(1, 1)
|
||||||
|
|
||||||
|
|
||||||
class RootLoggerTestCase(test.TrialTestCase):
|
class RootLoggerTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(RootLoggerTestCase, self).setUp()
|
super(RootLoggerTestCase, self).setUp()
|
||||||
self.log = log.logging.root
|
self.log = log.logging.root
|
||||||
@@ -46,7 +46,7 @@ class RootLoggerTestCase(test.TrialTestCase):
|
|||||||
self.assert_(True) # didn't raise exception
|
self.assert_(True) # didn't raise exception
|
||||||
|
|
||||||
|
|
||||||
class NovaFormatterTestCase(test.TrialTestCase):
|
class NovaFormatterTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NovaFormatterTestCase, self).setUp()
|
super(NovaFormatterTestCase, self).setUp()
|
||||||
self.flags(logging_context_format_string="HAS CONTEXT "\
|
self.flags(logging_context_format_string="HAS CONTEXT "\
|
||||||
@@ -78,7 +78,7 @@ class NovaFormatterTestCase(test.TrialTestCase):
|
|||||||
self.assertEqual("NOCTXT: baz --DBG\n", self.stream.getvalue())
|
self.assertEqual("NOCTXT: baz --DBG\n", self.stream.getvalue())
|
||||||
|
|
||||||
|
|
||||||
class NovaLoggerTestCase(test.TrialTestCase):
|
class NovaLoggerTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NovaLoggerTestCase, self).setUp()
|
super(NovaLoggerTestCase, self).setUp()
|
||||||
self.flags(default_log_levels=["nova-test=AUDIT"], verbose=False)
|
self.flags(default_log_levels=["nova-test=AUDIT"], verbose=False)
|
||||||
@@ -96,7 +96,7 @@ class NovaLoggerTestCase(test.TrialTestCase):
|
|||||||
self.assertEqual(log.AUDIT, l.level)
|
self.assertEqual(log.AUDIT, l.level)
|
||||||
|
|
||||||
|
|
||||||
class VerboseLoggerTestCase(test.TrialTestCase):
|
class VerboseLoggerTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(VerboseLoggerTestCase, self).setUp()
|
super(VerboseLoggerTestCase, self).setUp()
|
||||||
self.flags(default_log_levels=["nova.test=AUDIT"], verbose=True)
|
self.flags(default_log_levels=["nova.test=AUDIT"], verbose=True)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def conditional_forbid(req):
|
|||||||
return 'OK'
|
return 'OK'
|
||||||
|
|
||||||
|
|
||||||
class LockoutTestCase(test.TrialTestCase):
|
class LockoutTestCase(test.TestCase):
|
||||||
"""Test case for the Lockout middleware."""
|
"""Test case for the Lockout middleware."""
|
||||||
def setUp(self): # pylint: disable-msg=C0103
|
def setUp(self): # pylint: disable-msg=C0103
|
||||||
super(LockoutTestCase, self).setUp()
|
super(LockoutTestCase, self).setUp()
|
||||||
|
|||||||
@@ -96,6 +96,28 @@ class NetworkTestCase(test.TestCase):
|
|||||||
self.context.project_id = self.projects[project_num].id
|
self.context.project_id = self.projects[project_num].id
|
||||||
self.network.deallocate_fixed_ip(self.context, address)
|
self.network.deallocate_fixed_ip(self.context, address)
|
||||||
|
|
||||||
|
def test_private_ipv6(self):
|
||||||
|
"""Make sure ipv6 is OK"""
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
instance_ref = self._create_instance(0)
|
||||||
|
address = self._create_address(0, instance_ref['id'])
|
||||||
|
network_ref = db.project_get_network(
|
||||||
|
context.get_admin_context(),
|
||||||
|
self.context.project_id)
|
||||||
|
address_v6 = db.instance_get_fixed_address_v6(
|
||||||
|
context.get_admin_context(),
|
||||||
|
instance_ref['id'])
|
||||||
|
self.assertEqual(instance_ref['mac_address'],
|
||||||
|
utils.to_mac(address_v6))
|
||||||
|
instance_ref2 = db.fixed_ip_get_instance_v6(
|
||||||
|
context.get_admin_context(),
|
||||||
|
address_v6)
|
||||||
|
self.assertEqual(instance_ref['id'], instance_ref2['id'])
|
||||||
|
self.assertEqual(address_v6,
|
||||||
|
utils.to_global_ipv6(
|
||||||
|
network_ref['cidr_v6'],
|
||||||
|
instance_ref['mac_address']))
|
||||||
|
|
||||||
def test_public_network_association(self):
|
def test_public_network_association(self):
|
||||||
"""Makes sure that we can allocaate a public ip"""
|
"""Makes sure that we can allocaate a public ip"""
|
||||||
# TODO(vish): better way of adding floating ips
|
# TODO(vish): better way of adding floating ips
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from nova import test
|
|||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
class TwistdTestCase(test.TrialTestCase):
|
class TwistdTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TwistdTestCase, self).setUp()
|
super(TwistdTestCase, self).setUp()
|
||||||
self.Options = twistd.WrapTwistedOptions(twistd.TwistdServerOptions)
|
self.Options = twistd.WrapTwistedOptions(twistd.TwistdServerOptions)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from nova.compute import power_state
|
|||||||
from nova.virt import xenapi_conn
|
from nova.virt import xenapi_conn
|
||||||
from nova.virt.xenapi import fake as xenapi_fake
|
from nova.virt.xenapi import fake as xenapi_fake
|
||||||
from nova.virt.xenapi import volume_utils
|
from nova.virt.xenapi import volume_utils
|
||||||
|
from nova.virt.xenapi.vmops import SimpleDH
|
||||||
from nova.tests.db import fakes as db_fakes
|
from nova.tests.db import fakes as db_fakes
|
||||||
from nova.tests.xenapi import stubs
|
from nova.tests.xenapi import stubs
|
||||||
|
|
||||||
@@ -262,3 +263,29 @@ class XenAPIVMTestCase(test.TestCase):
|
|||||||
instance = db.instance_create(values)
|
instance = db.instance_create(values)
|
||||||
self.conn.spawn(instance)
|
self.conn.spawn(instance)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class XenAPIDiffieHellmanTestCase(test.TestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for Diffie-Hellman code
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
super(XenAPIDiffieHellmanTestCase, self).setUp()
|
||||||
|
self.alice = SimpleDH()
|
||||||
|
self.bob = SimpleDH()
|
||||||
|
|
||||||
|
def test_shared(self):
|
||||||
|
alice_pub = self.alice.get_public()
|
||||||
|
bob_pub = self.bob.get_public()
|
||||||
|
alice_shared = self.alice.compute_shared(bob_pub)
|
||||||
|
bob_shared = self.bob.compute_shared(alice_pub)
|
||||||
|
self.assertEquals(alice_shared, bob_shared)
|
||||||
|
|
||||||
|
def test_encryption(self):
|
||||||
|
msg = "This is a top-secret message"
|
||||||
|
enc = self.alice.encrypt(msg)
|
||||||
|
dec = self.bob.decrypt(enc)
|
||||||
|
self.assertEquals(dec, msg)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(XenAPIDiffieHellmanTestCase, self).tearDown()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ System-level utilities and helper functions.
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import inspect
|
import inspect
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -30,6 +31,8 @@ import struct
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from xml.sax import saxutils
|
from xml.sax import saxutils
|
||||||
|
import re
|
||||||
|
import netaddr
|
||||||
|
|
||||||
from eventlet import event
|
from eventlet import event
|
||||||
from eventlet import greenthread
|
from eventlet import greenthread
|
||||||
@@ -200,6 +203,40 @@ def last_octet(address):
|
|||||||
return int(address.split(".")[-1])
|
return int(address.split(".")[-1])
|
||||||
|
|
||||||
|
|
||||||
|
def get_my_linklocal(interface):
|
||||||
|
try:
|
||||||
|
if_str = execute("ip -f inet6 -o addr show %s" % interface)
|
||||||
|
condition = "\s+inet6\s+([0-9a-f:]+/\d+)\s+scope\s+link"
|
||||||
|
links = [re.search(condition, x) for x in if_str[0].split('\n')]
|
||||||
|
address = [w.group(1) for w in links if w is not None]
|
||||||
|
if address[0] is not None:
|
||||||
|
return address[0]
|
||||||
|
else:
|
||||||
|
return 'fe00::'
|
||||||
|
except IndexError as ex:
|
||||||
|
LOG.warn(_("Couldn't get Link Local IP of %s :%s"), interface, ex)
|
||||||
|
except ProcessExecutionError as ex:
|
||||||
|
LOG.warn(_("Couldn't get Link Local IP of %s :%s"), interface, ex)
|
||||||
|
except:
|
||||||
|
return 'fe00::'
|
||||||
|
|
||||||
|
|
||||||
|
def to_global_ipv6(prefix, mac):
|
||||||
|
mac64 = netaddr.EUI(mac).eui64().words
|
||||||
|
int_addr = int(''.join(['%02x' % i for i in mac64]), 16)
|
||||||
|
mac64_addr = netaddr.IPAddress(int_addr)
|
||||||
|
maskIP = netaddr.IPNetwork(prefix).ip
|
||||||
|
return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).format()
|
||||||
|
|
||||||
|
|
||||||
|
def to_mac(ipv6_address):
|
||||||
|
address = netaddr.IPAddress(ipv6_address)
|
||||||
|
mask1 = netaddr.IPAddress("::ffff:ffff:ffff:ffff")
|
||||||
|
mask2 = netaddr.IPAddress("::0200:0:0:0")
|
||||||
|
mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words
|
||||||
|
return ":".join(["%02x" % i for i in mac64[0:3] + mac64[5:8]])
|
||||||
|
|
||||||
|
|
||||||
def utcnow():
|
def utcnow():
|
||||||
"""Overridable version of datetime.datetime.utcnow."""
|
"""Overridable version of datetime.datetime.utcnow."""
|
||||||
if utcnow.override_time:
|
if utcnow.override_time:
|
||||||
@@ -372,3 +409,36 @@ def utf8(value):
|
|||||||
return value.encode("utf-8")
|
return value.encode("utf-8")
|
||||||
assert isinstance(value, str)
|
assert isinstance(value, str)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def to_primitive(value):
|
||||||
|
if type(value) is type([]) or type(value) is type((None,)):
|
||||||
|
o = []
|
||||||
|
for v in value:
|
||||||
|
o.append(to_primitive(v))
|
||||||
|
return o
|
||||||
|
elif type(value) is type({}):
|
||||||
|
o = {}
|
||||||
|
for k, v in value.iteritems():
|
||||||
|
o[k] = to_primitive(v)
|
||||||
|
return o
|
||||||
|
elif isinstance(value, datetime.datetime):
|
||||||
|
return str(value)
|
||||||
|
elif hasattr(value, 'iteritems'):
|
||||||
|
return to_primitive(dict(value.iteritems()))
|
||||||
|
elif hasattr(value, '__iter__'):
|
||||||
|
return to_primitive(list(value))
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def dumps(value):
|
||||||
|
try:
|
||||||
|
return json.dumps(value)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
return json.dumps(to_primitive(value))
|
||||||
|
|
||||||
|
|
||||||
|
def loads(s):
|
||||||
|
return json.loads(s)
|
||||||
|
|||||||
186
nova/virt/disk.py
Normal file
186
nova/virt/disk.py
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
Utility methods to resize, repartition, and modify disk images.
|
||||||
|
|
||||||
|
Includes injection of SSH PGP keys into authorized_keys file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
|
||||||
|
from nova import exception
|
||||||
|
from nova import flags
|
||||||
|
from nova import log as logging
|
||||||
|
from nova import utils
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger('nova.compute.disk')
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10,
|
||||||
|
'minimum size in bytes of root partition')
|
||||||
|
flags.DEFINE_integer('block_size', 1024 * 1024 * 256,
|
||||||
|
'block_size to use for dd')
|
||||||
|
|
||||||
|
|
||||||
|
def extend(image, size):
|
||||||
|
"""Increase image to size"""
|
||||||
|
file_size = os.path.getsize(image)
|
||||||
|
if file_size >= size:
|
||||||
|
return
|
||||||
|
utils.execute('truncate -s %s %s' % (size, image))
|
||||||
|
# NOTE(vish): attempts to resize filesystem
|
||||||
|
utils.execute('e2fsck -fp %s' % image, check_exit_code=False)
|
||||||
|
utils.execute('resize2fs %s' % image, check_exit_code=False)
|
||||||
|
|
||||||
|
|
||||||
|
def inject_data(image, key=None, net=None, partition=None, nbd=False):
|
||||||
|
"""Injects a ssh key and optionally net data into a disk image.
|
||||||
|
|
||||||
|
it will mount the image as a fully partitioned disk and attempt to inject
|
||||||
|
into the specified partition number.
|
||||||
|
|
||||||
|
If partition is not specified it mounts the image as a single partition.
|
||||||
|
|
||||||
|
"""
|
||||||
|
device = _link_device(image, nbd)
|
||||||
|
try:
|
||||||
|
if not partition is None:
|
||||||
|
# create partition
|
||||||
|
out, err = utils.execute('sudo kpartx -a %s' % device)
|
||||||
|
if err:
|
||||||
|
raise exception.Error(_('Failed to load partition: %s') % err)
|
||||||
|
mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1],
|
||||||
|
partition)
|
||||||
|
else:
|
||||||
|
mapped_device = device
|
||||||
|
|
||||||
|
# We can only loopback mount raw images. If the device isn't there,
|
||||||
|
# it's normally because it's a .vmdk or a .vdi etc
|
||||||
|
if not os.path.exists(mapped_device):
|
||||||
|
raise exception.Error('Mapped device was not found (we can'
|
||||||
|
' only inject raw disk images): %s' %
|
||||||
|
mapped_device)
|
||||||
|
|
||||||
|
# Configure ext2fs so that it doesn't auto-check every N boots
|
||||||
|
out, err = utils.execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)
|
||||||
|
|
||||||
|
tmpdir = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
# mount loopback to dir
|
||||||
|
out, err = utils.execute(
|
||||||
|
'sudo mount %s %s' % (mapped_device, tmpdir))
|
||||||
|
if err:
|
||||||
|
raise exception.Error(_('Failed to mount filesystem: %s')
|
||||||
|
% err)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if key:
|
||||||
|
# inject key file
|
||||||
|
_inject_key_into_fs(key, tmpdir)
|
||||||
|
if net:
|
||||||
|
_inject_net_into_fs(net, tmpdir)
|
||||||
|
finally:
|
||||||
|
# unmount device
|
||||||
|
utils.execute('sudo umount %s' % mapped_device)
|
||||||
|
finally:
|
||||||
|
# remove temporary directory
|
||||||
|
utils.execute('rmdir %s' % tmpdir)
|
||||||
|
if not partition is None:
|
||||||
|
# remove partitions
|
||||||
|
utils.execute('sudo kpartx -d %s' % device)
|
||||||
|
finally:
|
||||||
|
_unlink_device(device, nbd)
|
||||||
|
|
||||||
|
|
||||||
|
def _link_device(image, nbd):
|
||||||
|
"""Link image to device using loopback or nbd"""
|
||||||
|
if nbd:
|
||||||
|
device = _allocate_device()
|
||||||
|
utils.execute('sudo qemu-nbd -c %s %s' % (device, image))
|
||||||
|
# NOTE(vish): this forks into another process, so give it a chance
|
||||||
|
# to set up before continuuing
|
||||||
|
for i in xrange(10):
|
||||||
|
if os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
|
||||||
|
return device
|
||||||
|
time.sleep(1)
|
||||||
|
raise exception.Error(_('nbd device %s did not show up') % device)
|
||||||
|
else:
|
||||||
|
out, err = utils.execute('sudo losetup --find --show %s' % image)
|
||||||
|
if err:
|
||||||
|
raise exception.Error(_('Could not attach image to loopback: %s')
|
||||||
|
% err)
|
||||||
|
return out.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _unlink_device(device, nbd):
|
||||||
|
"""Unlink image from device using loopback or nbd"""
|
||||||
|
if nbd:
|
||||||
|
utils.execute('sudo qemu-nbd -d %s' % device)
|
||||||
|
_free_device(device)
|
||||||
|
else:
|
||||||
|
utils.execute('sudo losetup --detach %s' % device)
|
||||||
|
|
||||||
|
|
||||||
|
_DEVICES = ['/dev/nbd%s' % i for i in xrange(16)]
|
||||||
|
|
||||||
|
|
||||||
|
def _allocate_device():
|
||||||
|
# NOTE(vish): This assumes no other processes are allocating nbd devices.
|
||||||
|
# It may race cause a race condition if multiple
|
||||||
|
# workers are running on a given machine.
|
||||||
|
while True:
|
||||||
|
if not _DEVICES:
|
||||||
|
raise exception.Error(_('No free nbd devices'))
|
||||||
|
device = _DEVICES.pop()
|
||||||
|
if not os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
|
||||||
|
break
|
||||||
|
return device
|
||||||
|
|
||||||
|
|
||||||
|
def _free_device(device):
|
||||||
|
_DEVICES.append(device)
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_key_into_fs(key, fs):
|
||||||
|
"""Add the given public ssh key to root's authorized_keys.
|
||||||
|
|
||||||
|
key is an ssh key string.
|
||||||
|
fs is the path to the base of the filesystem into which to inject the key.
|
||||||
|
"""
|
||||||
|
sshdir = os.path.join(fs, 'root', '.ssh')
|
||||||
|
utils.execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter
|
||||||
|
utils.execute('sudo chown root %s' % sshdir)
|
||||||
|
utils.execute('sudo chmod 700 %s' % sshdir)
|
||||||
|
keyfile = os.path.join(sshdir, 'authorized_keys')
|
||||||
|
utils.execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_net_into_fs(net, fs):
|
||||||
|
"""Inject /etc/network/interfaces into the filesystem rooted at fs.
|
||||||
|
|
||||||
|
net is the contents of /etc/network/interfaces.
|
||||||
|
"""
|
||||||
|
netdir = os.path.join(os.path.join(fs, 'etc'), 'network')
|
||||||
|
utils.execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter
|
||||||
|
utils.execute('sudo chown root:root %s' % netdir)
|
||||||
|
utils.execute('sudo chmod 755 %s' % netdir)
|
||||||
|
netfile = os.path.join(netdir, 'interfaces')
|
||||||
|
utils.execute('sudo tee %s' % netfile, net)
|
||||||
@@ -98,7 +98,7 @@ class FakeConnection(object):
|
|||||||
the new instance.
|
the new instance.
|
||||||
|
|
||||||
The work will be done asynchronously. This function returns a
|
The work will be done asynchronously. This function returns a
|
||||||
Deferred that allows the caller to detect when it is complete.
|
task that allows the caller to detect when it is complete.
|
||||||
|
|
||||||
Once this successfully completes, the instance should be
|
Once this successfully completes, the instance should be
|
||||||
running (power_state.RUNNING).
|
running (power_state.RUNNING).
|
||||||
@@ -122,7 +122,7 @@ class FakeConnection(object):
|
|||||||
The second parameter is the name of the snapshot.
|
The second parameter is the name of the snapshot.
|
||||||
|
|
||||||
The work will be done asynchronously. This function returns a
|
The work will be done asynchronously. This function returns a
|
||||||
Deferred that allows the caller to detect when it is complete.
|
task that allows the caller to detect when it is complete.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -134,7 +134,20 @@ class FakeConnection(object):
|
|||||||
and so the instance is being specified as instance.name.
|
and so the instance is being specified as instance.name.
|
||||||
|
|
||||||
The work will be done asynchronously. This function returns a
|
The work will be done asynchronously. This function returns a
|
||||||
Deferred that allows the caller to detect when it is complete.
|
task that allows the caller to detect when it is complete.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_admin_password(self, instance, new_pass):
|
||||||
|
"""
|
||||||
|
Set the root password on the specified instance.
|
||||||
|
|
||||||
|
The first parameter is an instance of nova.compute.service.Instance,
|
||||||
|
and so the instance is being specified as instance.name. The second
|
||||||
|
parameter is the value of the new password.
|
||||||
|
|
||||||
|
The work will be done asynchronously. This function returns a
|
||||||
|
task that allows the caller to detect when it is complete.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -182,7 +195,7 @@ class FakeConnection(object):
|
|||||||
and so the instance is being specified as instance.name.
|
and so the instance is being specified as instance.name.
|
||||||
|
|
||||||
The work will be done asynchronously. This function returns a
|
The work will be done asynchronously. This function returns a
|
||||||
Deferred that allows the caller to detect when it is complete.
|
task that allows the caller to detect when it is complete.
|
||||||
"""
|
"""
|
||||||
del self.instances[instance.name]
|
del self.instances[instance.name]
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
#set $disk_bus = 'uml'
|
#set $disk_bus = 'uml'
|
||||||
<type>uml</type>
|
<type>uml</type>
|
||||||
<kernel>/usr/bin/linux</kernel>
|
<kernel>/usr/bin/linux</kernel>
|
||||||
<root>/dev/ubda1</root>
|
<root>/dev/ubda</root>
|
||||||
#else
|
#else
|
||||||
#if $type == 'xen'
|
#if $type == 'xen'
|
||||||
#set $disk_prefix = 'sd'
|
#set $disk_prefix = 'sd'
|
||||||
#set $disk_bus = 'scsi'
|
#set $disk_bus = 'scsi'
|
||||||
<type>linux</type>
|
<type>linux</type>
|
||||||
<root>/dev/xvda1</root>
|
<root>/dev/xvda</root>
|
||||||
#else
|
#else
|
||||||
#set $disk_prefix = 'vd'
|
#set $disk_prefix = 'vd'
|
||||||
#set $disk_bus = 'virtio'
|
#set $disk_bus = 'virtio'
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
#if $type == 'xen'
|
#if $type == 'xen'
|
||||||
<cmdline>ro</cmdline>
|
<cmdline>ro</cmdline>
|
||||||
#else
|
#else
|
||||||
<cmdline>root=/dev/vda1 console=ttyS0</cmdline>
|
<cmdline>root=/dev/vda console=ttyS0</cmdline>
|
||||||
#end if
|
#end if
|
||||||
#if $getVar('ramdisk', None)
|
#if $getVar('ramdisk', None)
|
||||||
<initrd>${ramdisk}</initrd>
|
<initrd>${ramdisk}</initrd>
|
||||||
@@ -46,18 +46,28 @@
|
|||||||
<devices>
|
<devices>
|
||||||
#if $getVar('rescue', False)
|
#if $getVar('rescue', False)
|
||||||
<disk type='file'>
|
<disk type='file'>
|
||||||
|
<driver type='${driver_type}'/>
|
||||||
<source file='${basepath}/rescue-disk'/>
|
<source file='${basepath}/rescue-disk'/>
|
||||||
<target dev='${disk_prefix}a' bus='${disk_bus}'/>
|
<target dev='${disk_prefix}a' bus='${disk_bus}'/>
|
||||||
</disk>
|
</disk>
|
||||||
<disk type='file'>
|
<disk type='file'>
|
||||||
|
<driver type='${driver_type}'/>
|
||||||
<source file='${basepath}/disk'/>
|
<source file='${basepath}/disk'/>
|
||||||
<target dev='${disk_prefix}b' bus='${disk_bus}'/>
|
<target dev='${disk_prefix}b' bus='${disk_bus}'/>
|
||||||
</disk>
|
</disk>
|
||||||
#else
|
#else
|
||||||
<disk type='file'>
|
<disk type='file'>
|
||||||
|
<driver type='${driver_type}'/>
|
||||||
<source file='${basepath}/disk'/>
|
<source file='${basepath}/disk'/>
|
||||||
<target dev='${disk_prefix}a' bus='${disk_bus}'/>
|
<target dev='${disk_prefix}a' bus='${disk_bus}'/>
|
||||||
</disk>
|
</disk>
|
||||||
|
#if $getVar('local', False)
|
||||||
|
<disk type='file'>
|
||||||
|
<driver type='${driver_type}'/>
|
||||||
|
<source file='${basepath}/local'/>
|
||||||
|
<target dev='${disk_prefix}b' bus='${disk_bus}'/>
|
||||||
|
</disk>
|
||||||
|
#end if
|
||||||
#end if
|
#end if
|
||||||
<interface type='bridge'>
|
<interface type='bridge'>
|
||||||
<source bridge='${bridge_name}'/>
|
<source bridge='${bridge_name}'/>
|
||||||
@@ -66,6 +76,7 @@
|
|||||||
<filterref filter="nova-instance-${name}">
|
<filterref filter="nova-instance-${name}">
|
||||||
<parameter name="IP" value="${ip_address}" />
|
<parameter name="IP" value="${ip_address}" />
|
||||||
<parameter name="DHCPSERVER" value="${dhcp_server}" />
|
<parameter name="DHCPSERVER" value="${dhcp_server}" />
|
||||||
|
<parameter name="RASERVER" value="${ra_server}" />
|
||||||
#if $getVar('extra_params', False)
|
#if $getVar('extra_params', False)
|
||||||
${extra_params}
|
${extra_params}
|
||||||
#end if
|
#end if
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ from nova import log as logging
|
|||||||
from nova import utils
|
from nova import utils
|
||||||
#from nova.api import context
|
#from nova.api import context
|
||||||
from nova.auth import manager
|
from nova.auth import manager
|
||||||
from nova.compute import disk
|
|
||||||
from nova.compute import instance_types
|
from nova.compute import instance_types
|
||||||
from nova.compute import power_state
|
from nova.compute import power_state
|
||||||
|
from nova.virt import disk
|
||||||
from nova.virt import images
|
from nova.virt import images
|
||||||
|
|
||||||
libvirt = None
|
libvirt = None
|
||||||
@@ -91,6 +91,9 @@ flags.DEFINE_string('libvirt_uri',
|
|||||||
flags.DEFINE_bool('allow_project_net_traffic',
|
flags.DEFINE_bool('allow_project_net_traffic',
|
||||||
True,
|
True,
|
||||||
'Whether to allow in project network traffic')
|
'Whether to allow in project network traffic')
|
||||||
|
flags.DEFINE_bool('use_cow_images',
|
||||||
|
True,
|
||||||
|
'Whether to use cow images')
|
||||||
flags.DEFINE_string('ajaxterm_portrange',
|
flags.DEFINE_string('ajaxterm_portrange',
|
||||||
'10000-12000',
|
'10000-12000',
|
||||||
'Range of ports that ajaxterm should randomly try to bind')
|
'Range of ports that ajaxterm should randomly try to bind')
|
||||||
@@ -127,6 +130,16 @@ def _get_net_and_mask(cidr):
|
|||||||
return str(net.net()), str(net.netmask())
|
return str(net.net()), str(net.netmask())
|
||||||
|
|
||||||
|
|
||||||
|
def _get_net_and_prefixlen(cidr):
|
||||||
|
net = IPy.IP(cidr)
|
||||||
|
return str(net.net()), str(net.prefixlen())
|
||||||
|
|
||||||
|
|
||||||
|
def _get_ip_version(cidr):
|
||||||
|
net = IPy.IP(cidr)
|
||||||
|
return int(net.version())
|
||||||
|
|
||||||
|
|
||||||
class LibvirtConnection(object):
|
class LibvirtConnection(object):
|
||||||
|
|
||||||
def __init__(self, read_only):
|
def __init__(self, read_only):
|
||||||
@@ -372,7 +385,6 @@ class LibvirtConnection(object):
|
|||||||
instance['id'],
|
instance['id'],
|
||||||
power_state.NOSTATE,
|
power_state.NOSTATE,
|
||||||
'launching')
|
'launching')
|
||||||
|
|
||||||
self.nwfilter.setup_basic_filtering(instance)
|
self.nwfilter.setup_basic_filtering(instance)
|
||||||
self.firewall_driver.prepare_instance_filter(instance)
|
self.firewall_driver.prepare_instance_filter(instance)
|
||||||
self._create_image(instance, xml)
|
self._create_image(instance, xml)
|
||||||
@@ -480,19 +492,57 @@ class LibvirtConnection(object):
|
|||||||
subprocess.Popen(cmd, shell=True)
|
subprocess.Popen(cmd, shell=True)
|
||||||
return {'token': token, 'host': host, 'port': port}
|
return {'token': token, 'host': host, 'port': port}
|
||||||
|
|
||||||
|
def _cache_image(self, fn, target, fname, cow=False, *args, **kwargs):
|
||||||
|
"""Wrapper for a method that creates an image that caches the image.
|
||||||
|
|
||||||
|
This wrapper will save the image into a common store and create a
|
||||||
|
copy for use by the hypervisor.
|
||||||
|
|
||||||
|
The underlying method should specify a kwarg of target representing
|
||||||
|
where the image will be saved.
|
||||||
|
|
||||||
|
fname is used as the filename of the base image. The filename needs
|
||||||
|
to be unique to a given image.
|
||||||
|
|
||||||
|
If cow is True, it will make a CoW image instead of a copy.
|
||||||
|
"""
|
||||||
|
if not os.path.exists(target):
|
||||||
|
base_dir = os.path.join(FLAGS.instances_path, '_base')
|
||||||
|
if not os.path.exists(base_dir):
|
||||||
|
os.mkdir(base_dir)
|
||||||
|
os.chmod(base_dir, 0777)
|
||||||
|
base = os.path.join(base_dir, fname)
|
||||||
|
if not os.path.exists(base):
|
||||||
|
fn(target=base, *args, **kwargs)
|
||||||
|
if cow:
|
||||||
|
utils.execute('qemu-img create -f qcow2 -o '
|
||||||
|
'cluster_size=2M,backing_file=%s %s'
|
||||||
|
% (base, target))
|
||||||
|
else:
|
||||||
|
utils.execute('cp %s %s' % (base, target))
|
||||||
|
|
||||||
|
def _fetch_image(self, target, image_id, user, project, size=None):
|
||||||
|
"""Grab image and optionally attempt to resize it"""
|
||||||
|
images.fetch(image_id, target, user, project)
|
||||||
|
if size:
|
||||||
|
disk.extend(target, size)
|
||||||
|
|
||||||
|
def _create_local(self, target, local_gb):
|
||||||
|
"""Create a blank image of specified size"""
|
||||||
|
utils.execute('truncate %s -s %dG' % (target, local_gb))
|
||||||
|
# TODO(vish): should we format disk by default?
|
||||||
|
|
||||||
def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None):
|
def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None):
|
||||||
# syntactic nicety
|
# syntactic nicety
|
||||||
basepath = lambda fname = '', prefix = prefix: os.path.join(
|
def basepath(fname='', prefix=prefix):
|
||||||
FLAGS.instances_path,
|
return os.path.join(FLAGS.instances_path,
|
||||||
inst['name'],
|
inst['name'],
|
||||||
prefix + fname)
|
prefix + fname)
|
||||||
|
|
||||||
# ensure directories exist and are writable
|
# ensure directories exist and are writable
|
||||||
utils.execute('mkdir -p %s' % basepath(prefix=''))
|
utils.execute('mkdir -p %s' % basepath(prefix=''))
|
||||||
utils.execute('chmod 0777 %s' % basepath(prefix=''))
|
utils.execute('chmod 0777 %s' % basepath(prefix=''))
|
||||||
|
|
||||||
# TODO(termie): these are blocking calls, it would be great
|
|
||||||
# if they weren't.
|
|
||||||
LOG.info(_('instance %s: Creating image'), inst['name'])
|
LOG.info(_('instance %s: Creating image'), inst['name'])
|
||||||
f = open(basepath('libvirt.xml'), 'w')
|
f = open(basepath('libvirt.xml'), 'w')
|
||||||
f.write(libvirt_xml)
|
f.write(libvirt_xml)
|
||||||
@@ -509,23 +559,44 @@ class LibvirtConnection(object):
|
|||||||
disk_images = {'image_id': inst['image_id'],
|
disk_images = {'image_id': inst['image_id'],
|
||||||
'kernel_id': inst['kernel_id'],
|
'kernel_id': inst['kernel_id'],
|
||||||
'ramdisk_id': inst['ramdisk_id']}
|
'ramdisk_id': inst['ramdisk_id']}
|
||||||
if not os.path.exists(basepath('disk')):
|
|
||||||
images.fetch(inst.image_id, basepath('disk-raw'), user,
|
|
||||||
project)
|
|
||||||
|
|
||||||
if inst['kernel_id']:
|
if disk_images['kernel_id']:
|
||||||
if not os.path.exists(basepath('kernel')):
|
self._cache_image(fn=self._fetch_image,
|
||||||
images.fetch(inst['kernel_id'], basepath('kernel'),
|
target=basepath('kernel'),
|
||||||
user, project)
|
fname=disk_images['kernel_id'],
|
||||||
if inst['ramdisk_id']:
|
image_id=disk_images['kernel_id'],
|
||||||
if not os.path.exists(basepath('ramdisk')):
|
user=user,
|
||||||
images.fetch(inst['ramdisk_id'], basepath('ramdisk'),
|
project=project)
|
||||||
user, project)
|
if disk_images['ramdisk_id']:
|
||||||
|
self._cache_image(fn=self._fetch_image,
|
||||||
|
target=basepath('ramdisk'),
|
||||||
|
fname=disk_images['ramdisk_id'],
|
||||||
|
image_id=disk_images['ramdisk_id'],
|
||||||
|
user=user,
|
||||||
|
project=project)
|
||||||
|
|
||||||
def execute(cmd, process_input=None, check_exit_code=True):
|
root_fname = disk_images['image_id']
|
||||||
return utils.execute(cmd=cmd,
|
size = FLAGS.minimum_root_size
|
||||||
process_input=process_input,
|
if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
|
||||||
check_exit_code=check_exit_code)
|
size = None
|
||||||
|
root_fname += "_sm"
|
||||||
|
|
||||||
|
self._cache_image(fn=self._fetch_image,
|
||||||
|
target=basepath('disk'),
|
||||||
|
fname=root_fname,
|
||||||
|
cow=FLAGS.use_cow_images,
|
||||||
|
image_id=disk_images['image_id'],
|
||||||
|
user=user,
|
||||||
|
project=project,
|
||||||
|
size=size)
|
||||||
|
type_data = instance_types.INSTANCE_TYPES[inst['instance_type']]
|
||||||
|
|
||||||
|
if type_data['local_gb']:
|
||||||
|
self._cache_image(fn=self._create_local,
|
||||||
|
target=basepath('local'),
|
||||||
|
fname="local_%s" % type_data['local_gb'],
|
||||||
|
cow=FLAGS.use_cow_images,
|
||||||
|
local_gb=type_data['local_gb'])
|
||||||
|
|
||||||
# For now, we assume that if we're not using a kernel, we're using a
|
# For now, we assume that if we're not using a kernel, we're using a
|
||||||
# partitioned disk image where the target partition is the first
|
# partitioned disk image where the target partition is the first
|
||||||
@@ -541,12 +612,16 @@ class LibvirtConnection(object):
|
|||||||
if network_ref['injected']:
|
if network_ref['injected']:
|
||||||
admin_context = context.get_admin_context()
|
admin_context = context.get_admin_context()
|
||||||
address = db.instance_get_fixed_address(admin_context, inst['id'])
|
address = db.instance_get_fixed_address(admin_context, inst['id'])
|
||||||
|
ra_server = network_ref['ra_server']
|
||||||
|
if not ra_server:
|
||||||
|
ra_server = "fd00::"
|
||||||
with open(FLAGS.injected_network_template) as f:
|
with open(FLAGS.injected_network_template) as f:
|
||||||
net = f.read() % {'address': address,
|
net = f.read() % {'address': address,
|
||||||
'netmask': network_ref['netmask'],
|
'netmask': network_ref['netmask'],
|
||||||
'gateway': network_ref['gateway'],
|
'gateway': network_ref['gateway'],
|
||||||
'broadcast': network_ref['broadcast'],
|
'broadcast': network_ref['broadcast'],
|
||||||
'dns': network_ref['dns']}
|
'dns': network_ref['dns'],
|
||||||
|
'ra_server': ra_server}
|
||||||
if key or net:
|
if key or net:
|
||||||
if key:
|
if key:
|
||||||
LOG.info(_('instance %s: injecting key into image %s'),
|
LOG.info(_('instance %s: injecting key into image %s'),
|
||||||
@@ -555,34 +630,15 @@ class LibvirtConnection(object):
|
|||||||
LOG.info(_('instance %s: injecting net into image %s'),
|
LOG.info(_('instance %s: injecting net into image %s'),
|
||||||
inst['name'], inst.image_id)
|
inst['name'], inst.image_id)
|
||||||
try:
|
try:
|
||||||
disk.inject_data(basepath('disk-raw'), key, net,
|
disk.inject_data(basepath('disk'), key, net,
|
||||||
partition=target_partition,
|
partition=target_partition,
|
||||||
execute=execute)
|
nbd=FLAGS.use_cow_images)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# This could be a windows image, or a vmdk format disk
|
# This could be a windows image, or a vmdk format disk
|
||||||
LOG.warn(_('instance %s: ignoring error injecting data'
|
LOG.warn(_('instance %s: ignoring error injecting data'
|
||||||
' into image %s (%s)'),
|
' into image %s (%s)'),
|
||||||
inst['name'], inst.image_id, e)
|
inst['name'], inst.image_id, e)
|
||||||
|
|
||||||
if inst['kernel_id']:
|
|
||||||
if os.path.exists(basepath('disk')):
|
|
||||||
utils.execute('rm -f %s' % basepath('disk'))
|
|
||||||
|
|
||||||
local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type]
|
|
||||||
['local_gb']
|
|
||||||
* 1024 * 1024 * 1024)
|
|
||||||
|
|
||||||
resize = True
|
|
||||||
if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
|
|
||||||
resize = False
|
|
||||||
|
|
||||||
if inst['kernel_id']:
|
|
||||||
disk.partition(basepath('disk-raw'), basepath('disk'),
|
|
||||||
local_bytes, resize, execute=execute)
|
|
||||||
else:
|
|
||||||
os.rename(basepath('disk-raw'), basepath('disk'))
|
|
||||||
disk.extend(basepath('disk'), local_bytes, execute=execute)
|
|
||||||
|
|
||||||
if FLAGS.libvirt_type == 'uml':
|
if FLAGS.libvirt_type == 'uml':
|
||||||
utils.execute('sudo chown root %s' % basepath('disk'))
|
utils.execute('sudo chown root %s' % basepath('disk'))
|
||||||
|
|
||||||
@@ -601,15 +657,36 @@ class LibvirtConnection(object):
|
|||||||
instance['id'])
|
instance['id'])
|
||||||
# Assume that the gateway also acts as the dhcp server.
|
# Assume that the gateway also acts as the dhcp server.
|
||||||
dhcp_server = network['gateway']
|
dhcp_server = network['gateway']
|
||||||
|
ra_server = network['ra_server']
|
||||||
|
if not ra_server:
|
||||||
|
ra_server = 'fd00::'
|
||||||
if FLAGS.allow_project_net_traffic:
|
if FLAGS.allow_project_net_traffic:
|
||||||
net, mask = _get_net_and_mask(network['cidr'])
|
if FLAGS.use_ipv6:
|
||||||
extra_params = ("<parameter name=\"PROJNET\" "
|
net, mask = _get_net_and_mask(network['cidr'])
|
||||||
|
net_v6, prefixlen_v6 = _get_net_and_prefixlen(
|
||||||
|
network['cidr_v6'])
|
||||||
|
extra_params = ("<parameter name=\"PROJNET\" "
|
||||||
"value=\"%s\" />\n"
|
"value=\"%s\" />\n"
|
||||||
"<parameter name=\"PROJMASK\" "
|
"<parameter name=\"PROJMASK\" "
|
||||||
"value=\"%s\" />\n") % (net, mask)
|
"value=\"%s\" />\n"
|
||||||
|
"<parameter name=\"PROJNETV6\" "
|
||||||
|
"value=\"%s\" />\n"
|
||||||
|
"<parameter name=\"PROJMASKV6\" "
|
||||||
|
"value=\"%s\" />\n") % \
|
||||||
|
(net, mask, net_v6, prefixlen_v6)
|
||||||
|
else:
|
||||||
|
net, mask = _get_net_and_mask(network['cidr'])
|
||||||
|
extra_params = ("<parameter name=\"PROJNET\" "
|
||||||
|
"value=\"%s\" />\n"
|
||||||
|
"<parameter name=\"PROJMASK\" "
|
||||||
|
"value=\"%s\" />\n") % \
|
||||||
|
(net, mask)
|
||||||
else:
|
else:
|
||||||
extra_params = "\n"
|
extra_params = "\n"
|
||||||
|
if FLAGS.use_cow_images:
|
||||||
|
driver_type = 'qcow2'
|
||||||
|
else:
|
||||||
|
driver_type = 'raw'
|
||||||
|
|
||||||
xml_info = {'type': FLAGS.libvirt_type,
|
xml_info = {'type': FLAGS.libvirt_type,
|
||||||
'name': instance['name'],
|
'name': instance['name'],
|
||||||
@@ -621,8 +698,11 @@ class LibvirtConnection(object):
|
|||||||
'mac_address': instance['mac_address'],
|
'mac_address': instance['mac_address'],
|
||||||
'ip_address': ip_address,
|
'ip_address': ip_address,
|
||||||
'dhcp_server': dhcp_server,
|
'dhcp_server': dhcp_server,
|
||||||
|
'ra_server': ra_server,
|
||||||
'extra_params': extra_params,
|
'extra_params': extra_params,
|
||||||
'rescue': rescue}
|
'rescue': rescue,
|
||||||
|
'local': instance_type['local_gb'],
|
||||||
|
'driver_type': driver_type}
|
||||||
if not rescue:
|
if not rescue:
|
||||||
if instance['kernel_id']:
|
if instance['kernel_id']:
|
||||||
xml_info['kernel'] = xml_info['basepath'] + "/kernel"
|
xml_info['kernel'] = xml_info['basepath'] + "/kernel"
|
||||||
@@ -882,6 +962,15 @@ class NWFilterFirewall(FirewallDriver):
|
|||||||
</rule>
|
</rule>
|
||||||
</filter>'''
|
</filter>'''
|
||||||
|
|
||||||
|
def nova_ra_filter(self):
|
||||||
|
return '''<filter name='nova-allow-ra-server' chain='root'>
|
||||||
|
<uuid>d707fa71-4fb5-4b27-9ab7-ba5ca19c8804</uuid>
|
||||||
|
<rule action='accept' direction='inout'
|
||||||
|
priority='100'>
|
||||||
|
<icmpv6 srcipaddr='$RASERVER'/>
|
||||||
|
</rule>
|
||||||
|
</filter>'''
|
||||||
|
|
||||||
def setup_basic_filtering(self, instance):
|
def setup_basic_filtering(self, instance):
|
||||||
"""Set up basic filtering (MAC, IP, and ARP spoofing protection)"""
|
"""Set up basic filtering (MAC, IP, and ARP spoofing protection)"""
|
||||||
logging.info('called setup_basic_filtering in nwfilter')
|
logging.info('called setup_basic_filtering in nwfilter')
|
||||||
@@ -910,9 +999,12 @@ class NWFilterFirewall(FirewallDriver):
|
|||||||
self._define_filter(self.nova_base_ipv4_filter)
|
self._define_filter(self.nova_base_ipv4_filter)
|
||||||
self._define_filter(self.nova_base_ipv6_filter)
|
self._define_filter(self.nova_base_ipv6_filter)
|
||||||
self._define_filter(self.nova_dhcp_filter)
|
self._define_filter(self.nova_dhcp_filter)
|
||||||
|
self._define_filter(self.nova_ra_filter)
|
||||||
self._define_filter(self.nova_vpn_filter)
|
self._define_filter(self.nova_vpn_filter)
|
||||||
if FLAGS.allow_project_net_traffic:
|
if FLAGS.allow_project_net_traffic:
|
||||||
self._define_filter(self.nova_project_filter)
|
self._define_filter(self.nova_project_filter)
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
self._define_filter(self.nova_project_filter_v6)
|
||||||
|
|
||||||
self.static_filters_configured = True
|
self.static_filters_configured = True
|
||||||
|
|
||||||
@@ -944,13 +1036,13 @@ class NWFilterFirewall(FirewallDriver):
|
|||||||
|
|
||||||
def nova_base_ipv6_filter(self):
|
def nova_base_ipv6_filter(self):
|
||||||
retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
|
retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
|
||||||
for protocol in ['tcp', 'udp', 'icmp']:
|
for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
|
||||||
for direction, action, priority in [('out', 'accept', 399),
|
for direction, action, priority in [('out', 'accept', 399),
|
||||||
('in', 'drop', 400)]:
|
('in', 'drop', 400)]:
|
||||||
retval += """<rule action='%s' direction='%s' priority='%d'>
|
retval += """<rule action='%s' direction='%s' priority='%d'>
|
||||||
<%s-ipv6 />
|
<%s />
|
||||||
</rule>""" % (action, direction,
|
</rule>""" % (action, direction,
|
||||||
priority, protocol)
|
priority, protocol)
|
||||||
retval += '</filter>'
|
retval += '</filter>'
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
@@ -963,10 +1055,20 @@ class NWFilterFirewall(FirewallDriver):
|
|||||||
retval += '</filter>'
|
retval += '</filter>'
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
def nova_project_filter_v6(self):
|
||||||
|
retval = "<filter name='nova-project-v6' chain='ipv6'>"
|
||||||
|
for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
|
||||||
|
retval += """<rule action='accept' direction='inout'
|
||||||
|
priority='200'>
|
||||||
|
<%s srcipaddr='$PROJNETV6'
|
||||||
|
srcipmask='$PROJMASKV6' />
|
||||||
|
</rule>""" % (protocol)
|
||||||
|
retval += '</filter>'
|
||||||
|
return retval
|
||||||
|
|
||||||
def _define_filter(self, xml):
|
def _define_filter(self, xml):
|
||||||
if callable(xml):
|
if callable(xml):
|
||||||
xml = xml()
|
xml = xml()
|
||||||
|
|
||||||
# execute in a native thread and block current greenthread until done
|
# execute in a native thread and block current greenthread until done
|
||||||
tpool.execute(self._conn.nwfilterDefineXML, xml)
|
tpool.execute(self._conn.nwfilterDefineXML, xml)
|
||||||
|
|
||||||
@@ -980,7 +1082,6 @@ class NWFilterFirewall(FirewallDriver):
|
|||||||
it makes sure the filters for the security groups as well as
|
it makes sure the filters for the security groups as well as
|
||||||
the base filter are all in place.
|
the base filter are all in place.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if instance['image_id'] == FLAGS.vpn_image_id:
|
if instance['image_id'] == FLAGS.vpn_image_id:
|
||||||
base_filter = 'nova-vpn'
|
base_filter = 'nova-vpn'
|
||||||
else:
|
else:
|
||||||
@@ -992,11 +1093,15 @@ class NWFilterFirewall(FirewallDriver):
|
|||||||
instance_secgroup_filter_children = ['nova-base-ipv4',
|
instance_secgroup_filter_children = ['nova-base-ipv4',
|
||||||
'nova-base-ipv6',
|
'nova-base-ipv6',
|
||||||
'nova-allow-dhcp-server']
|
'nova-allow-dhcp-server']
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
instance_secgroup_filter_children += ['nova-allow-ra-server']
|
||||||
|
|
||||||
ctxt = context.get_admin_context()
|
ctxt = context.get_admin_context()
|
||||||
|
|
||||||
if FLAGS.allow_project_net_traffic:
|
if FLAGS.allow_project_net_traffic:
|
||||||
instance_filter_children += ['nova-project']
|
instance_filter_children += ['nova-project']
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
instance_filter_children += ['nova-project-v6']
|
||||||
|
|
||||||
for security_group in db.security_group_get_by_instance(ctxt,
|
for security_group in db.security_group_get_by_instance(ctxt,
|
||||||
instance['id']):
|
instance['id']):
|
||||||
@@ -1024,12 +1129,19 @@ class NWFilterFirewall(FirewallDriver):
|
|||||||
security_group = db.security_group_get(context.get_admin_context(),
|
security_group = db.security_group_get(context.get_admin_context(),
|
||||||
security_group_id)
|
security_group_id)
|
||||||
rule_xml = ""
|
rule_xml = ""
|
||||||
|
v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'}
|
||||||
for rule in security_group.rules:
|
for rule in security_group.rules:
|
||||||
rule_xml += "<rule action='accept' direction='in' priority='300'>"
|
rule_xml += "<rule action='accept' direction='in' priority='300'>"
|
||||||
if rule.cidr:
|
if rule.cidr:
|
||||||
net, mask = _get_net_and_mask(rule.cidr)
|
version = _get_ip_version(rule.cidr)
|
||||||
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
|
if(FLAGS.use_ipv6 and version == 6):
|
||||||
(rule.protocol, net, mask)
|
net, prefixlen = _get_net_and_prefixlen(rule.cidr)
|
||||||
|
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
|
||||||
|
(v6protocol[rule.protocol], net, prefixlen)
|
||||||
|
else:
|
||||||
|
net, mask = _get_net_and_mask(rule.cidr)
|
||||||
|
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
|
||||||
|
(rule.protocol, net, mask)
|
||||||
if rule.protocol in ['tcp', 'udp']:
|
if rule.protocol in ['tcp', 'udp']:
|
||||||
rule_xml += "dstportstart='%s' dstportend='%s' " % \
|
rule_xml += "dstportstart='%s' dstportend='%s' " % \
|
||||||
(rule.from_port, rule.to_port)
|
(rule.from_port, rule.to_port)
|
||||||
@@ -1044,8 +1156,11 @@ class NWFilterFirewall(FirewallDriver):
|
|||||||
|
|
||||||
rule_xml += '/>\n'
|
rule_xml += '/>\n'
|
||||||
rule_xml += "</rule>\n"
|
rule_xml += "</rule>\n"
|
||||||
xml = "<filter name='nova-secgroup-%s' chain='ipv4'>%s</filter>" % \
|
xml = "<filter name='nova-secgroup-%s' " % security_group_id
|
||||||
(security_group_id, rule_xml,)
|
if(FLAGS.use_ipv6):
|
||||||
|
xml += "chain='root'>%s</filter>" % rule_xml
|
||||||
|
else:
|
||||||
|
xml += "chain='ipv4'>%s</filter>" % rule_xml
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
def _instance_filter_name(self, instance):
|
def _instance_filter_name(self, instance):
|
||||||
@@ -1082,11 +1197,17 @@ class IptablesFirewallDriver(FirewallDriver):
|
|||||||
def apply_ruleset(self):
|
def apply_ruleset(self):
|
||||||
current_filter, _ = self.execute('sudo iptables-save -t filter')
|
current_filter, _ = self.execute('sudo iptables-save -t filter')
|
||||||
current_lines = current_filter.split('\n')
|
current_lines = current_filter.split('\n')
|
||||||
new_filter = self.modify_rules(current_lines)
|
new_filter = self.modify_rules(current_lines, 4)
|
||||||
self.execute('sudo iptables-restore',
|
self.execute('sudo iptables-restore',
|
||||||
process_input='\n'.join(new_filter))
|
process_input='\n'.join(new_filter))
|
||||||
|
if(FLAGS.use_ipv6):
|
||||||
|
current_filter, _ = self.execute('sudo ip6tables-save -t filter')
|
||||||
|
current_lines = current_filter.split('\n')
|
||||||
|
new_filter = self.modify_rules(current_lines, 6)
|
||||||
|
self.execute('sudo ip6tables-restore',
|
||||||
|
process_input='\n'.join(new_filter))
|
||||||
|
|
||||||
def modify_rules(self, current_lines):
|
def modify_rules(self, current_lines, ip_version=4):
|
||||||
ctxt = context.get_admin_context()
|
ctxt = context.get_admin_context()
|
||||||
# Remove any trace of nova rules.
|
# Remove any trace of nova rules.
|
||||||
new_filter = filter(lambda l: 'nova-' not in l, current_lines)
|
new_filter = filter(lambda l: 'nova-' not in l, current_lines)
|
||||||
@@ -1100,8 +1221,8 @@ class IptablesFirewallDriver(FirewallDriver):
|
|||||||
if not new_filter[rules_index].startswith(':'):
|
if not new_filter[rules_index].startswith(':'):
|
||||||
break
|
break
|
||||||
|
|
||||||
our_chains = [':nova-ipv4-fallback - [0:0]']
|
our_chains = [':nova-fallback - [0:0]']
|
||||||
our_rules = ['-A nova-ipv4-fallback -j DROP']
|
our_rules = ['-A nova-fallback -j DROP']
|
||||||
|
|
||||||
our_chains += [':nova-local - [0:0]']
|
our_chains += [':nova-local - [0:0]']
|
||||||
our_rules += ['-A FORWARD -j nova-local']
|
our_rules += ['-A FORWARD -j nova-local']
|
||||||
@@ -1112,7 +1233,10 @@ class IptablesFirewallDriver(FirewallDriver):
|
|||||||
for instance_id in self.instances:
|
for instance_id in self.instances:
|
||||||
instance = self.instances[instance_id]
|
instance = self.instances[instance_id]
|
||||||
chain_name = self._instance_chain_name(instance)
|
chain_name = self._instance_chain_name(instance)
|
||||||
ip_address = self._ip_for_instance(instance)
|
if(ip_version == 4):
|
||||||
|
ip_address = self._ip_for_instance(instance)
|
||||||
|
elif(ip_version == 6):
|
||||||
|
ip_address = self._ip_for_instance_v6(instance)
|
||||||
|
|
||||||
our_chains += [':%s - [0:0]' % chain_name]
|
our_chains += [':%s - [0:0]' % chain_name]
|
||||||
|
|
||||||
@@ -1139,13 +1263,19 @@ class IptablesFirewallDriver(FirewallDriver):
|
|||||||
|
|
||||||
our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)]
|
our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)]
|
||||||
|
|
||||||
# Allow DHCP responses
|
if(ip_version == 4):
|
||||||
dhcp_server = self._dhcp_server_for_instance(instance)
|
# Allow DHCP responses
|
||||||
our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' %
|
dhcp_server = self._dhcp_server_for_instance(instance)
|
||||||
(chain_name, dhcp_server)]
|
our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' %
|
||||||
|
(chain_name, dhcp_server)]
|
||||||
|
elif(ip_version == 6):
|
||||||
|
# Allow RA responses
|
||||||
|
ra_server = self._ra_server_for_instance(instance)
|
||||||
|
our_rules += ['-A %s -s %s -p icmpv6' %
|
||||||
|
(chain_name, ra_server)]
|
||||||
|
|
||||||
# If nothing matches, jump to the fallback chain
|
# If nothing matches, jump to the fallback chain
|
||||||
our_rules += ['-A %s -j nova-ipv4-fallback' % (chain_name,)]
|
our_rules += ['-A %s -j nova-fallback' % (chain_name,)]
|
||||||
|
|
||||||
# then, security group chains and rules
|
# then, security group chains and rules
|
||||||
for security_group_id in security_groups:
|
for security_group_id in security_groups:
|
||||||
@@ -1158,15 +1288,22 @@ class IptablesFirewallDriver(FirewallDriver):
|
|||||||
|
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
logging.info('%r', rule)
|
logging.info('%r', rule)
|
||||||
args = ['-A', chain_name, '-p', rule.protocol]
|
|
||||||
|
|
||||||
if rule.cidr:
|
if not rule.cidr:
|
||||||
args += ['-s', rule.cidr]
|
|
||||||
else:
|
|
||||||
# Eventually, a mechanism to grant access for security
|
# Eventually, a mechanism to grant access for security
|
||||||
# groups will turn up here. It'll use ipsets.
|
# groups will turn up here. It'll use ipsets.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
version = _get_ip_version(rule.cidr)
|
||||||
|
if version != ip_version:
|
||||||
|
continue
|
||||||
|
|
||||||
|
protocol = rule.protocol
|
||||||
|
if version == 6 and rule.protocol == 'icmp':
|
||||||
|
protocol = 'icmpv6'
|
||||||
|
|
||||||
|
args = ['-A', chain_name, '-p', protocol, '-s', rule.cidr]
|
||||||
|
|
||||||
if rule.protocol in ['udp', 'tcp']:
|
if rule.protocol in ['udp', 'tcp']:
|
||||||
if rule.from_port == rule.to_port:
|
if rule.from_port == rule.to_port:
|
||||||
args += ['--dport', '%s' % (rule.from_port,)]
|
args += ['--dport', '%s' % (rule.from_port,)]
|
||||||
@@ -1186,7 +1323,12 @@ class IptablesFirewallDriver(FirewallDriver):
|
|||||||
icmp_type_arg += '/%s' % icmp_code
|
icmp_type_arg += '/%s' % icmp_code
|
||||||
|
|
||||||
if icmp_type_arg:
|
if icmp_type_arg:
|
||||||
args += ['-m', 'icmp', '--icmp-type', icmp_type_arg]
|
if(ip_version == 4):
|
||||||
|
args += ['-m', 'icmp', '--icmp-type',
|
||||||
|
icmp_type_arg]
|
||||||
|
elif(ip_version == 6):
|
||||||
|
args += ['-m', 'icmp6', '--icmpv6-type',
|
||||||
|
icmp_type_arg]
|
||||||
|
|
||||||
args += ['-j ACCEPT']
|
args += ['-j ACCEPT']
|
||||||
our_rules += [' '.join(args)]
|
our_rules += [' '.join(args)]
|
||||||
@@ -1212,7 +1354,16 @@ class IptablesFirewallDriver(FirewallDriver):
|
|||||||
return db.instance_get_fixed_address(context.get_admin_context(),
|
return db.instance_get_fixed_address(context.get_admin_context(),
|
||||||
instance['id'])
|
instance['id'])
|
||||||
|
|
||||||
|
def _ip_for_instance_v6(self, instance):
|
||||||
|
return db.instance_get_fixed_address_v6(context.get_admin_context(),
|
||||||
|
instance['id'])
|
||||||
|
|
||||||
def _dhcp_server_for_instance(self, instance):
|
def _dhcp_server_for_instance(self, instance):
|
||||||
network = db.project_get_network(context.get_admin_context(),
|
network = db.project_get_network(context.get_admin_context(),
|
||||||
instance['project_id'])
|
instance['project_id'])
|
||||||
return network['gateway']
|
return network['gateway']
|
||||||
|
|
||||||
|
def _ra_server_for_instance(self, instance):
|
||||||
|
network = db.project_get_network(context.get_admin_context(),
|
||||||
|
instance['project_id'])
|
||||||
|
return network['ra_server']
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ Management class for VM-related functions (spawn, reboot, etc).
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import M2Crypto
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import uuid
|
||||||
|
|
||||||
from nova import db
|
from nova import db
|
||||||
from nova import context
|
from nova import context
|
||||||
@@ -127,12 +132,31 @@ class VMOps(object):
|
|||||||
"""Refactored out the common code of many methods that receive either
|
"""Refactored out the common code of many methods that receive either
|
||||||
a vm name or a vm instance, and want a vm instance in return.
|
a vm name or a vm instance, and want a vm instance in return.
|
||||||
"""
|
"""
|
||||||
|
vm = None
|
||||||
try:
|
try:
|
||||||
instance_name = instance_or_vm.name
|
if instance_or_vm.startswith("OpaqueRef:"):
|
||||||
vm = VMHelper.lookup(self._session, instance_name)
|
# Got passed an opaque ref; return it
|
||||||
except AttributeError:
|
return instance_or_vm
|
||||||
# A vm opaque ref was passed
|
else:
|
||||||
vm = instance_or_vm
|
# Must be the instance name
|
||||||
|
instance_name = instance_or_vm
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
# Note the the KeyError will only happen with fakes.py
|
||||||
|
# Not a string; must be an ID or a vm instance
|
||||||
|
if isinstance(instance_or_vm, (int, long)):
|
||||||
|
ctx = context.get_admin_context()
|
||||||
|
try:
|
||||||
|
instance_obj = db.instance_get_by_id(ctx, instance_or_vm)
|
||||||
|
instance_name = instance_obj.name
|
||||||
|
except exception.NotFound:
|
||||||
|
# The unit tests screw this up, as they use an integer for
|
||||||
|
# the vm name. I'd fix that up, but that's a matter for
|
||||||
|
# another bug report. So for now, just try with the passed
|
||||||
|
# value
|
||||||
|
instance_name = instance_or_vm
|
||||||
|
else:
|
||||||
|
instance_name = instance_or_vm.name
|
||||||
|
vm = VMHelper.lookup(self._session, instance_name)
|
||||||
if vm is None:
|
if vm is None:
|
||||||
raise Exception(_('Instance not present %s') % instance_name)
|
raise Exception(_('Instance not present %s') % instance_name)
|
||||||
return vm
|
return vm
|
||||||
@@ -189,6 +213,44 @@ class VMOps(object):
|
|||||||
task = self._session.call_xenapi('Async.VM.clean_reboot', vm)
|
task = self._session.call_xenapi('Async.VM.clean_reboot', vm)
|
||||||
self._session.wait_for_task(instance.id, task)
|
self._session.wait_for_task(instance.id, task)
|
||||||
|
|
||||||
|
def set_admin_password(self, instance, new_pass):
|
||||||
|
"""Set the root/admin password on the VM instance. This is done via
|
||||||
|
an agent running on the VM. Communication between nova and the agent
|
||||||
|
is done via writing xenstore records. Since communication is done over
|
||||||
|
the XenAPI RPC calls, we need to encrypt the password. We're using a
|
||||||
|
simple Diffie-Hellman class instead of the more advanced one in
|
||||||
|
M2Crypto for compatibility with the agent code.
|
||||||
|
"""
|
||||||
|
# Need to uniquely identify this request.
|
||||||
|
transaction_id = str(uuid.uuid4())
|
||||||
|
# The simple Diffie-Hellman class is used to manage key exchange.
|
||||||
|
dh = SimpleDH()
|
||||||
|
args = {'id': transaction_id, 'pub': str(dh.get_public())}
|
||||||
|
resp = self._make_agent_call('key_init', instance, '', args)
|
||||||
|
if resp is None:
|
||||||
|
# No response from the agent
|
||||||
|
return
|
||||||
|
resp_dict = json.loads(resp)
|
||||||
|
# Successful return code from key_init is 'D0'
|
||||||
|
if resp_dict['returncode'] != 'D0':
|
||||||
|
# There was some sort of error; the message will contain
|
||||||
|
# a description of the error.
|
||||||
|
raise RuntimeError(resp_dict['message'])
|
||||||
|
agent_pub = int(resp_dict['message'])
|
||||||
|
dh.compute_shared(agent_pub)
|
||||||
|
enc_pass = dh.encrypt(new_pass)
|
||||||
|
# Send the encrypted password
|
||||||
|
args['enc_pass'] = enc_pass
|
||||||
|
resp = self._make_agent_call('password', instance, '', args)
|
||||||
|
if resp is None:
|
||||||
|
# No response from the agent
|
||||||
|
return
|
||||||
|
resp_dict = json.loads(resp)
|
||||||
|
# Successful return code from password is '0'
|
||||||
|
if resp_dict['returncode'] != '0':
|
||||||
|
raise RuntimeError(resp_dict['message'])
|
||||||
|
return resp_dict['message']
|
||||||
|
|
||||||
def destroy(self, instance):
|
def destroy(self, instance):
|
||||||
"""Destroy VM instance"""
|
"""Destroy VM instance"""
|
||||||
vm = VMHelper.lookup(self._session, instance.name)
|
vm = VMHelper.lookup(self._session, instance.name)
|
||||||
@@ -246,30 +308,19 @@ class VMOps(object):
|
|||||||
|
|
||||||
def suspend(self, instance, callback):
|
def suspend(self, instance, callback):
|
||||||
"""suspend the specified instance"""
|
"""suspend the specified instance"""
|
||||||
instance_name = instance.name
|
vm = self._get_vm_opaque_ref(instance)
|
||||||
vm = VMHelper.lookup(self._session, instance_name)
|
|
||||||
if vm is None:
|
|
||||||
raise Exception(_("suspend: instance not present %s") %
|
|
||||||
instance_name)
|
|
||||||
task = self._session.call_xenapi('Async.VM.suspend', vm)
|
task = self._session.call_xenapi('Async.VM.suspend', vm)
|
||||||
self._wait_with_callback(instance.id, task, callback)
|
self._wait_with_callback(instance.id, task, callback)
|
||||||
|
|
||||||
def resume(self, instance, callback):
|
def resume(self, instance, callback):
|
||||||
"""resume the specified instance"""
|
"""resume the specified instance"""
|
||||||
instance_name = instance.name
|
vm = self._get_vm_opaque_ref(instance)
|
||||||
vm = VMHelper.lookup(self._session, instance_name)
|
|
||||||
if vm is None:
|
|
||||||
raise Exception(_("resume: instance not present %s") %
|
|
||||||
instance_name)
|
|
||||||
task = self._session.call_xenapi('Async.VM.resume', vm, False, True)
|
task = self._session.call_xenapi('Async.VM.resume', vm, False, True)
|
||||||
self._wait_with_callback(instance.id, task, callback)
|
self._wait_with_callback(instance.id, task, callback)
|
||||||
|
|
||||||
def get_info(self, instance_id):
|
def get_info(self, instance):
|
||||||
"""Return data about VM instance"""
|
"""Return data about VM instance"""
|
||||||
vm = VMHelper.lookup(self._session, instance_id)
|
vm = self._get_vm_opaque_ref(instance)
|
||||||
if vm is None:
|
|
||||||
raise exception.NotFound(_('Instance not'
|
|
||||||
' found %s') % instance_id)
|
|
||||||
rec = self._session.get_xenapi().VM.get_record(vm)
|
rec = self._session.get_xenapi().VM.get_record(vm)
|
||||||
return VMHelper.compile_info(rec)
|
return VMHelper.compile_info(rec)
|
||||||
|
|
||||||
@@ -333,22 +384,34 @@ class VMOps(object):
|
|||||||
return self._make_plugin_call('xenstore.py', method=method, vm=vm,
|
return self._make_plugin_call('xenstore.py', method=method, vm=vm,
|
||||||
path=path, addl_args=addl_args)
|
path=path, addl_args=addl_args)
|
||||||
|
|
||||||
|
def _make_agent_call(self, method, vm, path, addl_args={}):
|
||||||
|
"""Abstracts out the interaction with the agent xenapi plugin."""
|
||||||
|
return self._make_plugin_call('agent', method=method, vm=vm,
|
||||||
|
path=path, addl_args=addl_args)
|
||||||
|
|
||||||
def _make_plugin_call(self, plugin, method, vm, path, addl_args={}):
|
def _make_plugin_call(self, plugin, method, vm, path, addl_args={}):
|
||||||
"""Abstracts out the process of calling a method of a xenapi plugin.
|
"""Abstracts out the process of calling a method of a xenapi plugin.
|
||||||
Any errors raised by the plugin will in turn raise a RuntimeError here.
|
Any errors raised by the plugin will in turn raise a RuntimeError here.
|
||||||
"""
|
"""
|
||||||
|
instance_id = vm.id
|
||||||
vm = self._get_vm_opaque_ref(vm)
|
vm = self._get_vm_opaque_ref(vm)
|
||||||
rec = self._session.get_xenapi().VM.get_record(vm)
|
rec = self._session.get_xenapi().VM.get_record(vm)
|
||||||
args = {'dom_id': rec['domid'], 'path': path}
|
args = {'dom_id': rec['domid'], 'path': path}
|
||||||
args.update(addl_args)
|
args.update(addl_args)
|
||||||
# If the 'testing_mode' attribute is set, add that to the args.
|
|
||||||
if getattr(self, 'testing_mode', False):
|
|
||||||
args['testing_mode'] = 'true'
|
|
||||||
try:
|
try:
|
||||||
task = self._session.async_call_plugin(plugin, method, args)
|
task = self._session.async_call_plugin(plugin, method, args)
|
||||||
ret = self._session.wait_for_task(0, task)
|
ret = self._session.wait_for_task(instance_id, task)
|
||||||
except self.XenAPI.Failure, e:
|
except self.XenAPI.Failure, e:
|
||||||
raise RuntimeError("%s" % e.details[-1])
|
ret = None
|
||||||
|
err_trace = e.details[-1]
|
||||||
|
err_msg = err_trace.splitlines()[-1]
|
||||||
|
strargs = str(args)
|
||||||
|
if 'TIMEOUT:' in err_msg:
|
||||||
|
LOG.error(_('TIMEOUT: The call to %(method)s timed out. '
|
||||||
|
'VM id=%(instance_id)s; args=%(strargs)s') % locals())
|
||||||
|
else:
|
||||||
|
LOG.error(_('The call to %(method)s returned an error: %(e)s. '
|
||||||
|
'VM id=%(instance_id)s; args=%(strargs)s') % locals())
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def add_to_xenstore(self, vm, path, key, value):
|
def add_to_xenstore(self, vm, path, key, value):
|
||||||
@@ -460,3 +523,89 @@ class VMOps(object):
|
|||||||
"""Removes all data from the xenstore parameter record for this VM."""
|
"""Removes all data from the xenstore parameter record for this VM."""
|
||||||
self.write_to_param_xenstore(instance_or_vm, {})
|
self.write_to_param_xenstore(instance_or_vm, {})
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def _runproc(cmd):
|
||||||
|
pipe = subprocess.PIPE
|
||||||
|
return subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe,
|
||||||
|
stderr=pipe, close_fds=True)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleDH(object):
|
||||||
|
"""This class wraps all the functionality needed to implement
|
||||||
|
basic Diffie-Hellman-Merkle key exchange in Python. It features
|
||||||
|
intelligent defaults for the prime and base numbers needed for the
|
||||||
|
calculation, while allowing you to supply your own. It requires that
|
||||||
|
the openssl binary be installed on the system on which this is run,
|
||||||
|
as it uses that to handle the encryption and decryption. If openssl
|
||||||
|
is not available, a RuntimeError will be raised.
|
||||||
|
"""
|
||||||
|
def __init__(self, prime=None, base=None, secret=None):
|
||||||
|
"""You can specify the values for prime and base if you wish;
|
||||||
|
otherwise, reasonable default values will be used.
|
||||||
|
"""
|
||||||
|
if prime is None:
|
||||||
|
self._prime = 162259276829213363391578010288127
|
||||||
|
else:
|
||||||
|
self._prime = prime
|
||||||
|
if base is None:
|
||||||
|
self._base = 5
|
||||||
|
else:
|
||||||
|
self._base = base
|
||||||
|
self._shared = self._public = None
|
||||||
|
|
||||||
|
self._dh = M2Crypto.DH.set_params(
|
||||||
|
self.dec_to_mpi(self._prime),
|
||||||
|
self.dec_to_mpi(self._base))
|
||||||
|
self._dh.gen_key()
|
||||||
|
self._public = self.mpi_to_dec(self._dh.pub)
|
||||||
|
|
||||||
|
def get_public(self):
|
||||||
|
return self._public
|
||||||
|
|
||||||
|
def compute_shared(self, other):
|
||||||
|
self._shared = self.bin_to_dec(
|
||||||
|
self._dh.compute_key(self.dec_to_mpi(other)))
|
||||||
|
return self._shared
|
||||||
|
|
||||||
|
def mpi_to_dec(self, mpi):
|
||||||
|
bn = M2Crypto.m2.mpi_to_bn(mpi)
|
||||||
|
hexval = M2Crypto.m2.bn_to_hex(bn)
|
||||||
|
dec = int(hexval, 16)
|
||||||
|
return dec
|
||||||
|
|
||||||
|
def bin_to_dec(self, binval):
|
||||||
|
bn = M2Crypto.m2.bin_to_bn(binval)
|
||||||
|
hexval = M2Crypto.m2.bn_to_hex(bn)
|
||||||
|
dec = int(hexval, 16)
|
||||||
|
return dec
|
||||||
|
|
||||||
|
def dec_to_mpi(self, dec):
|
||||||
|
bn = M2Crypto.m2.dec_to_bn('%s' % dec)
|
||||||
|
mpi = M2Crypto.m2.bn_to_mpi(bn)
|
||||||
|
return mpi
|
||||||
|
|
||||||
|
def _run_ssl(self, text, which):
|
||||||
|
base_cmd = ('cat %(tmpfile)s | openssl enc -aes-128-cbc '
|
||||||
|
'-a -pass pass:%(shared)s -nosalt %(dec_flag)s')
|
||||||
|
if which.lower()[0] == 'd':
|
||||||
|
dec_flag = ' -d'
|
||||||
|
else:
|
||||||
|
dec_flag = ''
|
||||||
|
fd, tmpfile = tempfile.mkstemp()
|
||||||
|
os.close(fd)
|
||||||
|
file(tmpfile, 'w').write(text)
|
||||||
|
shared = self._shared
|
||||||
|
cmd = base_cmd % locals()
|
||||||
|
proc = _runproc(cmd)
|
||||||
|
proc.wait()
|
||||||
|
err = proc.stderr.read()
|
||||||
|
if err:
|
||||||
|
raise RuntimeError(_('OpenSSL error: %s') % err)
|
||||||
|
return proc.stdout.read()
|
||||||
|
|
||||||
|
def encrypt(self, text):
|
||||||
|
return self._run_ssl(text, 'enc')
|
||||||
|
|
||||||
|
def decrypt(self, text):
|
||||||
|
return self._run_ssl(text, 'dec')
|
||||||
|
|||||||
@@ -153,6 +153,10 @@ class XenAPIConnection(object):
|
|||||||
"""Reboot VM instance"""
|
"""Reboot VM instance"""
|
||||||
self._vmops.reboot(instance)
|
self._vmops.reboot(instance)
|
||||||
|
|
||||||
|
def set_admin_password(self, instance, new_pass):
|
||||||
|
"""Set the root/admin password on the VM instance"""
|
||||||
|
self._vmops.set_admin_password(instance, new_pass)
|
||||||
|
|
||||||
def destroy(self, instance):
|
def destroy(self, instance):
|
||||||
"""Destroy VM instance"""
|
"""Destroy VM instance"""
|
||||||
self._vmops.destroy(instance)
|
self._vmops.destroy(instance)
|
||||||
@@ -270,7 +274,8 @@ class XenAPISession(object):
|
|||||||
|
|
||||||
def _poll_task(self, id, task, done):
|
def _poll_task(self, id, task, done):
|
||||||
"""Poll the given XenAPI task, and fire the given action if we
|
"""Poll the given XenAPI task, and fire the given action if we
|
||||||
get a result."""
|
get a result.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
name = self._session.xenapi.task.get_name_label(task)
|
name = self._session.xenapi.task.get_name_label(task)
|
||||||
status = self._session.xenapi.task.get_status(task)
|
status = self._session.xenapi.task.get_status(task)
|
||||||
|
|||||||
@@ -371,3 +371,52 @@ class RBDDriver(VolumeDriver):
|
|||||||
def undiscover_volume(self, volume):
|
def undiscover_volume(self, volume):
|
||||||
"""Undiscover volume on a remote host"""
|
"""Undiscover volume on a remote host"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SheepdogDriver(VolumeDriver):
|
||||||
|
"""Executes commands relating to Sheepdog Volumes"""
|
||||||
|
|
||||||
|
def check_for_setup_error(self):
|
||||||
|
"""Returns an error if prerequisites aren't met"""
|
||||||
|
try:
|
||||||
|
(out, err) = self._execute("collie cluster info")
|
||||||
|
if not out.startswith('running'):
|
||||||
|
raise exception.Error(_("Sheepdog is not working: %s") % out)
|
||||||
|
except exception.ProcessExecutionError:
|
||||||
|
raise exception.Error(_("Sheepdog is not working"))
|
||||||
|
|
||||||
|
def create_volume(self, volume):
|
||||||
|
"""Creates a sheepdog volume"""
|
||||||
|
if int(volume['size']) == 0:
|
||||||
|
sizestr = '100M'
|
||||||
|
else:
|
||||||
|
sizestr = '%sG' % volume['size']
|
||||||
|
self._try_execute("qemu-img create sheepdog:%s %s" %
|
||||||
|
(volume['name'], sizestr))
|
||||||
|
|
||||||
|
def delete_volume(self, volume):
|
||||||
|
"""Deletes a logical volume"""
|
||||||
|
self._try_execute("collie vdi delete %s" % volume['name'])
|
||||||
|
|
||||||
|
def local_path(self, volume):
|
||||||
|
return "sheepdog:%s" % volume['name']
|
||||||
|
|
||||||
|
def ensure_export(self, context, volume):
|
||||||
|
"""Safely and synchronously recreates an export for a logical volume"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_export(self, context, volume):
|
||||||
|
"""Exports the volume"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def remove_export(self, context, volume):
|
||||||
|
"""Removes an export for a logical volume"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def discover_volume(self, volume):
|
||||||
|
"""Discover volume on a remote host"""
|
||||||
|
return "sheepdog:%s" % volume['name']
|
||||||
|
|
||||||
|
def undiscover_volume(self, volume):
|
||||||
|
"""Undiscover volume on a remote host"""
|
||||||
|
pass
|
||||||
|
|||||||
36
nova/wsgi.py
36
nova/wsgi.py
@@ -21,7 +21,6 @@
|
|||||||
Utility methods for working with WSGI servers
|
Utility methods for working with WSGI servers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
|
||||||
import sys
|
import sys
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
|
|
||||||
@@ -35,6 +34,7 @@ import webob.dec
|
|||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
|
from nova import utils
|
||||||
|
|
||||||
|
|
||||||
class WritableLogger(object):
|
class WritableLogger(object):
|
||||||
@@ -117,20 +117,38 @@ class Application(object):
|
|||||||
|
|
||||||
|
|
||||||
class Middleware(Application):
|
class Middleware(Application):
|
||||||
"""
|
"""Base WSGI middleware.
|
||||||
Base WSGI middleware wrapper. These classes require an application to be
|
|
||||||
|
These classes require an application to be
|
||||||
initialized that will be called next. By default the middleware will
|
initialized that will be called next. By default the middleware will
|
||||||
simply call its wrapped app, or you can override __call__ to customize its
|
simply call its wrapped app, or you can override __call__ to customize its
|
||||||
behavior.
|
behavior.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, application): # pylint: disable-msg=W0231
|
def __init__(self, application):
|
||||||
self.application = application
|
self.application = application
|
||||||
|
|
||||||
|
def process_request(self, req):
|
||||||
|
"""Called on each request.
|
||||||
|
|
||||||
|
If this returns None, the next application down the stack will be
|
||||||
|
executed. If it returns a response then that response will be returned
|
||||||
|
and execution will stop here.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def process_response(self, response):
|
||||||
|
"""Do whatever you'd like to the response."""
|
||||||
|
return response
|
||||||
|
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify
|
||||||
def __call__(self, req): # pylint: disable-msg=W0221
|
def __call__(self, req):
|
||||||
"""Override to implement middleware behavior."""
|
response = self.process_request(req)
|
||||||
return self.application
|
if response:
|
||||||
|
return response
|
||||||
|
response = req.get_response(self.application)
|
||||||
|
return self.process_response(response)
|
||||||
|
|
||||||
|
|
||||||
class Debug(Middleware):
|
class Debug(Middleware):
|
||||||
@@ -316,7 +334,7 @@ class Serializer(object):
|
|||||||
try:
|
try:
|
||||||
is_xml = (datastring[0] == '<')
|
is_xml = (datastring[0] == '<')
|
||||||
if not is_xml:
|
if not is_xml:
|
||||||
return json.loads(datastring)
|
return utils.loads(datastring)
|
||||||
return self._from_xml(datastring)
|
return self._from_xml(datastring)
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
@@ -349,7 +367,7 @@ class Serializer(object):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _to_json(self, data):
|
def _to_json(self, data):
|
||||||
return json.dumps(data)
|
return utils.dumps(data)
|
||||||
|
|
||||||
def _to_xml(self, data):
|
def _to_xml(self, data):
|
||||||
metadata = self.metadata.get('application/xml', {})
|
metadata = self.metadata.get('application/xml', {})
|
||||||
|
|||||||
126
plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
Executable file
126
plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
Executable file
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (c) 2011 Citrix Systems, Inc.
|
||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# Copyright 2011 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.
|
||||||
|
|
||||||
|
#
|
||||||
|
# XenAPI plugin for reading/writing information to xenstore
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
|
||||||
|
import XenAPIPlugin
|
||||||
|
|
||||||
|
from pluginlib_nova import *
|
||||||
|
configure_logging("xenstore")
|
||||||
|
import xenstore
|
||||||
|
|
||||||
|
AGENT_TIMEOUT = 30
|
||||||
|
|
||||||
|
|
||||||
|
def jsonify(fnc):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return json.dumps(fnc(*args, **kwargs))
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutError(StandardError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@jsonify
|
||||||
|
def key_init(self, arg_dict):
|
||||||
|
"""Handles the Diffie-Hellman key exchange with the agent to
|
||||||
|
establish the shared secret key used to encrypt/decrypt sensitive
|
||||||
|
info to be passed, such as passwords. Returns the shared
|
||||||
|
secret key value.
|
||||||
|
"""
|
||||||
|
pub = int(arg_dict["pub"])
|
||||||
|
arg_dict["value"] = json.dumps({"name": "keyinit", "value": pub})
|
||||||
|
request_id = arg_dict["id"]
|
||||||
|
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("%s" % e)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@jsonify
|
||||||
|
def password(self, arg_dict):
|
||||||
|
"""Writes a request to xenstore that tells the agent to set
|
||||||
|
the root password for the given VM. The password should be
|
||||||
|
encrypted using the shared secret key that was returned by a
|
||||||
|
previous call to key_init. The encrypted password value should
|
||||||
|
be passed as the value for the 'enc_pass' key in arg_dict.
|
||||||
|
"""
|
||||||
|
pub = int(arg_dict["pub"])
|
||||||
|
enc_pass = arg_dict["enc_pass"]
|
||||||
|
arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass})
|
||||||
|
request_id = arg_dict["id"]
|
||||||
|
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("%s" % e)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
the agent's response for that request will be in 'data/guest/{id}'.
|
||||||
|
If no value appears from the agent within the time specified by
|
||||||
|
AGENT_TIMEOUT, the original request is deleted and a TimeoutError
|
||||||
|
is returned.
|
||||||
|
"""
|
||||||
|
arg_dict["path"] = "data/guest/%s" % request_id
|
||||||
|
arg_dict["ignore_missing_path"] = True
|
||||||
|
start = time.time()
|
||||||
|
while True:
|
||||||
|
if time.time() - start > AGENT_TIMEOUT:
|
||||||
|
# No response within the timeout period; bail out
|
||||||
|
# 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)
|
||||||
|
ret = xenstore.read_record(self, arg_dict)
|
||||||
|
# Note: the response for None with be a string that includes
|
||||||
|
# double quotes.
|
||||||
|
if ret != '"None"':
|
||||||
|
# The agent responded
|
||||||
|
return ret
|
||||||
|
else:
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
XenAPIPlugin.dispatch(
|
||||||
|
{"key_init": key_init,
|
||||||
|
"password": password})
|
||||||
@@ -43,7 +43,7 @@ flags.DEFINE_string('suite', None, 'Specific test suite to run ' + SUITE_NAMES)
|
|||||||
# TODO(devamcar): Use random tempfile
|
# TODO(devamcar): Use random tempfile
|
||||||
ZIP_FILENAME = '/tmp/nova-me-x509.zip'
|
ZIP_FILENAME = '/tmp/nova-me-x509.zip'
|
||||||
|
|
||||||
TEST_PREFIX = 'test%s' % int(random.random()*1000000)
|
TEST_PREFIX = 'test%s' % int(random.random() * 1000000)
|
||||||
TEST_USERNAME = '%suser' % TEST_PREFIX
|
TEST_USERNAME = '%suser' % TEST_PREFIX
|
||||||
TEST_PROJECTNAME = '%sproject' % TEST_PREFIX
|
TEST_PROJECTNAME = '%sproject' % TEST_PREFIX
|
||||||
|
|
||||||
@@ -96,4 +96,3 @@ class UserTests(AdminSmokeTestCase):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
suites = {'user': unittest.makeSuite(UserTests)}
|
suites = {'user': unittest.makeSuite(UserTests)}
|
||||||
sys.exit(base.run_tests(suites))
|
sys.exit(base.run_tests(suites))
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import boto
|
import boto
|
||||||
|
import boto_v6
|
||||||
import commands
|
import commands
|
||||||
import httplib
|
import httplib
|
||||||
import os
|
import os
|
||||||
@@ -69,6 +70,17 @@ class SmokeTestCase(unittest.TestCase):
|
|||||||
'test.')
|
'test.')
|
||||||
|
|
||||||
parts = self.split_clc_url(clc_url)
|
parts = self.split_clc_url(clc_url)
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
return boto_v6.connect_ec2(aws_access_key_id=access_key,
|
||||||
|
aws_secret_access_key=secret_key,
|
||||||
|
is_secure=parts['is_secure'],
|
||||||
|
region=RegionInfo(None,
|
||||||
|
'nova',
|
||||||
|
parts['ip']),
|
||||||
|
port=parts['port'],
|
||||||
|
path='/services/Cloud',
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
return boto.connect_ec2(aws_access_key_id=access_key,
|
return boto.connect_ec2(aws_access_key_id=access_key,
|
||||||
aws_secret_access_key=secret_key,
|
aws_secret_access_key=secret_key,
|
||||||
is_secure=parts['is_secure'],
|
is_secure=parts['is_secure'],
|
||||||
@@ -115,7 +127,8 @@ class SmokeTestCase(unittest.TestCase):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def upload_image(self, bucket_name, image):
|
def upload_image(self, bucket_name, image):
|
||||||
cmd = 'euca-upload-bundle -b %s -m /tmp/%s.manifest.xml' % (bucket_name, image)
|
cmd = 'euca-upload-bundle -b '
|
||||||
|
cmd += '%s -m /tmp/%s.manifest.xml' % (bucket_name, image)
|
||||||
status, output = commands.getstatusoutput(cmd)
|
status, output = commands.getstatusoutput(cmd)
|
||||||
if status != 0:
|
if status != 0:
|
||||||
print '%s -> \n %s' % (cmd, output)
|
print '%s -> \n %s' % (cmd, output)
|
||||||
@@ -130,6 +143,7 @@ class SmokeTestCase(unittest.TestCase):
|
|||||||
raise Exception(output)
|
raise Exception(output)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def run_tests(suites):
|
def run_tests(suites):
|
||||||
argv = FLAGS(sys.argv)
|
argv = FLAGS(sys.argv)
|
||||||
|
|
||||||
@@ -151,4 +165,3 @@ def run_tests(suites):
|
|||||||
else:
|
else:
|
||||||
for suite in suites.itervalues():
|
for suite in suites.itervalues():
|
||||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
unittest.TextTestRunner(verbosity=2).run(suite)
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ DEFINE_bool = DEFINE_bool
|
|||||||
# __GLOBAL FLAGS ONLY__
|
# __GLOBAL FLAGS ONLY__
|
||||||
# Define any app-specific flags in their own files, docs at:
|
# Define any app-specific flags in their own files, docs at:
|
||||||
# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
|
# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
|
||||||
|
|
||||||
DEFINE_string('region', 'nova', 'Region to use')
|
DEFINE_string('region', 'nova', 'Region to use')
|
||||||
DEFINE_string('test_image', 'ami-tiny', 'Image to use for launch tests')
|
DEFINE_string('test_image', 'ami-tiny', 'Image to use for launch tests')
|
||||||
|
DEFINE_string('use_ipv6', True, 'use the ipv6 or not')
|
||||||
|
|||||||
180
smoketests/public_network_smoketests.py
Normal file
180
smoketests/public_network_smoketests.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import commands
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from smoketests import flags
|
||||||
|
from smoketests import base
|
||||||
|
from smoketests import user_smoketests
|
||||||
|
|
||||||
|
#Note that this test should run from
|
||||||
|
#public network (outside of private network segments)
|
||||||
|
#Please set EC2_URL correctly
|
||||||
|
#You should use admin account in this test
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
TEST_PREFIX = 'test%s' % int(random.random() * 1000000)
|
||||||
|
TEST_BUCKET = '%s_bucket' % TEST_PREFIX
|
||||||
|
TEST_KEY = '%s_key' % TEST_PREFIX
|
||||||
|
TEST_KEY2 = '%s_key2' % TEST_PREFIX
|
||||||
|
TEST_DATA = {}
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceTestsFromPublic(user_smoketests.UserSmokeTestCase):
|
||||||
|
def test_001_can_create_keypair(self):
|
||||||
|
key = self.create_key_pair(self.conn, TEST_KEY)
|
||||||
|
self.assertEqual(key.name, TEST_KEY)
|
||||||
|
|
||||||
|
def test_002_security_group(self):
|
||||||
|
security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd")
|
||||||
|
for x in range(random.randint(4, 8)))
|
||||||
|
group = self.conn.create_security_group(security_group_name,
|
||||||
|
'test group')
|
||||||
|
group.connection = self.conn
|
||||||
|
group.authorize('tcp', 22, 22, '0.0.0.0/0')
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
group.authorize('tcp', 22, 22, '::/0')
|
||||||
|
|
||||||
|
reservation = self.conn.run_instances(FLAGS.test_image,
|
||||||
|
key_name=TEST_KEY,
|
||||||
|
security_groups=[security_group_name],
|
||||||
|
instance_type='m1.tiny')
|
||||||
|
self.data['security_group_name'] = security_group_name
|
||||||
|
self.data['group'] = group
|
||||||
|
self.data['instance_id'] = reservation.instances[0].id
|
||||||
|
|
||||||
|
def test_003_instance_with_group_runs_within_60_seconds(self):
|
||||||
|
reservations = self.conn.get_all_instances([self.data['instance_id']])
|
||||||
|
instance = reservations[0].instances[0]
|
||||||
|
# allow 60 seconds to exit pending with IP
|
||||||
|
for x in xrange(60):
|
||||||
|
instance.update()
|
||||||
|
if instance.state == u'running':
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
self.fail('instance failed to start')
|
||||||
|
ip = reservations[0].instances[0].private_dns_name
|
||||||
|
self.failIf(ip == '0.0.0.0')
|
||||||
|
self.data['private_ip'] = ip
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
ipv6 = reservations[0].instances[0].dns_name_v6
|
||||||
|
self.failIf(ipv6 is None)
|
||||||
|
self.data['ip_v6'] = ipv6
|
||||||
|
|
||||||
|
def test_004_can_ssh_to_ipv6(self):
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
for x in xrange(20):
|
||||||
|
try:
|
||||||
|
conn = self.connect_ssh(
|
||||||
|
self.data['ip_v6'], TEST_KEY)
|
||||||
|
conn.close()
|
||||||
|
except Exception as ex:
|
||||||
|
print ex
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail('could not ssh to instance')
|
||||||
|
|
||||||
|
def test_012_can_create_instance_with_keypair(self):
|
||||||
|
if 'instance_id' in self.data:
|
||||||
|
self.conn.terminate_instances([self.data['instance_id']])
|
||||||
|
reservation = self.conn.run_instances(FLAGS.test_image,
|
||||||
|
key_name=TEST_KEY,
|
||||||
|
instance_type='m1.tiny')
|
||||||
|
self.assertEqual(len(reservation.instances), 1)
|
||||||
|
self.data['instance_id'] = reservation.instances[0].id
|
||||||
|
|
||||||
|
def test_013_instance_runs_within_60_seconds(self):
|
||||||
|
reservations = self.conn.get_all_instances([self.data['instance_id']])
|
||||||
|
instance = reservations[0].instances[0]
|
||||||
|
# allow 60 seconds to exit pending with IP
|
||||||
|
for x in xrange(60):
|
||||||
|
instance.update()
|
||||||
|
if instance.state == u'running':
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
self.fail('instance failed to start')
|
||||||
|
ip = reservations[0].instances[0].private_dns_name
|
||||||
|
self.failIf(ip == '0.0.0.0')
|
||||||
|
self.data['private_ip'] = ip
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
ipv6 = reservations[0].instances[0].dns_name_v6
|
||||||
|
self.failIf(ipv6 is None)
|
||||||
|
self.data['ip_v6'] = ipv6
|
||||||
|
|
||||||
|
def test_014_can_not_ping_private_ip(self):
|
||||||
|
for x in xrange(4):
|
||||||
|
# ping waits for 1 second
|
||||||
|
status, output = commands.getstatusoutput(
|
||||||
|
'ping -c1 %s' % self.data['private_ip'])
|
||||||
|
if status == 0:
|
||||||
|
self.fail('can ping private ip from public network')
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
status, output = commands.getstatusoutput(
|
||||||
|
'ping6 -c1 %s' % self.data['ip_v6'])
|
||||||
|
if status == 0:
|
||||||
|
self.fail('can ping ipv6 from public network')
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_015_can_not_ssh_to_private_ip(self):
|
||||||
|
for x in xrange(1):
|
||||||
|
try:
|
||||||
|
conn = self.connect_ssh(self.data['private_ip'], TEST_KEY)
|
||||||
|
conn.close()
|
||||||
|
except Exception:
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
self.fail('can ssh for ipv4 address from public network')
|
||||||
|
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
for x in xrange(1):
|
||||||
|
try:
|
||||||
|
conn = self.connect_ssh(
|
||||||
|
self.data['ip_v6'], TEST_KEY)
|
||||||
|
conn.close()
|
||||||
|
except Exception:
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
self.fail('can ssh for ipv6 address from public network')
|
||||||
|
|
||||||
|
def test_999_tearDown(self):
|
||||||
|
self.delete_key_pair(self.conn, TEST_KEY)
|
||||||
|
security_group_name = self.data['security_group_name']
|
||||||
|
group = self.data['group']
|
||||||
|
if group:
|
||||||
|
group.revoke('tcp', 22, 22, '0.0.0.0/0')
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
group.revoke('tcp', 22, 22, '::/0')
|
||||||
|
self.conn.delete_security_group(security_group_name)
|
||||||
|
if 'instance_id' in self.data:
|
||||||
|
self.conn.terminate_instances([self.data['instance_id']])
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
suites = {'instance': unittest.makeSuite(InstanceTestsFromPublic)}
|
||||||
|
sys.exit(base.run_tests(suites))
|
||||||
@@ -45,7 +45,7 @@ flags.DEFINE_string('bundle_kernel', 'openwrt-x86-vmlinuz',
|
|||||||
flags.DEFINE_string('bundle_image', 'openwrt-x86-ext2.image',
|
flags.DEFINE_string('bundle_image', 'openwrt-x86-ext2.image',
|
||||||
'Local image file to use for bundling tests')
|
'Local image file to use for bundling tests')
|
||||||
|
|
||||||
TEST_PREFIX = 'test%s' % int (random.random()*1000000)
|
TEST_PREFIX = 'test%s' % int(random.random() * 1000000)
|
||||||
TEST_BUCKET = '%s_bucket' % TEST_PREFIX
|
TEST_BUCKET = '%s_bucket' % TEST_PREFIX
|
||||||
TEST_KEY = '%s_key' % TEST_PREFIX
|
TEST_KEY = '%s_key' % TEST_PREFIX
|
||||||
TEST_GROUP = '%s_group' % TEST_PREFIX
|
TEST_GROUP = '%s_group' % TEST_PREFIX
|
||||||
@@ -80,7 +80,7 @@ class ImageTests(UserSmokeTestCase):
|
|||||||
|
|
||||||
def test_006_can_register_kernel(self):
|
def test_006_can_register_kernel(self):
|
||||||
kernel_id = self.conn.register_image('%s/%s.manifest.xml' %
|
kernel_id = self.conn.register_image('%s/%s.manifest.xml' %
|
||||||
(TEST_BUCKET, FLAGS.bundle_kernel))
|
(TEST_BUCKET, FLAGS.bundle_kernel))
|
||||||
self.assert_(kernel_id is not None)
|
self.assert_(kernel_id is not None)
|
||||||
self.data['kernel_id'] = kernel_id
|
self.data['kernel_id'] = kernel_id
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ class ImageTests(UserSmokeTestCase):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
print image.state
|
print image.state
|
||||||
self.assert_(False) # wasn't available within 10 seconds
|
self.assert_(False) # wasn't available within 10 seconds
|
||||||
self.assert_(image.type == 'machine')
|
self.assert_(image.type == 'machine')
|
||||||
|
|
||||||
for i in xrange(10):
|
for i in xrange(10):
|
||||||
@@ -101,7 +101,7 @@ class ImageTests(UserSmokeTestCase):
|
|||||||
break
|
break
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
self.assert_(False) # wasn't available within 10 seconds
|
self.assert_(False) # wasn't available within 10 seconds
|
||||||
self.assert_(kernel.type == 'kernel')
|
self.assert_(kernel.type == 'kernel')
|
||||||
|
|
||||||
def test_008_can_describe_image_attribute(self):
|
def test_008_can_describe_image_attribute(self):
|
||||||
@@ -152,14 +152,17 @@ class InstanceTests(UserSmokeTestCase):
|
|||||||
for x in xrange(60):
|
for x in xrange(60):
|
||||||
instance.update()
|
instance.update()
|
||||||
if instance.state == u'running':
|
if instance.state == u'running':
|
||||||
break
|
break
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
self.fail('instance failed to start')
|
self.fail('instance failed to start')
|
||||||
ip = reservations[0].instances[0].private_dns_name
|
ip = reservations[0].instances[0].private_dns_name
|
||||||
self.failIf(ip == '0.0.0.0')
|
self.failIf(ip == '0.0.0.0')
|
||||||
self.data['private_ip'] = ip
|
self.data['private_ip'] = ip
|
||||||
print self.data['private_ip']
|
if FLAGS.use_ipv6:
|
||||||
|
ipv6 = reservations[0].instances[0].dns_name_v6
|
||||||
|
self.failIf(ipv6 is None)
|
||||||
|
self.data['ip_v6'] = ipv6
|
||||||
|
|
||||||
def test_004_can_ping_private_ip(self):
|
def test_004_can_ping_private_ip(self):
|
||||||
for x in xrange(120):
|
for x in xrange(120):
|
||||||
@@ -171,6 +174,16 @@ class InstanceTests(UserSmokeTestCase):
|
|||||||
else:
|
else:
|
||||||
self.fail('could not ping instance')
|
self.fail('could not ping instance')
|
||||||
|
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
for x in xrange(120):
|
||||||
|
# ping waits for 1 second
|
||||||
|
status, output = commands.getstatusoutput(
|
||||||
|
'ping6 -c1 %s' % self.data['ip_v6'])
|
||||||
|
if status == 0:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail('could not ping instance')
|
||||||
|
|
||||||
def test_005_can_ssh_to_private_ip(self):
|
def test_005_can_ssh_to_private_ip(self):
|
||||||
for x in xrange(30):
|
for x in xrange(30):
|
||||||
try:
|
try:
|
||||||
@@ -183,6 +196,19 @@ class InstanceTests(UserSmokeTestCase):
|
|||||||
else:
|
else:
|
||||||
self.fail('could not ssh to instance')
|
self.fail('could not ssh to instance')
|
||||||
|
|
||||||
|
if FLAGS.use_ipv6:
|
||||||
|
for x in xrange(30):
|
||||||
|
try:
|
||||||
|
conn = self.connect_ssh(
|
||||||
|
self.data['ip_v6'], TEST_KEY)
|
||||||
|
conn.close()
|
||||||
|
except Exception:
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail('could not ssh to instance v6')
|
||||||
|
|
||||||
def test_006_can_allocate_elastic_ip(self):
|
def test_006_can_allocate_elastic_ip(self):
|
||||||
result = self.conn.allocate_address()
|
result = self.conn.allocate_address()
|
||||||
self.assertTrue(hasattr(result, 'public_ip'))
|
self.assertTrue(hasattr(result, 'public_ip'))
|
||||||
@@ -388,7 +414,6 @@ class SecurityGroupTests(UserSmokeTestCase):
|
|||||||
raise Exception("Timeout")
|
raise Exception("Timeout")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
def test_999_tearDown(self):
|
def test_999_tearDown(self):
|
||||||
self.conn.delete_key_pair(TEST_KEY)
|
self.conn.delete_key_pair(TEST_KEY)
|
||||||
self.conn.delete_security_group(TEST_GROUP)
|
self.conn.delete_security_group(TEST_GROUP)
|
||||||
|
|||||||
@@ -25,3 +25,4 @@ bzr
|
|||||||
Twisted>=10.1.0
|
Twisted>=10.1.0
|
||||||
PasteDeploy
|
PasteDeploy
|
||||||
paste
|
paste
|
||||||
|
netaddr
|
||||||
|
|||||||
Reference in New Issue
Block a user