merge from upstream
This commit is contained in:
commit
9750e4ab3e
6
Authors
6
Authors
@ -4,8 +4,8 @@ Anthony Young <sleepsonthefloor@gmail.com>
|
||||
Antony Messerli <ant@openstack.org>
|
||||
Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
|
||||
Chiradeep Vittal <chiradeep@cloud.com>
|
||||
Chris Behrens <cbehrens@codestud.com>
|
||||
Chmouel Boudjnah <chmouel@chmouel.com>
|
||||
Chris Behrens <cbehrens@codestud.com>
|
||||
Cory Wright <corywright@gmail.com>
|
||||
David Pravec <David.Pravec@danix.org>
|
||||
Dean Troyer <dtroyer@gmail.com>
|
||||
@ -14,6 +14,7 @@ Ed Leafe <ed@leafe.com>
|
||||
Eldar Nugaev <enugaev@griddynamics.com>
|
||||
Eric Day <eday@oddments.org>
|
||||
Ewan Mellor <ewan.mellor@citrix.com>
|
||||
Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>
|
||||
Hisaki Ohara <hisaki.ohara@intel.com>
|
||||
Ilya Alekseyev <ialekseev@griddynamics.com>
|
||||
Jay Pipes <jaypipes@gmail.com>
|
||||
@ -26,11 +27,14 @@ Josh Kearney <josh.kearney@rackspace.com>
|
||||
Joshua McKenty <jmckenty@gmail.com>
|
||||
Justin Santa Barbara <justin@fathomdb.com>
|
||||
Ken Pepple <ken.pepple@gmail.com>
|
||||
Koji Iida <iida.koji@lab.ntt.co.jp>
|
||||
Lorin Hochstein <lorin@isi.edu>
|
||||
Matt Dietz <matt.dietz@rackspace.com>
|
||||
Michael Gundlach <michael.gundlach@rackspace.com>
|
||||
Monsyne Dragon <mdragon@rackspace.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>
|
||||
Rick Clark <rick@openstack.org>
|
||||
Rick Harris <rconradharris@gmail.com>
|
||||
|
@ -92,6 +92,7 @@ flags.DECLARE('num_networks', 'nova.network.manager')
|
||||
flags.DECLARE('network_size', 'nova.network.manager')
|
||||
flags.DECLARE('vlan_start', 'nova.network.manager')
|
||||
flags.DECLARE('vpn_start', 'nova.network.manager')
|
||||
flags.DECLARE('fixed_range_v6', 'nova.network.manager')
|
||||
|
||||
|
||||
class VpnCommands(object):
|
||||
@ -440,11 +441,12 @@ class NetworkCommands(object):
|
||||
"""Class for managing networks."""
|
||||
|
||||
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
|
||||
arguments: [fixed_range=FLAG], [num_networks=FLAG],
|
||||
[network_size=FLAG], [vlan_start=FLAG],
|
||||
[vpn_start=FLAG]"""
|
||||
[vpn_start=FLAG], [fixed_range_v6=FLAG]"""
|
||||
if not fixed_range:
|
||||
fixed_range = FLAGS.fixed_range
|
||||
if not num_networks:
|
||||
@ -455,11 +457,13 @@ class NetworkCommands(object):
|
||||
vlan_start = FLAGS.vlan_start
|
||||
if not 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.create_networks(context.get_admin_context(),
|
||||
fixed_range, int(num_networks),
|
||||
int(network_size), int(vlan_start),
|
||||
int(vpn_start))
|
||||
int(vpn_start), fixed_range_v6)
|
||||
|
||||
|
||||
class ServiceCommands(object):
|
||||
|
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
|
@ -87,6 +87,13 @@ if [ "$CMD" == "install" ]; then
|
||||
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-ipy
|
||||
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
|
||||
cat <<MYSQL_PRESEED | debconf-set-selections
|
||||
mysql-server-5.1 mysql-server/root_password password $MYSQL_PASS
|
||||
@ -108,6 +115,8 @@ function screen_it {
|
||||
|
||||
if [ "$CMD" == "run" ]; then
|
||||
killall dnsmasq
|
||||
#For IPv6
|
||||
killall radvd
|
||||
screen -d -m -S nova -t nova
|
||||
sleep 1
|
||||
if [ "$USE_MYSQL" == 1 ]; then
|
||||
|
@ -26,16 +26,17 @@ import base64
|
||||
import datetime
|
||||
import IPy
|
||||
import os
|
||||
import urllib
|
||||
|
||||
from nova import compute
|
||||
from nova import context
|
||||
|
||||
from nova import crypto
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import log as logging
|
||||
from nova import network
|
||||
from nova import rpc
|
||||
from nova import utils
|
||||
from nova import volume
|
||||
from nova.compute import instance_types
|
||||
@ -128,15 +129,6 @@ class CloudController(object):
|
||||
result[key] = [line]
|
||||
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):
|
||||
services = db.service_get_all_by_host(context, host)
|
||||
if len(services) > 0:
|
||||
@ -374,6 +366,7 @@ class CloudController(object):
|
||||
values['group_id'] = source_security_group['id']
|
||||
elif cidr_ip:
|
||||
# If this fails, it throws an exception. This is what we want.
|
||||
cidr_ip = urllib.unquote(cidr_ip).decode()
|
||||
IPy.IP(cidr_ip)
|
||||
values['cidr'] = cidr_ip
|
||||
else:
|
||||
@ -519,13 +512,7 @@ class CloudController(object):
|
||||
# instance_id is passed in as a list of instances
|
||||
ec2_id = instance_id[0]
|
||||
instance_id = ec2_id_to_id(ec2_id)
|
||||
instance_ref = self.compute_api.get(context, instance_id)
|
||||
output = rpc.call(context,
|
||||
'%s.%s' % (FLAGS.compute_topic,
|
||||
instance_ref['host']),
|
||||
{"method": "get_console_output",
|
||||
"args": {"instance_id": instance_ref['id']}})
|
||||
|
||||
output = self.compute_api.get_console_output(context, instance_id)
|
||||
now = datetime.datetime.utcnow()
|
||||
return {"InstanceId": ec2_id,
|
||||
"Timestamp": now,
|
||||
@ -643,6 +630,10 @@ class CloudController(object):
|
||||
def describe_instances(self, 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):
|
||||
return {'reservationSet': self._format_instances(context, **kwargs)}
|
||||
|
||||
@ -678,10 +669,16 @@ class CloudController(object):
|
||||
if instance['fixed_ip']['floating_ips']:
|
||||
fixed = instance['fixed_ip']
|
||||
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['publicDnsName'] = floating_addr
|
||||
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
|
||||
i['keyName'] = instance['key_name']
|
||||
|
||||
if context.user.is_admin():
|
||||
i['keyName'] = '%s (%s, %s)' % (i['keyName'],
|
||||
instance['project_id'],
|
||||
|
@ -165,15 +165,18 @@ class Controller(wsgi.Controller):
|
||||
if not inst_dict:
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
|
||||
ctxt = req.environ['nova.context']
|
||||
update_dict = {}
|
||||
if 'adminPass' in inst_dict['server']:
|
||||
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']:
|
||||
update_dict['display_name'] = inst_dict['server']['name']
|
||||
|
||||
try:
|
||||
self.compute_api.update(req.environ['nova.context'], id,
|
||||
**update_dict)
|
||||
self.compute_api.update(ctxt, id, **update_dict)
|
||||
except exception.NotFound:
|
||||
return faults.Fault(exc.HTTPNotFound())
|
||||
return exc.HTTPNoContent()
|
||||
|
@ -21,6 +21,7 @@ Handles all requests relating to instances (guest vms).
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import time
|
||||
|
||||
from nova import db
|
||||
@ -280,7 +281,7 @@ class API(base.Base):
|
||||
return self.db.instance_update(context, instance_id, kwargs)
|
||||
|
||||
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:
|
||||
instance = self.get(context, instance_id)
|
||||
except exception.NotFound as e:
|
||||
@ -301,10 +302,8 @@ class API(base.Base):
|
||||
|
||||
host = instance['host']
|
||||
if host:
|
||||
rpc.cast(context,
|
||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
||||
{"method": "terminate_instance",
|
||||
"args": {"instance_id": instance_id}})
|
||||
self._cast_compute_message('terminate_instance', context,
|
||||
instance_id, host)
|
||||
else:
|
||||
self.db.instance_destroy(context, instance_id)
|
||||
|
||||
@ -332,50 +331,46 @@ class API(base.Base):
|
||||
project_id)
|
||||
return self.db.instance_get_all(context)
|
||||
|
||||
def _cast_compute_message(self, method, context, instance_id, host=None):
|
||||
"""Generic handler for RPC casts to compute."""
|
||||
if not host:
|
||||
instance = self.get(context, instance_id)
|
||||
host = instance['host']
|
||||
queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
|
||||
kwargs = {'method': method, 'args': {'instance_id': instance_id}}
|
||||
rpc.cast(context, queue, kwargs)
|
||||
|
||||
def _call_compute_message(self, method, context, instance_id, host=None):
|
||||
"""Generic handler for RPC calls to compute."""
|
||||
if not host:
|
||||
instance = self.get(context, instance_id)
|
||||
host = instance["host"]
|
||||
queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
|
||||
kwargs = {"method": method, "args": {"instance_id": instance_id}}
|
||||
return rpc.call(context, queue, kwargs)
|
||||
|
||||
def snapshot(self, context, instance_id, name):
|
||||
"""Snapshot the given instance."""
|
||||
instance = self.get(context, instance_id)
|
||||
host = instance['host']
|
||||
rpc.cast(context,
|
||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
||||
{"method": "snapshot_instance",
|
||||
"args": {"instance_id": instance_id, "name": name}})
|
||||
self._cast_compute_message('snapshot_instance', context, instance_id)
|
||||
|
||||
def reboot(self, context, instance_id):
|
||||
"""Reboot the given instance."""
|
||||
instance = self.get(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}})
|
||||
self._cast_compute_message('reboot_instance', context, instance_id)
|
||||
|
||||
def pause(self, context, instance_id):
|
||||
"""Pause the given instance."""
|
||||
instance = self.get(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}})
|
||||
self._cast_compute_message('pause_instance', context, instance_id)
|
||||
|
||||
def unpause(self, context, instance_id):
|
||||
"""Unpause the given instance."""
|
||||
instance = self.get(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}})
|
||||
self._cast_compute_message('unpause_instance', context, instance_id)
|
||||
|
||||
def get_diagnostics(self, context, instance_id):
|
||||
"""Retrieve diagnostics for the given instance."""
|
||||
instance = self.get(context, instance_id)
|
||||
host = instance["host"]
|
||||
return rpc.call(context,
|
||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
||||
{"method": "get_diagnostics",
|
||||
"args": {"instance_id": instance_id}})
|
||||
return self._call_compute_message(
|
||||
"get_diagnostics",
|
||||
context,
|
||||
instance_id)
|
||||
|
||||
def get_actions(self, context, instance_id):
|
||||
"""Retrieve actions for the given instance."""
|
||||
@ -383,89 +378,54 @@ class API(base.Base):
|
||||
|
||||
def suspend(self, context, instance_id):
|
||||
"""suspend the instance with instance_id"""
|
||||
instance = self.get(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}})
|
||||
self._cast_compute_message('suspend_instance', context, instance_id)
|
||||
|
||||
def resume(self, context, instance_id):
|
||||
"""resume the instance with instance_id"""
|
||||
instance = self.get(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}})
|
||||
self._cast_compute_message('resume_instance', context, instance_id)
|
||||
|
||||
def rescue(self, context, instance_id):
|
||||
"""Rescue the given instance."""
|
||||
instance = self.get(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}})
|
||||
self._cast_compute_message('rescue_instance', context, instance_id)
|
||||
|
||||
def unrescue(self, context, instance_id):
|
||||
"""Unrescue the given instance."""
|
||||
instance = self.get(context, instance_id)
|
||||
host = instance['host']
|
||||
rpc.cast(context,
|
||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
||||
{"method": "unrescue_instance",
|
||||
"args": {"instance_id": instance['id']}})
|
||||
self._cast_compute_message('unrescue_instance', context, instance_id)
|
||||
|
||||
def set_admin_password(self, context, instance_id):
|
||||
"""Set the root/admin password for the given instance."""
|
||||
self._cast_compute_message('set_admin_password', context, instance_id)
|
||||
|
||||
def get_ajax_console(self, context, instance_id):
|
||||
"""Get a url to an AJAX Console"""
|
||||
|
||||
instance = self.get(context, instance_id)
|
||||
|
||||
output = rpc.call(context,
|
||||
'%s.%s' % (FLAGS.compute_topic,
|
||||
instance['host']),
|
||||
{'method': 'get_ajax_console',
|
||||
'args': {'instance_id': instance['id']}})
|
||||
|
||||
output = self._call_compute_message('get_ajax_console',
|
||||
context,
|
||||
instance_id)
|
||||
rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic,
|
||||
{'method': 'authorize_ajax_console',
|
||||
'args': {'token': output['token'], 'host': output['host'],
|
||||
'port': output['port']}})
|
||||
|
||||
return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url,
|
||||
output['token'])}
|
||||
|
||||
def lock(self, context, instance_id):
|
||||
"""
|
||||
lock the instance with instance_id
|
||||
def get_console_output(self, context, instance_id):
|
||||
"""Get console output for an an instance"""
|
||||
return self._call_compute_message('get_console_output',
|
||||
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": "lock_instance",
|
||||
"args": {"instance_id": instance['id']}})
|
||||
def lock(self, context, instance_id):
|
||||
"""lock the instance with instance_id"""
|
||||
self._cast_compute_message('lock_instance', context, instance_id)
|
||||
|
||||
def unlock(self, context, instance_id):
|
||||
"""
|
||||
unlock the instance with 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']}})
|
||||
"""unlock the instance with instance_id"""
|
||||
self._cast_compute_message('unlock_instance', context, instance_id)
|
||||
|
||||
def get_lock(self, context, instance_id):
|
||||
"""
|
||||
return the boolean state of (instance with instance_id)'s lock
|
||||
|
||||
"""
|
||||
instance = self.get_instance(context, instance_id)
|
||||
"""return the boolean state of (instance with instance_id)'s lock"""
|
||||
instance = self.get(context, instance_id)
|
||||
return instance['locked']
|
||||
|
||||
def attach_volume(self, context, instance_id, volume_id, device):
|
||||
|
@ -35,6 +35,8 @@ terminating it.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import random
|
||||
import string
|
||||
import logging
|
||||
import socket
|
||||
import functools
|
||||
@ -54,6 +56,8 @@ flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection',
|
||||
'Driver to use for controlling virtualization')
|
||||
flags.DEFINE_string('stub_network', False,
|
||||
'Stub network related code')
|
||||
flags.DEFINE_integer('password_length', 12,
|
||||
'Length of generated admin passwords')
|
||||
flags.DEFINE_string('console_host', socket.gethostname(),
|
||||
'Console proxy host to use to connect to instances on'
|
||||
'this host.')
|
||||
@ -309,6 +313,35 @@ class ComputeManager(manager.Manager):
|
||||
|
||||
self.driver.snapshot(instance_ref, name)
|
||||
|
||||
@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
|
||||
@checks_instance_lock
|
||||
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)
|
||||
|
||||
|
||||
def fixed_ip_get_instance_v6(context, address):
|
||||
return IMPL.fixed_ip_get_instance_v6(context, address)
|
||||
|
||||
|
||||
def fixed_ip_get_network(context, address):
|
||||
"""Get a network for a fixed ip by 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)
|
||||
|
||||
|
||||
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):
|
||||
"""Get the first floating ip address of an instance."""
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
def fixed_ip_get_network(context, address):
|
||||
fixed_ip_ref = fixed_ip_get_by_address(context, address)
|
||||
@ -793,6 +804,17 @@ def instance_get_fixed_address(context, instance_id):
|
||||
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
|
||||
def instance_get_floating_address(context, instance_id):
|
||||
session = get_session()
|
||||
@ -1130,6 +1152,11 @@ def project_get_network(context, project_id, associate=True):
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def project_get_network_v6(context, project_id):
|
||||
return project_get_network(context, project_id)
|
||||
|
||||
|
||||
###################
|
||||
|
||||
|
||||
|
@ -366,6 +366,10 @@ class Network(BASE, NovaBase):
|
||||
|
||||
injected = Column(Boolean, default=False)
|
||||
cidr = Column(String(255), unique=True)
|
||||
cidr_v6 = Column(String(255), unique=True)
|
||||
|
||||
ra_server = Column(String(255))
|
||||
|
||||
netmask = Column(String(255))
|
||||
bridge = Column(String(255))
|
||||
gateway = Column(String(255))
|
||||
|
@ -76,6 +76,10 @@ class InvalidInputException(Error):
|
||||
pass
|
||||
|
||||
|
||||
class TimeoutException(Error):
|
||||
pass
|
||||
|
||||
|
||||
def wrap_exception(f):
|
||||
def _wrap(*args, **kw):
|
||||
try:
|
||||
|
@ -50,6 +50,7 @@ flags.DEFINE_string('routing_source_ip', '$my_ip',
|
||||
'Public IP of network host')
|
||||
flags.DEFINE_bool('use_nova_chains', False,
|
||||
'use the nova_ routing chains instead of default')
|
||||
|
||||
flags.DEFINE_string('dns_server', None,
|
||||
'if set, uses specific dns server for dnsmasq')
|
||||
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['broadcast'],
|
||||
net_attrs['netmask']))
|
||||
if(FLAGS.use_ipv6):
|
||||
_execute("sudo ifconfig %s add %s up" % \
|
||||
(bridge,
|
||||
net_attrs['cidr_v6']))
|
||||
else:
|
||||
_execute("sudo ifconfig %s up" % bridge)
|
||||
if FLAGS.use_nova_chains:
|
||||
@ -262,6 +267,50 @@ def update_dhcp(context, network_id):
|
||||
_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):
|
||||
"""Return a host string for an address"""
|
||||
instance_ref = fixed_ip_ref['instance']
|
||||
@ -323,6 +372,15 @@ def _dnsmasq_cmd(net):
|
||||
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):
|
||||
"""Stops the dnsmasq instance for a given network"""
|
||||
pid = _dnsmasq_pid_for(network)
|
||||
@ -344,6 +402,16 @@ def _dhcp_file(bridge, 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):
|
||||
"""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):
|
||||
with open(pid_file, 'r') as f:
|
||||
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',
|
||||
'Floating 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,
|
||||
'Number of addresses reserved for vpn clients')
|
||||
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')
|
||||
flags.DEFINE_integer('fixed_ip_disassociate_timeout', 600,
|
||||
'Seconds after which a deallocated ip is disassociated')
|
||||
|
||||
flags.DEFINE_bool('use_ipv6', True,
|
||||
'use the ipv6')
|
||||
flags.DEFINE_string('network_host', socket.gethostname(),
|
||||
'Network host to use for ip allocation in flat modes')
|
||||
flags.DEFINE_bool('fake_call', False,
|
||||
@ -235,8 +239,8 @@ class NetworkManager(manager.Manager):
|
||||
"""Get the network host for the current context."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_networks(self, context, num_networks, network_size,
|
||||
*args, **kwargs):
|
||||
def create_networks(self, context, cidr, num_networks, network_size,
|
||||
cidr_v6, *args, **kwargs):
|
||||
"""Create networks based on parameters."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@ -321,9 +325,11 @@ class FlatManager(NetworkManager):
|
||||
pass
|
||||
|
||||
def create_networks(self, context, cidr, num_networks, network_size,
|
||||
*args, **kwargs):
|
||||
cidr_v6, *args, **kwargs):
|
||||
"""Create networks based on parameters."""
|
||||
fixed_net = IPy.IP(cidr)
|
||||
fixed_net_v6 = IPy.IP(cidr_v6)
|
||||
significant_bits_v6 = 64
|
||||
for index in range(num_networks):
|
||||
start = index * network_size
|
||||
significant_bits = 32 - int(math.log(network_size, 2))
|
||||
@ -336,7 +342,13 @@ class FlatManager(NetworkManager):
|
||||
net['gateway'] = str(project_net[1])
|
||||
net['broadcast'] = str(project_net.broadcast())
|
||||
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)
|
||||
|
||||
if network_ref:
|
||||
self._create_fixed_ips(context, network_ref['id'])
|
||||
|
||||
@ -482,12 +494,16 @@ class VlanManager(NetworkManager):
|
||||
network_ref['bridge'])
|
||||
|
||||
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."""
|
||||
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):
|
||||
vlan = vlan_start + index
|
||||
start = index * network_size
|
||||
start_v6 = index * network_size_v6
|
||||
significant_bits = 32 - int(math.log(network_size, 2))
|
||||
cidr = "%s/%s" % (fixed_net[start], significant_bits)
|
||||
project_net = IPy.IP(cidr)
|
||||
@ -500,6 +516,13 @@ class VlanManager(NetworkManager):
|
||||
net['dhcp_start'] = str(project_net[3])
|
||||
net['vlan'] = 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
|
||||
# robust solution would be to make them unique per ip
|
||||
net['vpn_public_port'] = vpn_start + index
|
||||
@ -538,6 +561,7 @@ class VlanManager(NetworkManager):
|
||||
self.driver.ensure_vlan_bridge(network_ref['vlan'],
|
||||
network_ref['bridge'],
|
||||
network_ref)
|
||||
|
||||
# NOTE(vish): only ensure this forward if the address hasn't been set
|
||||
# manually.
|
||||
if address == FLAGS.vpn_ip:
|
||||
@ -546,6 +570,8 @@ class VlanManager(NetworkManager):
|
||||
network_ref['vpn_private_address'])
|
||||
if not FLAGS.fake_network:
|
||||
self.driver.update_dhcp(context, network_id)
|
||||
if(FLAGS.use_ipv6):
|
||||
self.driver.update_ra(context, network_id)
|
||||
|
||||
@property
|
||||
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 sys
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import mox
|
||||
import stubout
|
||||
from twisted.internet import defer
|
||||
from twisted.trial import unittest as trial_unittest
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
@ -74,7 +70,8 @@ class TestCase(unittest.TestCase):
|
||||
FLAGS.fixed_range,
|
||||
5, 16,
|
||||
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
|
||||
# because it screws with our generators
|
||||
@ -139,95 +136,3 @@ class TestCase(unittest.TestCase):
|
||||
|
||||
_wrapped.func_name = self.originalAttach.func_name
|
||||
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
|
||||
|
||||
|
||||
class XmlConversionTestCase(test.TrialTestCase):
|
||||
class XmlConversionTestCase(test.TestCase):
|
||||
"""Unit test api xml conversion"""
|
||||
def test_number_conversion(self):
|
||||
conv = apirequest._try_convert
|
||||
@ -96,7 +96,7 @@ class XmlConversionTestCase(test.TrialTestCase):
|
||||
self.assertEqual(conv('-0'), 0)
|
||||
|
||||
|
||||
class ApiEc2TestCase(test.TrialTestCase):
|
||||
class ApiEc2TestCase(test.TestCase):
|
||||
"""Unit test for the cloud controller on an EC2 API"""
|
||||
def setUp(self):
|
||||
super(ApiEc2TestCase, self).setUp()
|
||||
@ -265,6 +265,72 @@ class ApiEc2TestCase(test.TrialTestCase):
|
||||
|
||||
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):
|
||||
"""
|
||||
Test that we can grant and revoke another security group access
|
||||
|
@ -151,6 +151,13 @@ class ComputeTestCase(test.TestCase):
|
||||
self.compute.reboot_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):
|
||||
"""Ensure instance can be snapshotted"""
|
||||
instance_id = self._create_instance()
|
||||
|
@ -9,7 +9,7 @@ def _fake_context():
|
||||
return context.RequestContext(1, 1)
|
||||
|
||||
|
||||
class RootLoggerTestCase(test.TrialTestCase):
|
||||
class RootLoggerTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(RootLoggerTestCase, self).setUp()
|
||||
self.log = log.logging.root
|
||||
@ -46,7 +46,7 @@ class RootLoggerTestCase(test.TrialTestCase):
|
||||
self.assert_(True) # didn't raise exception
|
||||
|
||||
|
||||
class NovaFormatterTestCase(test.TrialTestCase):
|
||||
class NovaFormatterTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(NovaFormatterTestCase, self).setUp()
|
||||
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())
|
||||
|
||||
|
||||
class NovaLoggerTestCase(test.TrialTestCase):
|
||||
class NovaLoggerTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(NovaLoggerTestCase, self).setUp()
|
||||
self.flags(default_log_levels=["nova-test=AUDIT"], verbose=False)
|
||||
@ -96,7 +96,7 @@ class NovaLoggerTestCase(test.TrialTestCase):
|
||||
self.assertEqual(log.AUDIT, l.level)
|
||||
|
||||
|
||||
class VerboseLoggerTestCase(test.TrialTestCase):
|
||||
class VerboseLoggerTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(VerboseLoggerTestCase, self).setUp()
|
||||
self.flags(default_log_levels=["nova.test=AUDIT"], verbose=True)
|
||||
|
@ -38,7 +38,7 @@ def conditional_forbid(req):
|
||||
return 'OK'
|
||||
|
||||
|
||||
class LockoutTestCase(test.TrialTestCase):
|
||||
class LockoutTestCase(test.TestCase):
|
||||
"""Test case for the Lockout middleware."""
|
||||
def setUp(self): # pylint: disable-msg=C0103
|
||||
super(LockoutTestCase, self).setUp()
|
||||
|
@ -96,6 +96,28 @@ class NetworkTestCase(test.TestCase):
|
||||
self.context.project_id = self.projects[project_num].id
|
||||
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):
|
||||
"""Makes sure that we can allocaate a public ip"""
|
||||
# TODO(vish): better way of adding floating ips
|
||||
|
@ -28,7 +28,7 @@ from nova import test
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class TwistdTestCase(test.TrialTestCase):
|
||||
class TwistdTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TwistdTestCase, self).setUp()
|
||||
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.xenapi import fake as xenapi_fake
|
||||
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.xenapi import stubs
|
||||
|
||||
@ -262,3 +263,29 @@ class XenAPIVMTestCase(test.TestCase):
|
||||
instance = db.instance_create(values)
|
||||
self.conn.spawn(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()
|
||||
|
@ -30,6 +30,8 @@ import struct
|
||||
import sys
|
||||
import time
|
||||
from xml.sax import saxutils
|
||||
import re
|
||||
import netaddr
|
||||
|
||||
from eventlet import event
|
||||
from eventlet import greenthread
|
||||
@ -200,6 +202,40 @@ def last_octet(address):
|
||||
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():
|
||||
"""Overridable version of datetime.datetime.utcnow."""
|
||||
if utcnow.override_time:
|
||||
|
@ -98,7 +98,7 @@ class FakeConnection(object):
|
||||
the new instance.
|
||||
|
||||
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
|
||||
running (power_state.RUNNING).
|
||||
@ -122,7 +122,7 @@ class FakeConnection(object):
|
||||
The second parameter is the name of the snapshot.
|
||||
|
||||
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
|
||||
|
||||
@ -134,7 +134,20 @@ class FakeConnection(object):
|
||||
and so the instance is being specified as instance.name.
|
||||
|
||||
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
|
||||
|
||||
@ -182,7 +195,7 @@ class FakeConnection(object):
|
||||
and so the instance is being specified as instance.name.
|
||||
|
||||
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]
|
||||
|
||||
|
@ -76,6 +76,7 @@
|
||||
<filterref filter="nova-instance-${name}">
|
||||
<parameter name="IP" value="${ip_address}" />
|
||||
<parameter name="DHCPSERVER" value="${dhcp_server}" />
|
||||
<parameter name="RASERVER" value="${ra_server}" />
|
||||
#if $getVar('extra_params', False)
|
||||
${extra_params}
|
||||
#end if
|
||||
|
@ -130,6 +130,16 @@ def _get_net_and_mask(cidr):
|
||||
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):
|
||||
|
||||
def __init__(self, read_only):
|
||||
@ -375,7 +385,6 @@ class LibvirtConnection(object):
|
||||
instance['id'],
|
||||
power_state.NOSTATE,
|
||||
'launching')
|
||||
|
||||
self.nwfilter.setup_basic_filtering(instance)
|
||||
self.firewall_driver.prepare_instance_filter(instance)
|
||||
self._create_image(instance, xml)
|
||||
@ -603,12 +612,16 @@ class LibvirtConnection(object):
|
||||
if network_ref['injected']:
|
||||
admin_context = context.get_admin_context()
|
||||
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:
|
||||
net = f.read() % {'address': address,
|
||||
'netmask': network_ref['netmask'],
|
||||
'gateway': network_ref['gateway'],
|
||||
'broadcast': network_ref['broadcast'],
|
||||
'dns': network_ref['dns']}
|
||||
'dns': network_ref['dns'],
|
||||
'ra_server': ra_server}
|
||||
if key or net:
|
||||
if key:
|
||||
LOG.info(_('instance %s: injecting key into image %s'),
|
||||
@ -644,13 +657,30 @@ class LibvirtConnection(object):
|
||||
instance['id'])
|
||||
# Assume that the gateway also acts as the dhcp server.
|
||||
dhcp_server = network['gateway']
|
||||
|
||||
ra_server = network['ra_server']
|
||||
if not ra_server:
|
||||
ra_server = 'fd00::'
|
||||
if FLAGS.allow_project_net_traffic:
|
||||
net, mask = _get_net_and_mask(network['cidr'])
|
||||
extra_params = ("<parameter name=\"PROJNET\" "
|
||||
if FLAGS.use_ipv6:
|
||||
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"
|
||||
"<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:
|
||||
extra_params = "\n"
|
||||
if FLAGS.use_cow_images:
|
||||
@ -668,6 +698,7 @@ class LibvirtConnection(object):
|
||||
'mac_address': instance['mac_address'],
|
||||
'ip_address': ip_address,
|
||||
'dhcp_server': dhcp_server,
|
||||
'ra_server': ra_server,
|
||||
'extra_params': extra_params,
|
||||
'rescue': rescue,
|
||||
'local': instance_type['local_gb'],
|
||||
@ -931,6 +962,15 @@ class NWFilterFirewall(FirewallDriver):
|
||||
</rule>
|
||||
</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):
|
||||
"""Set up basic filtering (MAC, IP, and ARP spoofing protection)"""
|
||||
logging.info('called setup_basic_filtering in nwfilter')
|
||||
@ -955,13 +995,17 @@ class NWFilterFirewall(FirewallDriver):
|
||||
['no-mac-spoofing',
|
||||
'no-ip-spoofing',
|
||||
'no-arp-spoofing',
|
||||
'allow-dhcp-server']))
|
||||
'allow-dhcp-server'
|
||||
]))
|
||||
self._define_filter(self.nova_base_ipv4_filter)
|
||||
self._define_filter(self.nova_base_ipv6_filter)
|
||||
self._define_filter(self.nova_dhcp_filter)
|
||||
self._define_filter(self.nova_ra_filter)
|
||||
self._define_filter(self.nova_vpn_filter)
|
||||
if FLAGS.allow_project_net_traffic:
|
||||
self._define_filter(self.nova_project_filter)
|
||||
if FLAGS.use_ipv6:
|
||||
self._define_filter(self.nova_project_filter_v6)
|
||||
|
||||
self.static_filters_configured = True
|
||||
|
||||
@ -993,13 +1037,13 @@ class NWFilterFirewall(FirewallDriver):
|
||||
|
||||
def nova_base_ipv6_filter(self):
|
||||
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),
|
||||
('in', 'drop', 400)]:
|
||||
retval += """<rule action='%s' direction='%s' priority='%d'>
|
||||
<%s-ipv6 />
|
||||
<%s />
|
||||
</rule>""" % (action, direction,
|
||||
priority, protocol)
|
||||
priority, protocol)
|
||||
retval += '</filter>'
|
||||
return retval
|
||||
|
||||
@ -1012,10 +1056,20 @@ class NWFilterFirewall(FirewallDriver):
|
||||
retval += '</filter>'
|
||||
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):
|
||||
if callable(xml):
|
||||
xml = xml()
|
||||
|
||||
# execute in a native thread and block current greenthread until done
|
||||
tpool.execute(self._conn.nwfilterDefineXML, xml)
|
||||
|
||||
@ -1029,7 +1083,6 @@ class NWFilterFirewall(FirewallDriver):
|
||||
it makes sure the filters for the security groups as well as
|
||||
the base filter are all in place.
|
||||
"""
|
||||
|
||||
if instance['image_id'] == FLAGS.vpn_image_id:
|
||||
base_filter = 'nova-vpn'
|
||||
else:
|
||||
@ -1041,11 +1094,15 @@ class NWFilterFirewall(FirewallDriver):
|
||||
instance_secgroup_filter_children = ['nova-base-ipv4',
|
||||
'nova-base-ipv6',
|
||||
'nova-allow-dhcp-server']
|
||||
if FLAGS.use_ipv6:
|
||||
instance_secgroup_filter_children += ['nova-allow-ra-server']
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
if FLAGS.allow_project_net_traffic:
|
||||
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,
|
||||
instance['id']):
|
||||
@ -1073,12 +1130,19 @@ class NWFilterFirewall(FirewallDriver):
|
||||
security_group = db.security_group_get(context.get_admin_context(),
|
||||
security_group_id)
|
||||
rule_xml = ""
|
||||
v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'}
|
||||
for rule in security_group.rules:
|
||||
rule_xml += "<rule action='accept' direction='in' priority='300'>"
|
||||
if rule.cidr:
|
||||
net, mask = _get_net_and_mask(rule.cidr)
|
||||
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
|
||||
(rule.protocol, net, mask)
|
||||
version = _get_ip_version(rule.cidr)
|
||||
if(FLAGS.use_ipv6 and version == 6):
|
||||
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']:
|
||||
rule_xml += "dstportstart='%s' dstportend='%s' " % \
|
||||
(rule.from_port, rule.to_port)
|
||||
@ -1093,8 +1157,11 @@ class NWFilterFirewall(FirewallDriver):
|
||||
|
||||
rule_xml += '/>\n'
|
||||
rule_xml += "</rule>\n"
|
||||
xml = "<filter name='nova-secgroup-%s' chain='ipv4'>%s</filter>" % \
|
||||
(security_group_id, rule_xml,)
|
||||
xml = "<filter name='nova-secgroup-%s' " % security_group_id
|
||||
if(FLAGS.use_ipv6):
|
||||
xml += "chain='root'>%s</filter>" % rule_xml
|
||||
else:
|
||||
xml += "chain='ipv4'>%s</filter>" % rule_xml
|
||||
return xml
|
||||
|
||||
def _instance_filter_name(self, instance):
|
||||
@ -1131,11 +1198,17 @@ class IptablesFirewallDriver(FirewallDriver):
|
||||
def apply_ruleset(self):
|
||||
current_filter, _ = self.execute('sudo iptables-save -t filter')
|
||||
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',
|
||||
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()
|
||||
# Remove any trace of nova rules.
|
||||
new_filter = filter(lambda l: 'nova-' not in l, current_lines)
|
||||
@ -1149,8 +1222,8 @@ class IptablesFirewallDriver(FirewallDriver):
|
||||
if not new_filter[rules_index].startswith(':'):
|
||||
break
|
||||
|
||||
our_chains = [':nova-ipv4-fallback - [0:0]']
|
||||
our_rules = ['-A nova-ipv4-fallback -j DROP']
|
||||
our_chains = [':nova-fallback - [0:0]']
|
||||
our_rules = ['-A nova-fallback -j DROP']
|
||||
|
||||
our_chains += [':nova-local - [0:0]']
|
||||
our_rules += ['-A FORWARD -j nova-local']
|
||||
@ -1161,7 +1234,10 @@ class IptablesFirewallDriver(FirewallDriver):
|
||||
for instance_id in self.instances:
|
||||
instance = self.instances[instance_id]
|
||||
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]
|
||||
|
||||
@ -1188,13 +1264,19 @@ class IptablesFirewallDriver(FirewallDriver):
|
||||
|
||||
our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)]
|
||||
|
||||
# Allow DHCP responses
|
||||
dhcp_server = self._dhcp_server_for_instance(instance)
|
||||
our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' %
|
||||
(chain_name, dhcp_server)]
|
||||
if(ip_version == 4):
|
||||
# Allow DHCP responses
|
||||
dhcp_server = self._dhcp_server_for_instance(instance)
|
||||
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
|
||||
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
|
||||
for security_group_id in security_groups:
|
||||
@ -1207,15 +1289,22 @@ class IptablesFirewallDriver(FirewallDriver):
|
||||
|
||||
for rule in rules:
|
||||
logging.info('%r', rule)
|
||||
args = ['-A', chain_name, '-p', rule.protocol]
|
||||
|
||||
if rule.cidr:
|
||||
args += ['-s', rule.cidr]
|
||||
else:
|
||||
if not rule.cidr:
|
||||
# Eventually, a mechanism to grant access for security
|
||||
# groups will turn up here. It'll use ipsets.
|
||||
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.from_port == rule.to_port:
|
||||
args += ['--dport', '%s' % (rule.from_port,)]
|
||||
@ -1235,7 +1324,12 @@ class IptablesFirewallDriver(FirewallDriver):
|
||||
icmp_type_arg += '/%s' % icmp_code
|
||||
|
||||
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']
|
||||
our_rules += [' '.join(args)]
|
||||
@ -1261,7 +1355,16 @@ class IptablesFirewallDriver(FirewallDriver):
|
||||
return db.instance_get_fixed_address(context.get_admin_context(),
|
||||
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):
|
||||
network = db.project_get_network(context.get_admin_context(),
|
||||
instance['project_id'])
|
||||
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 M2Crypto
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import uuid
|
||||
|
||||
from nova import db
|
||||
from nova import context
|
||||
@ -127,12 +132,31 @@ class VMOps(object):
|
||||
"""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.
|
||||
"""
|
||||
vm = None
|
||||
try:
|
||||
instance_name = instance_or_vm.name
|
||||
vm = VMHelper.lookup(self._session, instance_name)
|
||||
except AttributeError:
|
||||
# A vm opaque ref was passed
|
||||
vm = instance_or_vm
|
||||
if instance_or_vm.startswith("OpaqueRef:"):
|
||||
# Got passed an opaque ref; return it
|
||||
return instance_or_vm
|
||||
else:
|
||||
# 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:
|
||||
raise Exception(_('Instance not present %s') % instance_name)
|
||||
return vm
|
||||
@ -189,6 +213,44 @@ class VMOps(object):
|
||||
task = self._session.call_xenapi('Async.VM.clean_reboot', vm)
|
||||
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):
|
||||
"""Destroy VM instance"""
|
||||
vm = VMHelper.lookup(self._session, instance.name)
|
||||
@ -246,30 +308,19 @@ class VMOps(object):
|
||||
|
||||
def suspend(self, instance, callback):
|
||||
"""suspend the specified instance"""
|
||||
instance_name = instance.name
|
||||
vm = VMHelper.lookup(self._session, instance_name)
|
||||
if vm is None:
|
||||
raise Exception(_("suspend: instance not present %s") %
|
||||
instance_name)
|
||||
vm = self._get_vm_opaque_ref(instance)
|
||||
task = self._session.call_xenapi('Async.VM.suspend', vm)
|
||||
self._wait_with_callback(instance.id, task, callback)
|
||||
|
||||
def resume(self, instance, callback):
|
||||
"""resume the specified instance"""
|
||||
instance_name = instance.name
|
||||
vm = VMHelper.lookup(self._session, instance_name)
|
||||
if vm is None:
|
||||
raise Exception(_("resume: instance not present %s") %
|
||||
instance_name)
|
||||
vm = self._get_vm_opaque_ref(instance)
|
||||
task = self._session.call_xenapi('Async.VM.resume', vm, False, True)
|
||||
self._wait_with_callback(instance.id, task, callback)
|
||||
|
||||
def get_info(self, instance_id):
|
||||
def get_info(self, instance):
|
||||
"""Return data about VM instance"""
|
||||
vm = VMHelper.lookup(self._session, instance_id)
|
||||
if vm is None:
|
||||
raise exception.NotFound(_('Instance not'
|
||||
' found %s') % instance_id)
|
||||
vm = self._get_vm_opaque_ref(instance)
|
||||
rec = self._session.get_xenapi().VM.get_record(vm)
|
||||
return VMHelper.compile_info(rec)
|
||||
|
||||
@ -333,22 +384,34 @@ class VMOps(object):
|
||||
return self._make_plugin_call('xenstore.py', method=method, vm=vm,
|
||||
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={}):
|
||||
"""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.
|
||||
"""
|
||||
instance_id = vm.id
|
||||
vm = self._get_vm_opaque_ref(vm)
|
||||
rec = self._session.get_xenapi().VM.get_record(vm)
|
||||
args = {'dom_id': rec['domid'], 'path': path}
|
||||
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:
|
||||
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:
|
||||
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
|
||||
|
||||
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."""
|
||||
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')
|
||||
|
@ -149,6 +149,10 @@ class XenAPIConnection(object):
|
||||
"""Reboot VM 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):
|
||||
"""Destroy VM instance"""
|
||||
self._vmops.destroy(instance)
|
||||
@ -266,7 +270,8 @@ class XenAPISession(object):
|
||||
|
||||
def _poll_task(self, id, task, done):
|
||||
"""Poll the given XenAPI task, and fire the given action if we
|
||||
get a result."""
|
||||
get a result.
|
||||
"""
|
||||
try:
|
||||
name = self._session.xenapi.task.get_name_label(task)
|
||||
status = self._session.xenapi.task.get_status(task)
|
||||
|
@ -371,3 +371,52 @@ class RBDDriver(VolumeDriver):
|
||||
def undiscover_volume(self, volume):
|
||||
"""Undiscover volume on a remote host"""
|
||||
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
|
||||
|
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
|
||||
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_PROJECTNAME = '%sproject' % TEST_PREFIX
|
||||
|
||||
@ -96,4 +96,3 @@ class UserTests(AdminSmokeTestCase):
|
||||
if __name__ == "__main__":
|
||||
suites = {'user': unittest.makeSuite(UserTests)}
|
||||
sys.exit(base.run_tests(suites))
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
# under the License.
|
||||
|
||||
import boto
|
||||
import boto_v6
|
||||
import commands
|
||||
import httplib
|
||||
import os
|
||||
@ -69,6 +70,17 @@ class SmokeTestCase(unittest.TestCase):
|
||||
'test.')
|
||||
|
||||
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,
|
||||
aws_secret_access_key=secret_key,
|
||||
is_secure=parts['is_secure'],
|
||||
@ -115,7 +127,8 @@ class SmokeTestCase(unittest.TestCase):
|
||||
return True
|
||||
|
||||
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)
|
||||
if status != 0:
|
||||
print '%s -> \n %s' % (cmd, output)
|
||||
@ -130,6 +143,7 @@ class SmokeTestCase(unittest.TestCase):
|
||||
raise Exception(output)
|
||||
return True
|
||||
|
||||
|
||||
def run_tests(suites):
|
||||
argv = FLAGS(sys.argv)
|
||||
|
||||
@ -151,4 +165,3 @@ def run_tests(suites):
|
||||
else:
|
||||
for suite in suites.itervalues():
|
||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
|
||||
|
@ -33,6 +33,7 @@ DEFINE_bool = DEFINE_bool
|
||||
# __GLOBAL FLAGS ONLY__
|
||||
# Define any app-specific flags in their own files, docs at:
|
||||
# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
|
||||
|
||||
DEFINE_string('region', 'nova', 'Region to use')
|
||||
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',
|
||||
'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_KEY = '%s_key' % TEST_PREFIX
|
||||
TEST_GROUP = '%s_group' % TEST_PREFIX
|
||||
@ -80,7 +80,7 @@ class ImageTests(UserSmokeTestCase):
|
||||
|
||||
def test_006_can_register_kernel(self):
|
||||
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.data['kernel_id'] = kernel_id
|
||||
|
||||
@ -92,7 +92,7 @@ class ImageTests(UserSmokeTestCase):
|
||||
time.sleep(1)
|
||||
else:
|
||||
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')
|
||||
|
||||
for i in xrange(10):
|
||||
@ -101,7 +101,7 @@ class ImageTests(UserSmokeTestCase):
|
||||
break
|
||||
time.sleep(1)
|
||||
else:
|
||||
self.assert_(False) # wasn't available within 10 seconds
|
||||
self.assert_(False) # wasn't available within 10 seconds
|
||||
self.assert_(kernel.type == 'kernel')
|
||||
|
||||
def test_008_can_describe_image_attribute(self):
|
||||
@ -152,14 +152,17 @@ class InstanceTests(UserSmokeTestCase):
|
||||
for x in xrange(60):
|
||||
instance.update()
|
||||
if instance.state == u'running':
|
||||
break
|
||||
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
|
||||
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):
|
||||
for x in xrange(120):
|
||||
@ -171,6 +174,16 @@ class InstanceTests(UserSmokeTestCase):
|
||||
else:
|
||||
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):
|
||||
for x in xrange(30):
|
||||
try:
|
||||
@ -183,6 +196,19 @@ class InstanceTests(UserSmokeTestCase):
|
||||
else:
|
||||
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):
|
||||
result = self.conn.allocate_address()
|
||||
self.assertTrue(hasattr(result, 'public_ip'))
|
||||
@ -388,7 +414,6 @@ class SecurityGroupTests(UserSmokeTestCase):
|
||||
raise Exception("Timeout")
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def test_999_tearDown(self):
|
||||
self.conn.delete_key_pair(TEST_KEY)
|
||||
self.conn.delete_security_group(TEST_GROUP)
|
||||
|
@ -26,3 +26,4 @@ Twisted>=10.1.0
|
||||
PasteDeploy
|
||||
paste
|
||||
sqlalchemy-migrate
|
||||
netaddr
|
||||
|
Loading…
Reference in New Issue
Block a user