Merge from trunk and merge conflict resolution

This commit is contained in:
Cerberus
2011-02-18 16:45:31 -06:00
25 changed files with 591 additions and 86 deletions
+28 -22
View File
@@ -1,37 +1,43 @@
# Format is:
# <preferred e-mail> <other e-mail>
<code@term.ie> <github@anarkystic.com>
<code@term.ie> <termie@preciousroy.local>
<Armando.Migliaccio@eu.citrix.com> <armando.migliaccio@citrix.com>
<matt.dietz@rackspace.com> <matthewdietz@Matthew-Dietzs-MacBook-Pro.local>
<matt.dietz@rackspace.com> <mdietz@openstack>
<cbehrens@codestud.com> <chris.behrens@rackspace.com>
<devin.carlen@gmail.com> <devcamcar@illian.local>
<ewan.mellor@citrix.com> <emellor@silver>
<jaypipes@gmail.com> <jpipes@serialcoder>
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>
<anotherjesse@gmail.com> <jesse@dancelamb>
<anotherjesse@gmail.com> <jesse@gigantor.local>
<anotherjesse@gmail.com> <jesse@ubuntu>
<jmckenty@gmail.com> <jmckenty@yyj-dhcp171.corp.flock.com>
<ant@openstack.org> <amesserl@rackspace.com>
<Armando.Migliaccio@eu.citrix.com> <armando.migliaccio@citrix.com>
<brian.lamar@rackspace.com> <brian.lamar@gmail.com>
<bschott@isi.edu> <bfschott@gmail.com>
<cbehrens@codestud.com> <chris.behrens@rackspace.com>
<chiradeep@cloud.com> <chiradeep@chiradeep-lt2>
<code@term.ie> <github@anarkystic.com>
<code@term.ie> <termie@preciousroy.local>
<corywright@gmail.com> <cory.wright@rackspace.com>
<devin.carlen@gmail.com> <devcamcar@illian.local>
<ewan.mellor@citrix.com> <emellor@silver>
<jaypipes@gmail.com> <jpipes@serialcoder>
<jmckenty@gmail.com> <jmckenty@joshua-mckentys-macbook-pro.local>
<jmckenty@gmail.com> <jmckenty@yyj-dhcp171.corp.flock.com>
<jmckenty@gmail.com> <joshua.mckenty@nasa.gov>
<justin@fathomdb.com> <justinsb@justinsb-desktop>
<masumotok@nttdata.co.jp> <root@openstack2-api>
<justin@fathomdb.com> <superstack@superstack.org>
<masumotok@nttdata.co.jp> Masumoto<masumotok@nttdata.co.jp>
<masumotok@nttdata.co.jp> <root@openstack2-api>
<matt.dietz@rackspace.com> <matthewdietz@Matthew-Dietzs-MacBook-Pro.local>
<matt.dietz@rackspace.com> <mdietz@openstack>
<mordred@inaugust.com> <mordred@hudson>
<paul@openstack.org> <pvoccio@castor.local>
<paul@openstack.org> <paul.voccio@rackspace.com>
<paul@openstack.org> <pvoccio@castor.local>
<rconradharris@gmail.com> <rick.harris@rackspace.com>
<rlane@wikimedia.org> <laner@controller>
<sleepsonthefloor@gmail.com> <root@tonbuntu>
<soren.hansen@rackspace.com> <soren@linux2go.dk>
<todd@ansolabs.com> <todd@lapex>
<todd@ansolabs.com> <todd@rubidine.com>
<vishvananda@gmail.com> <vishvananda@yahoo.com>
<tushar.vitthal.patil@gmail.com> <tpatil@vertex.co.in>
<ueno.nachi@lab.ntt.co.jp> <nati.ueno@gmail.com>
<ueno.nachi@lab.ntt.co.jp> <nova@u4>
<ueno.nachi@lab.ntt.co.jp> <openstack@lab.ntt.co.jp>
<vishvananda@gmail.com> <root@mirror.nasanebula.net>
<vishvananda@gmail.com> <root@ubuntu>
<sleepsonthefloor@gmail.com> <root@tonbuntu>
<rlane@wikimedia.org> <laner@controller>
<rconradharris@gmail.com> <rick.harris@rackspace.com>
<corywright@gmail.com> <cory.wright@rackspace.com>
<ant@openstack.org> <amesserl@rackspace.com>
<chiradeep@cloud.com> <chiradeep@chiradeep-lt2>
<justin@fathomdb.com> <superstack@superstack.org>
<brian.lamar@rackspace.com> <brian.lamar@gmail.com>
<vishvananda@gmail.com> <vishvananda@yahoo.com>
+5 -5
View File
@@ -3,17 +3,17 @@ Anne Gentle <anne@openstack.org>
Anthony Young <sleepsonthefloor@gmail.com>
Antony Messerli <ant@openstack.org>
Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
Brian Waldon <brian.waldon@rackspace.com>
Bilal Akhtar <bilalakhtar@ubuntu.com>
Brian Lamar <brian.lamar@rackspace.com>
Brian Schott <bschott@isi.edu> <bfschott@gmail.com>
Brian Schott <bschott@isi.edu>
Brian Waldon <brian.waldon@rackspace.com>
Chiradeep Vittal <chiradeep@cloud.com>
Chmouel Boudjnah <chmouel@chmouel.com>
Chris Behrens <cbehrens@codestud.com>
Christian Berendt <berendt@b1-systems.de>
Cory Wright <corywright@gmail.com>
David Pravec <David.Pravec@danix.org>
Dan Prince <dan.prince@rackspace.com>
David Pravec <David.Pravec@danix.org>
Dean Troyer <dtroyer@gmail.com>
Devin Carlen <devin.carlen@gmail.com>
Ed Leafe <ed@leafe.com>
@@ -44,7 +44,7 @@ Monsyne Dragon <mdragon@rackspace.com>
Monty Taylor <mordred@inaugust.com>
MORITA Kazutaka <morita.kazutaka@gmail.com>
Muneyuki Noguchi <noguchimn@nttdata.co.jp>
Nachi Ueno <ueno.nachi@lab.ntt.co.jp> <openstack@lab.ntt.co.jp> <nati.ueno@gmail.com> <nova@u4>
Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
Naveed Massjouni <naveed.massjouni@rackspace.com>
Paul Voccio <paul@openstack.org>
Ricardo Carrillo Cruz <emaildericky@gmail.com>
@@ -59,7 +59,7 @@ Soren Hansen <soren.hansen@rackspace.com>
Thierry Carrez <thierry@openstack.org>
Todd Willey <todd@ansolabs.com>
Trey Morris <trey.morris@rackspace.com>
Tushar Patil <tushar.vitthal.patil@gmail.com> <tpatil@vertex.co.in>
Tushar Patil <tushar.vitthal.patil@gmail.com>
Vasiliy Shlykov <vash@vasiliyshlykov.org>
Vishvananda Ishaya <vishvananda@gmail.com>
Youcef Laribi <Youcef.Laribi@eu.citrix.com>
+4
View File
@@ -34,6 +34,7 @@ from nova.api.openstack import flavors
from nova.api.openstack import images
from nova.api.openstack import servers
from nova.api.openstack import shared_ip_groups
from nova.api.openstack import zones
LOG = logging.getLogger('nova.api.openstack')
@@ -81,6 +82,9 @@ class APIRouter(wsgi.Router):
server_members['resume'] = 'POST'
server_members['reset_network'] = 'POST'
mapper.resource("zone", "zones", controller=zones.Controller(),
collection={'detail': 'GET'})
mapper.resource("server", "servers", controller=servers.Controller(),
collection={'detail': 'GET'},
member=server_members)
+1
View File
@@ -19,6 +19,7 @@ import datetime
import hashlib
import json
import time
import logging
import webob.exc
import webob.dec
+2 -3
View File
@@ -1,5 +1,3 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 OpenStack LLC.
# All Rights Reserved.
#
@@ -179,7 +177,8 @@ class Controller(wsgi.Controller):
display_name=env['server']['name'],
display_description=env['server']['name'],
key_name=key_pair['name'],
key_data=key_pair['public_key'])
key_data=key_pair['public_key'],
onset_files=env.get('onset_files', []))
return _translate_keys(instances[0])
def update(self, req, id):
+80
View File
@@ -0,0 +1,80 @@
# Copyright 2010 OpenStack LLC.
# 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 common
import logging
from nova import flags
from nova import wsgi
from nova import db
FLAGS = flags.FLAGS
def _filter_keys(item, keys):
"""
Filters all model attributes except for keys
item is a dict
"""
return dict((k, v) for k, v in item.iteritems() if k in keys)
def _scrub_zone(zone):
return _filter_keys(zone, ('id', 'api_url'))
class Controller(wsgi.Controller):
_serialization_metadata = {
'application/xml': {
"attributes": {
"zone": ["id", "api_url"]}}}
def index(self, req):
"""Return all zones in brief"""
items = db.zone_get_all(req.environ['nova.context'])
items = common.limited(items, req)
items = [_scrub_zone(item) for item in items]
return dict(zones=items)
def detail(self, req):
"""Return all zones in detail"""
return self.index(req)
def show(self, req, id):
"""Return data about the given zone id"""
zone_id = int(id)
zone = db.zone_get(req.environ['nova.context'], zone_id)
return dict(zone=_scrub_zone(zone))
def delete(self, req, id):
zone_id = int(id)
db.zone_delete(req.environ['nova.context'], zone_id)
return {}
def create(self, req):
context = req.environ['nova.context']
env = self._deserialize(req.body, req)
zone = db.zone_create(context, env["zone"])
return dict(zone=_scrub_zone(zone))
def update(self, req, id):
context = req.environ['nova.context']
env = self._deserialize(req.body, req)
zone_id = int(id)
zone = db.zone_update(context, zone_id, env["zone"])
return dict(zone=_scrub_zone(zone))
+3 -4
View File
@@ -10,7 +10,6 @@ export NOVA_CERT=${NOVA_KEY_DIR}/%(nova)s
export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set
alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}"
alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}"
export CLOUD_SERVERS_API_KEY="%(access)s"
export CLOUD_SERVERS_USERNAME="%(user)s"
export CLOUD_SERVERS_URL="%(os)s"
export NOVA_API_KEY="%(access)s"
export NOVA_USERNAME="%(user)s"
export NOVA_URL="%(os)s"
+10 -5
View File
@@ -85,10 +85,11 @@ class API(base.Base):
min_count=1, max_count=1,
display_name='', display_description='',
key_name=None, key_data=None, security_group='default',
availability_zone=None, user_data=None):
availability_zone=None, user_data=None,
onset_files=None):
"""Create the number of instances requested if quota and
other arguments check out ok."""
other arguments check out ok.
"""
type_data = instance_types.INSTANCE_TYPES[instance_type]
num_instances = quota.allowed_instances(context, max_count, type_data)
if num_instances < min_count:
@@ -156,7 +157,6 @@ class API(base.Base):
'key_data': key_data,
'locked': False,
'availability_zone': availability_zone}
elevated = context.elevated()
instances = []
LOG.debug(_("Going to run %s instances..."), num_instances)
@@ -193,7 +193,8 @@ class API(base.Base):
{"method": "run_instance",
"args": {"topic": FLAGS.compute_topic,
"instance_id": instance_id,
"availability_zone": availability_zone}})
"availability_zone": availability_zone,
"onset_files": onset_files}})
for group_id in security_groups:
self.trigger_security_group_members_refresh(elevated, group_id)
@@ -477,6 +478,10 @@ class API(base.Base):
"""Set the root/admin password for the given instance."""
self._cast_compute_message('set_admin_password', context, instance_id)
def inject_file(self, context, instance_id):
"""Write a file to the given instance."""
self._cast_compute_message('inject_file', context, instance_id)
def get_ajax_console(self, context, instance_id):
"""Get a url to an AJAX Console"""
instance = self.get(context, instance_id)
+39 -22
View File
@@ -34,6 +34,7 @@ terminating it.
:func:`nova.utils.import_object`
"""
import base64
import datetime
import random
import string
@@ -130,7 +131,7 @@ class ComputeManager(manager.Manager):
state = power_state.FAILED
self.db.instance_set_state(context, instance_id, state)
def get_console_topic(self, context, **_kwargs):
def get_console_topic(self, context, **kwargs):
"""Retrieves the console host for a project on this host
Currently this is just set in the flags for each compute
host."""
@@ -139,7 +140,7 @@ class ComputeManager(manager.Manager):
FLAGS.console_topic,
FLAGS.console_host)
def get_network_topic(self, context, **_kwargs):
def get_network_topic(self, context, **kwargs):
"""Retrieves the network host for a project on this host"""
# TODO(vish): This method should be memoized. This will make
# the call to get_network_host cheaper, so that
@@ -158,21 +159,22 @@ class ComputeManager(manager.Manager):
@exception.wrap_exception
def refresh_security_group_rules(self, context,
security_group_id, **_kwargs):
security_group_id, **kwargs):
"""This call passes straight through to the virtualization driver."""
return self.driver.refresh_security_group_rules(security_group_id)
@exception.wrap_exception
def refresh_security_group_members(self, context,
security_group_id, **_kwargs):
security_group_id, **kwargs):
"""This call passes straight through to the virtualization driver."""
return self.driver.refresh_security_group_members(security_group_id)
@exception.wrap_exception
def run_instance(self, context, instance_id, **_kwargs):
def run_instance(self, context, instance_id, **kwargs):
"""Launch a new instance with specified options."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
instance_ref.onset_files = kwargs.get('onset_files', [])
if instance_ref['name'] in self.driver.list_instances():
raise exception.Error(_("Instance has already been created"))
LOG.audit(_("instance %s: starting..."), instance_id,
@@ -323,28 +325,43 @@ class ComputeManager(manager.Manager):
"""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_id = instance_ref['id']
instance_state = instance_ref['state']
expected_state = power_state.RUNNING
if instance_state != expected_state:
LOG.warn(_('trying to reset the password on a non-running '
'instance: %(instance_id)s (state: %(instance_state)s '
'expected: %(expected_state)s)') % locals())
LOG.audit(_('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)
new_pass = utils.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 inject_file(self, context, instance_id, path, file_contents):
"""Write a file to the specified path on an instance on this server"""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
instance_id = instance_ref['id']
instance_state = instance_ref['state']
expected_state = power_state.RUNNING
if instance_state != expected_state:
LOG.warn(_('trying to inject a file into a non-running '
'instance: %(instance_id)s (state: %(instance_state)s '
'expected: %(expected_state)s)') % locals())
# Files/paths *should* be base64-encoded at this point, but
# double-check to make sure.
b64_path = utils.ensure_b64_encoding(path)
b64_contents = utils.ensure_b64_encoding(file_contents)
plain_path = base64.b64decode(b64_path)
nm = instance_ref['name']
msg = _('instance %(nm)s: injecting file to %(plain_path)s') % locals()
LOG.audit(msg)
self.driver.inject_file(instance_ref, b64_path, b64_contents)
@exception.wrap_exception
@checks_instance_lock
@@ -629,7 +646,7 @@ class ComputeManager(manager.Manager):
def get_ajax_console(self, context, instance_id):
"""Return connection information for an ajax console"""
context = context.elevated()
logging.debug(_("instance %s: getting ajax console"), instance_id)
LOG.debug(_("instance %s: getting ajax console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id)
return self.driver.get_ajax_console(instance_ref)
+29 -1
View File
@@ -603,7 +603,7 @@ def project_get_network(context, project_id, associate=True):
"""
return IMPL.project_get_network(context, project_id)
return IMPL.project_get_network(context, project_id, associate)
def project_get_network_v6(context, project_id):
@@ -1027,3 +1027,31 @@ def console_get_all_by_instance(context, instance_id):
def console_get(context, console_id, instance_id=None):
"""Get a specific console (possibly on a given instance)."""
return IMPL.console_get(context, console_id, instance_id)
####################
def zone_create(context, values):
"""Create a new child Zone entry."""
return IMPL.zone_create(context, values)
def zone_update(context, zone_id, values):
"""Update a child Zone entry."""
return IMPL.zone_update(context, values)
def zone_delete(context, zone_id):
"""Delete a child Zone."""
return IMPL.zone_delete(context, zone_id)
def zone_get(context, zone_id):
"""Get a specific child Zone."""
return IMPL.zone_get(context, zone_id)
def zone_get_all(context):
"""Get all child Zones."""
return IMPL.zone_get_all(context)
+44
View File
@@ -2114,3 +2114,47 @@ def console_get(context, console_id, instance_id=None):
raise exception.NotFound(_("No console with id %(console_id)s"
" %(idesc)s") % locals())
return result
####################
@require_admin_context
def zone_create(context, values):
zone = models.Zone()
zone.update(values)
zone.save()
return zone
@require_admin_context
def zone_update(context, zone_id, values):
zone = session.query(models.Zone).filter_by(id=zone_id).first()
if not zone:
raise exception.NotFound(_("No zone with id %(zone_id)s") % locals())
zone.update(values)
zone.save()
return zone
@require_admin_context
def zone_delete(context, zone_id):
session = get_session()
with session.begin():
session.execute('delete from zones '
'where id=:id', {'id': zone_id})
@require_admin_context
def zone_get(context, zone_id):
session = get_session()
result = session.query(models.Zone).filter_by(id=zone_id).first()
if not result:
raise exception.NotFound(_("No zone with id %(zone_id)s") % locals())
return result
@require_admin_context
def zone_get_all(context):
session = get_session()
return session.query(models.Zone).all()
@@ -0,0 +1,61 @@
# Copyright 2010 OpenStack LLC.
# 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.
from sqlalchemy import *
from migrate import *
from nova import log as logging
meta = MetaData()
#
# New Tables
#
zones = Table('zones', meta,
Column('created_at', DateTime(timezone=False)),
Column('updated_at', DateTime(timezone=False)),
Column('deleted_at', DateTime(timezone=False)),
Column('deleted', Boolean(create_constraint=True, name=None)),
Column('id', Integer(), primary_key=True, nullable=False),
Column('api_url',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('username',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('password',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
)
#
# Tables to alter
#
# (none currently)
def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine;
# bind migrate_engine to your metadata
meta.bind = migrate_engine
for table in (zones, ):
try:
table.create()
except Exception:
logging.info(repr(table))
+2 -2
View File
@@ -55,8 +55,8 @@ def db_version():
engine = sqlalchemy.create_engine(FLAGS.sql_connection, echo=False)
meta.reflect(bind=engine)
try:
for table in ('auth_tokens', 'export_devices', 'fixed_ips',
'floating_ips', 'instances',
for table in ('auth_tokens', 'zones', 'export_devices',
'fixed_ips', 'floating_ips', 'instances',
'key_pairs', 'networks', 'projects', 'quotas',
'security_group_instance_association',
'security_group_rules', 'security_groups',
+10 -2
View File
@@ -548,6 +548,15 @@ class Console(BASE, NovaBase):
pool = relationship(ConsolePool, backref=backref('consoles'))
class Zone(BASE, NovaBase):
"""Represents a child zone of this zone."""
__tablename__ = 'zones'
id = Column(Integer, primary_key=True)
api_url = Column(String(255))
username = Column(String(255))
password = Column(String(255))
def register_models():
"""Register Models and create metadata.
@@ -560,8 +569,7 @@ def register_models():
Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp,
Network, SecurityGroup, SecurityGroupIngressRule,
SecurityGroupInstanceAssociation, AuthToken, User,
Project, Certificate, ConsolePool, Console,
Migration) # , Image, Host
Project, Certificate, ConsolePool, Console, Zone, Migration)
engine = create_engine(FLAGS.sql_connection, echo=False)
for model in models:
model.metadata.create_all(engine)
+2 -2
View File
@@ -94,7 +94,7 @@ critical = logging.critical
log = logging.log
# handlers
StreamHandler = logging.StreamHandler
RotatingFileHandler = logging.handlers.RotatingFileHandler
WatchedFileHandler = logging.handlers.WatchedFileHandler
# logging.SysLogHandler is nicer than logging.logging.handler.SysLogHandler.
SysLogHandler = logging.handlers.SysLogHandler
@@ -139,7 +139,7 @@ def basicConfig():
logging.root.addHandler(syslog)
logpath = get_log_file_path()
if logpath:
logfile = RotatingFileHandler(logpath)
logfile = WatchedFileHandler(logpath)
logfile.setFormatter(_formatter)
logging.root.addHandler(logfile)
+1 -1
View File
@@ -44,7 +44,7 @@ flags.DEFINE_string('dhcp_domain',
flags.DEFINE_string('networks_path', '$state_path/networks',
'Location to keep network config files')
flags.DEFINE_string('public_interface', 'vlan1',
flags.DEFINE_string('public_interface', 'eth0',
'Interface for public IP addresses')
flags.DEFINE_string('vlan_interface', 'eth0',
'network device for vlans')
+16 -13
View File
@@ -110,6 +110,7 @@ class NetworkManager(manager.Manager):
This class must be subclassed to support specific topologies.
"""
timeout_fixed_ips = True
def __init__(self, network_driver=None, *args, **kwargs):
if not network_driver:
@@ -138,6 +139,19 @@ class NetworkManager(manager.Manager):
self.driver.ensure_floating_forward(floating_ip['address'],
fixed_address)
def periodic_tasks(self, context=None):
"""Tasks to be run at a periodic interval."""
super(NetworkManager, self).periodic_tasks(context)
if self.timeout_fixed_ips:
now = utils.utcnow()
timeout = FLAGS.fixed_ip_disassociate_timeout
time = now - datetime.timedelta(seconds=timeout)
num = self.db.fixed_ip_disassociate_all_by_timeout(context,
self.host,
time)
if num:
LOG.debug(_("Dissassociated %s stale fixed ip(s)"), num)
def set_network_host(self, context, network_id):
"""Safely sets the host of the network."""
LOG.debug(_("setting network host"), context=context)
@@ -306,6 +320,7 @@ class FlatManager(NetworkManager):
not do any setup in this mode, it must be done manually. Requests to
169.254.169.254 port 80 will need to be forwarded to the api server.
"""
timeout_fixed_ips = False
def allocate_fixed_ip(self, context, instance_id, *args, **kwargs):
"""Gets a fixed ip from the pool."""
@@ -457,18 +472,6 @@ class VlanManager(NetworkManager):
instances in its subnet.
"""
def periodic_tasks(self, context=None):
"""Tasks to be run at a periodic interval."""
super(VlanManager, self).periodic_tasks(context)
now = datetime.datetime.utcnow()
timeout = FLAGS.fixed_ip_disassociate_timeout
time = now - datetime.timedelta(seconds=timeout)
num = self.db.fixed_ip_disassociate_all_by_timeout(context,
self.host,
time)
if num:
LOG.debug(_("Dissassociated %s stale fixed ip(s)"), num)
def init_host(self):
"""Do any initialization that needs to be run if this is a
standalone service.
@@ -509,7 +512,7 @@ class VlanManager(NetworkManager):
network_ref['bridge'])
def create_networks(self, context, cidr, num_networks, network_size,
cidr_v6, vlan_start, vpn_start):
cidr_v6, vlan_start, vpn_start, **kwargs):
"""Create networks based on parameters."""
# Check that num_networks + vlan_start is not > 4094, fixes lp708025
if num_networks + vlan_start > 4094:
+141
View File
@@ -0,0 +1,141 @@
# Copyright 2010 OpenStack LLC.
# 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 unittest
import stubout
import webob
import json
import nova.db
from nova import context
from nova import flags
from nova.api.openstack import zones
from nova.tests.api.openstack import fakes
FLAGS = flags.FLAGS
FLAGS.verbose = True
def zone_get(context, zone_id):
return dict(id=1, api_url='http://foo.com', username='bob',
password='xxx')
def zone_create(context, values):
zone = dict(id=1)
zone.update(values)
return zone
def zone_update(context, zone_id, values):
zone = dict(id=zone_id, api_url='http://foo.com', username='bob',
password='xxx')
zone.update(values)
return zone
def zone_delete(context, zone_id):
pass
def zone_get_all(context):
return [
dict(id=1, api_url='http://foo.com', username='bob',
password='xxx'),
dict(id=2, api_url='http://blah.com', username='alice',
password='qwerty')
]
class ZonesTest(unittest.TestCase):
def setUp(self):
self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.auth_data = {}
fakes.FakeAuthDatabase.data = {}
fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_auth(self.stubs)
self.allow_admin = FLAGS.allow_admin_api
FLAGS.allow_admin_api = True
self.stubs.Set(nova.db, 'zone_get', zone_get)
self.stubs.Set(nova.db, 'zone_get_all', zone_get_all)
self.stubs.Set(nova.db, 'zone_update', zone_update)
self.stubs.Set(nova.db, 'zone_create', zone_create)
self.stubs.Set(nova.db, 'zone_delete', zone_delete)
def tearDown(self):
self.stubs.UnsetAll()
FLAGS.allow_admin_api = self.allow_admin
def test_get_zone_list(self):
req = webob.Request.blank('/v1.0/zones')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(len(res_dict['zones']), 2)
def test_get_zone_by_id(self):
req = webob.Request.blank('/v1.0/zones/1')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res_dict['zone']['id'], 1)
self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')
self.assertFalse('password' in res_dict['zone'])
self.assertEqual(res.status_int, 200)
def test_zone_delete(self):
req = webob.Request.blank('/v1.0/zones/1')
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
def test_zone_create(self):
body = dict(zone=dict(api_url='http://blah.zoo', username='fred',
password='fubar'))
req = webob.Request.blank('/v1.0/zones')
req.method = 'POST'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(res_dict['zone']['id'], 1)
self.assertEqual(res_dict['zone']['api_url'], 'http://blah.zoo')
self.assertFalse('username' in res_dict['zone'])
def test_zone_update(self):
body = dict(zone=dict(username='zeb', password='sneaky'))
req = webob.Request.blank('/v1.0/zones/1')
req.method = 'PUT'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(res_dict['zone']['id'], 1)
self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')
self.assertFalse('username' in res_dict['zone'])
if __name__ == '__main__':
unittest.main()
+8
View File
@@ -203,6 +203,14 @@ class ComputeTestCase(test.TestCase):
self.compute.set_admin_password(self.context, instance_id)
self.compute.terminate_instance(self.context, instance_id)
def test_inject_file(self):
"""Ensure we can write a file to an instance"""
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
self.compute.inject_file(self.context, instance_id, "/tmp/test",
"File Contents")
self.compute.terminate_instance(self.context, instance_id)
def test_snapshot(self):
"""Ensure instance can be snapshotted"""
instance_id = self._create_instance()
+23
View File
@@ -20,12 +20,14 @@
System-level utilities and helper functions.
"""
import base64
import datetime
import inspect
import json
import os
import random
import socket
import string
import struct
import sys
import time
@@ -235,6 +237,15 @@ def generate_mac():
return ':'.join(map(lambda x: "%02x" % x, mac))
def generate_password(length=20):
"""Generate a random sequence of letters and digits
to be used as a password. Note that this is not intended
to represent the ultimate in security.
"""
chrs = string.letters + string.digits
return "".join([random.choice(chrs) for i in xrange(length)])
def last_octet(address):
return int(address.split(".")[-1])
@@ -476,3 +487,15 @@ def dumps(value):
def loads(s):
return json.loads(s)
def ensure_b64_encoding(val):
"""Safety method to ensure that values expected to be base64-encoded
actually are. If they are, the value is returned unchanged. Otherwise,
the encoded value is returned.
"""
try:
dummy = base64.decode(val)
return val
except TypeError:
return base64.b64encode(val)
+15
View File
@@ -170,6 +170,21 @@ class FakeConnection(object):
"""
pass
def inject_file(self, instance, b64_path, b64_contents):
"""
Writes a file 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 base64-encoded path to which the file is to be
written on the instance; the third is the contents of the file, also
base64-encoded.
The work will be done asynchronously. This function returns a
task that allows the caller to detect when it is complete.
"""
pass
def rescue(self, instance):
"""
Rescue the specified instance.
+50
View File
@@ -102,6 +102,7 @@ class VMOps(object):
vdi_uuid = VMHelper.fetch_image(self._session, instance.id,
instance.image_id, user, project, disk_image_type)
vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid)
else:
vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', disk)
@@ -169,6 +170,21 @@ class VMOps(object):
LOG.info(_('Spawning VM %(instance_name)s created %(vm_ref)s.')
% locals())
def _inject_onset_files():
onset_files = instance.onset_files
if onset_files:
# Check if this is a JSON-encoded string and convert if needed.
if isinstance(onset_files, basestring):
try:
onset_files = json.loads(onset_files)
except ValueError:
LOG.exception(_("Invalid value for onset_files: '%s'")
% onset_files)
onset_files = []
# Inject any files, if specified
for path, contents in instance.onset_files:
LOG.debug(_("Injecting file path: '%s'") % path)
self.inject_file(instance, path, contents)
# NOTE(armando): Do we really need to do this in virt?
# NOTE(tr3buchet): not sure but wherever we do it, we need to call
# reset_network afterwards
@@ -182,6 +198,8 @@ class VMOps(object):
if state == power_state.RUNNING:
LOG.debug(_('Instance %s: booted'), instance['name'])
timer.stop()
_inject_onset_files()
return True
except Exception, exc:
LOG.warn(exc)
LOG.exception(_('instance %s: failed to boot'),
@@ -190,6 +208,7 @@ class VMOps(object):
instance['id'],
power_state.SHUTDOWN)
timer.stop()
return False
timer.f = _wait_for_boot
@@ -402,6 +421,32 @@ class VMOps(object):
raise RuntimeError(resp_dict['message'])
return resp_dict['message']
def inject_file(self, instance, b64_path, b64_contents):
"""Write a file to the VM instance. The path to which it is to be
written and the contents of the file need to be supplied; both should
be base64-encoded to prevent errors with non-ASCII characters being
transmitted. If the agent does not support file injection, or the user
has disabled it, a NotImplementedError will be raised.
"""
# Files/paths *should* be base64-encoded at this point, but
# double-check to make sure.
b64_path = utils.ensure_b64_encoding(b64_path)
b64_contents = utils.ensure_b64_encoding(b64_contents)
# Need to uniquely identify this request.
transaction_id = str(uuid.uuid4())
args = {'id': transaction_id, 'b64_path': b64_path,
'b64_contents': b64_contents}
# If the agent doesn't support file injection, a NotImplementedError
# will be raised with the appropriate message.
resp = self._make_agent_call('inject_file', instance, '', args)
resp_dict = json.loads(resp)
if resp_dict['returncode'] != '0':
# There was some other sort of error; the message will contain
# a description of the error.
raise RuntimeError(resp_dict['message'])
return resp_dict['message']
def _shutdown(self, instance, vm, method='hard'):
"""Shutdown an instance """
state = self.get_info(instance['name'])['state']
@@ -620,6 +665,11 @@ class VMOps(object):
if 'TIMEOUT:' in err_msg:
LOG.error(_('TIMEOUT: The call to %(method)s timed out. '
'VM id=%(instance_id)s; args=%(strargs)s') % locals())
elif 'NOT IMPLEMENTED:' in err_msg:
LOG.error(_('NOT IMPLEMENTED: The call to %(method)s is not'
' supported by the agent. VM id=%(instance_id)s;'
' args=%(strargs)s') % locals())
raise NotImplementedError(err_msg)
else:
LOG.error(_('The call to %(method)s returned an error: %(e)s. '
'VM id=%(instance_id)s; args=%(strargs)s') % locals())
+6
View File
@@ -172,6 +172,12 @@ class XenAPIConnection(object):
"""Set the root/admin password on the VM instance"""
self._vmops.set_admin_password(instance, new_pass)
def inject_file(self, instance, b64_path, b64_contents):
"""Create a file on the VM instance. The file path and contents
should be base64-encoded.
"""
self._vmops.inject_file(instance, b64_path, b64_contents)
def destroy(self, instance):
"""Destroy VM instance"""
self._vmops.destroy(instance)
@@ -93,9 +93,8 @@ def password(self, arg_dict):
@jsonify
def resetnetwork(self, arg_dict):
"""
Writes a resquest to xenstore that tells the agent to reset networking.
"""Writes a resquest to xenstore that tells the agent
to reset networking.
"""
arg_dict['value'] = json.dumps({'name': 'resetnetwork', 'value': ''})
request_id = arg_dict['id']
@@ -36,7 +36,15 @@ pluginlib.configure_logging("xenstore")
def jsonify(fnc):
def wrapper(*args, **kwargs):
return json.dumps(fnc(*args, **kwargs))
ret = fnc(*args, **kwargs)
try:
json.loads(ret)
except ValueError:
# Value should already be JSON-encoded, but some operations
# may write raw sting values; this will catch those and
# properly encode them.
ret = json.dumps(ret)
return ret
return wrapper