Merged trunk.

This commit is contained in:
Eric Day 2010-08-07 14:45:36 -07:00
commit ff47d384a4
23 changed files with 757 additions and 490 deletions

View File

@ -35,32 +35,34 @@ sys.path.append(os.path.abspath(os.path.join(__file__, "../../")))
from nova import flags
from nova import rpc
from nova import utils
from nova.compute import linux_net
from nova.compute import network
from nova.network import linux_net
from nova.network import model
from nova.network import service
FLAGS = flags.FLAGS
def add_lease(mac, ip, hostname, interface):
if FLAGS.fake_rabbit:
network.lease_ip(ip)
service.VlanNetworkService().lease_ip(ip)
else:
rpc.cast(FLAGS.cloud_topic, {"method": "lease_ip",
"args" : {"address": ip}})
rpc.cast("%s.%s" (FLAGS.network_topic, FLAGS.node_name),
{"method": "lease_ip",
"args" : {"fixed_ip": ip}})
def old_lease(mac, ip, hostname, interface):
logging.debug("Adopted old lease or got a change of mac/hostname")
def del_lease(mac, ip, hostname, interface):
if FLAGS.fake_rabbit:
network.release_ip(ip)
service.VlanNetworkService().release_ip(ip)
else:
rpc.cast(FLAGS.cloud_topic, {"method": "release_ip",
"args" : {"address": ip}})
rpc.cast("%s.%s" (FLAGS.network_topic, FLAGS.node_name),
{"method": "release_ip",
"args" : {"fixed_ip": ip}})
def init_leases(interface):
net = network.get_network_by_interface(interface)
net = model.get_network_by_interface(interface)
res = ""
for host_name in net.hosts:
res += "%s\n" % linux_net.hostDHCP(net, host_name, net.hosts[host_name])

View File

@ -29,16 +29,12 @@ from nova import flags
from nova import utils
from nova.auth import manager
from nova.compute import model
from nova.compute import network
from nova.cloudpipe import pipelib
from nova.endpoint import cloud
FLAGS = flags.FLAGS
class NetworkCommands(object):
def restart(self):
network.restart_nets()
class VpnCommands(object):
def __init__(self):
@ -170,6 +166,13 @@ class ProjectCommands(object):
arguments: name"""
self.manager.delete_project(name)
def environment(self, project_id, user_id, filename='novarc'):
"""exports environment variables to an sourcable file
arguments: project_id user_id [filename='novarc]"""
rc = self.manager.get_environment_rc(project_id, user_id)
with open(filename, 'w') as f:
f.write(rc)
def list(self):
"""lists all projects
arguments: <none>"""
@ -182,14 +185,11 @@ class ProjectCommands(object):
self.manager.remove_from_project(user, project)
def zip(self, project_id, user_id, filename='nova.zip'):
"""exports credentials for user to a zip file
"""exports credentials for project to a zip file
arguments: project_id user_id [filename='nova.zip]"""
project = self.manager.get_project(project_id)
if project:
with open(filename, 'w') as f:
f.write(project.get_credentials(user_id))
else:
print "Project %s doesn't exist" % project
zip = self.manager.get_credentials(project_id, user_id)
with open(filename, 'w') as f:
f.write(zip)
def usage(script_name):
@ -197,7 +197,6 @@ def usage(script_name):
categories = [
('network', NetworkCommands),
('user', UserCommands),
('project', ProjectCommands),
('role', RoleCommands),

View File

@ -21,12 +21,16 @@
Twistd daemon for the nova network nodes.
"""
from nova import flags
from nova import twistd
from nova.network import service
FLAGS = flags.FLAGS
if __name__ == '__main__':
twistd.serve(__file__)
if __name__ == '__builtin__':
application = service.NetworkService.create()
application = service.type_to_class(FLAGS.network_type).create()

View File

@ -28,6 +28,8 @@ import json
from nova import datastore
SCOPE_BASE = 0
SCOPE_ONELEVEL = 1 # not implemented
SCOPE_SUBTREE = 2
MOD_ADD = 0
MOD_DELETE = 1
@ -188,15 +190,18 @@ class FakeLDAP(object):
Args:
dn -- dn to search under
scope -- only SCOPE_SUBTREE is supported
scope -- only SCOPE_BASE and SCOPE_SUBTREE are supported
query -- query to filter objects by
fields -- fields to return. Returns all fields if not specified
"""
if scope != SCOPE_SUBTREE:
if scope != SCOPE_BASE and scope != SCOPE_SUBTREE:
raise NotImplementedError(str(scope))
redis = datastore.Redis.instance()
keys = redis.keys("%s*%s" % (self.__redis_prefix, dn))
if scope == SCOPE_BASE:
keys = ["%s%s" % (self.__redis_prefix, dn)]
else:
keys = redis.keys("%s*%s" % (self.__redis_prefix, dn))
objects = []
for key in keys:
# get the attributes from redis

View File

@ -272,26 +272,30 @@ class LdapDriver(object):
"""Check if project exists"""
return self.get_project(name) != None
def __find_object(self, dn, query = None):
def __find_object(self, dn, query=None, scope=None):
"""Find an object by dn and query"""
objects = self.__find_objects(dn, query)
objects = self.__find_objects(dn, query, scope)
if len(objects) == 0:
return None
return objects[0]
def __find_dns(self, dn, query=None):
def __find_dns(self, dn, query=None, scope=None):
"""Find dns by query"""
if scope is None: # one of the flags is 0!!
scope = self.ldap.SCOPE_SUBTREE
try:
res = self.conn.search_s(dn, self.ldap.SCOPE_SUBTREE, query)
res = self.conn.search_s(dn, scope, query)
except self.ldap.NO_SUCH_OBJECT:
return []
# just return the DNs
return [dn for dn, attributes in res]
def __find_objects(self, dn, query = None):
def __find_objects(self, dn, query=None, scope=None):
"""Find objects by query"""
if scope is None: # one of the flags is 0!!
scope = self.ldap.SCOPE_SUBTREE
try:
res = self.conn.search_s(dn, self.ldap.SCOPE_SUBTREE, query)
res = self.conn.search_s(dn, scope, query)
except self.ldap.NO_SUCH_OBJECT:
return []
# just return the attributes
@ -361,7 +365,8 @@ class LdapDriver(object):
if not self.__group_exists(group_dn):
return False
res = self.__find_object(group_dn,
'(member=%s)' % self.__uid_to_dn(uid))
'(member=%s)' % self.__uid_to_dn(uid),
self.ldap.SCOPE_BASE)
return res != None
def __add_to_group(self, uid, group_dn):
@ -391,7 +396,11 @@ class LdapDriver(object):
if not self.__is_in_group(uid, group_dn):
raise exception.NotFound("User %s is not a member of the group" %
(uid,))
self.__safe_remove_from_group(uid, group_dn)
# NOTE(vish): remove user from group and any sub_groups
sub_dns = self.__find_group_dns_with_member(
group_dn, uid)
for sub_dn in sub_dns:
self.__safe_remove_from_group(uid, sub_dn)
def __safe_remove_from_group(self, uid, group_dn):
"""Remove user from group, deleting group if user is last member"""

View File

@ -36,6 +36,7 @@ from nova import objectstore # for flags
from nova import utils
from nova.auth import ldapdriver # for flags
from nova.auth import signer
from nova.network import vpn
FLAGS = flags.FLAGS
@ -50,13 +51,14 @@ flags.DEFINE_list('global_roles', ['cloudadmin', 'itsec'],
'Roles that apply to all projects')
flags.DEFINE_bool('use_vpn', True, 'Support per-project vpns')
flags.DEFINE_string('credentials_template',
utils.abspath('auth/novarc.template'),
'Template for creating users rc file')
flags.DEFINE_string('vpn_client_template',
utils.abspath('cloudpipe/client.ovpn.template'),
'Template for creating users vpn file')
flags.DEFINE_string('credential_vpn_file', 'nova-vpn.conf',
'Filename of certificate in credentials zip')
flags.DEFINE_string('credential_key_file', 'pk.pem',
'Filename of private key in credentials zip')
flags.DEFINE_string('credential_cert_file', 'cert.pem',
@ -64,19 +66,11 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem',
flags.DEFINE_string('credential_rc_file', 'novarc',
'Filename of rc in credentials zip')
flags.DEFINE_integer('vpn_start_port', 1000,
'Start port for the cloudpipe VPN servers')
flags.DEFINE_integer('vpn_end_port', 2000,
'End port for the cloudpipe VPN servers')
flags.DEFINE_string('credential_cert_subject',
'/C=US/ST=California/L=MountainView/O=AnsoLabs/'
'OU=NovaDev/CN=%s-%s',
'Subject for certificate for users')
flags.DEFINE_string('vpn_ip', '127.0.0.1',
'Public IP for the cloudpipe VPN servers')
flags.DEFINE_string('auth_driver', 'nova.auth.ldapdriver.FakeLdapDriver',
'Driver that auth manager uses')
@ -228,86 +222,6 @@ class Project(AuthBase):
self.member_ids)
class NoMorePorts(exception.Error):
pass
class Vpn(datastore.BasicModel):
"""Manages vpn ips and ports for projects"""
def __init__(self, project_id):
self.project_id = project_id
super(Vpn, self).__init__()
@property
def identifier(self):
"""Identifier used for key in redis"""
return self.project_id
@classmethod
def create(cls, project_id):
"""Creates a vpn for project
This method finds a free ip and port and stores the associated
values in the datastore.
"""
# TODO(vish): get list of vpn ips from redis
port = cls.find_free_port_for_ip(FLAGS.vpn_ip)
vpn = cls(project_id)
# save ip for project
vpn['project'] = project_id
vpn['ip'] = FLAGS.vpn_ip
vpn['port'] = port
vpn.save()
return vpn
@classmethod
def find_free_port_for_ip(cls, ip):
"""Finds a free port for a given ip from the redis set"""
# TODO(vish): these redis commands should be generalized and
# placed into a base class. Conceptually, it is
# similar to an association, but we are just
# storing a set of values instead of keys that
# should be turned into objects.
redis = datastore.Redis.instance()
key = 'ip:%s:ports' % ip
# TODO(vish): these ports should be allocated through an admin
# command instead of a flag
if (not redis.exists(key) and
not redis.exists(cls._redis_association_name('ip', ip))):
for i in range(FLAGS.vpn_start_port, FLAGS.vpn_end_port + 1):
redis.sadd(key, i)
port = redis.spop(key)
if not port:
raise NoMorePorts()
return port
@classmethod
def num_ports_for_ip(cls, ip):
"""Calculates the number of free ports for a given ip"""
return datastore.Redis.instance().scard('ip:%s:ports' % ip)
@property
def ip(self):
"""The ip assigned to the project"""
return self['ip']
@property
def port(self):
"""The port assigned to the project"""
return int(self['port'])
def save(self):
"""Saves the association to the given ip"""
self.associate_with('ip', self.ip)
super(Vpn, self).save()
def destroy(self):
"""Cleans up datastore and adds port back to pool"""
self.unassociate_with('ip', self.ip)
datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port)
super(Vpn, self).destroy()
class AuthManager(object):
"""Manager Singleton for dealing with Users, Projects, and Keypairs
@ -585,8 +499,6 @@ class AuthManager(object):
description,
member_users)
if project_dict:
if FLAGS.use_vpn:
Vpn.create(project_dict['id'])
return Project(**project_dict)
def add_to_project(self, user, project):
@ -623,10 +535,10 @@ class AuthManager(object):
@return: A tuple containing (ip, port) or None, None if vpn has
not been allocated for user.
"""
vpn = Vpn.lookup(Project.safe_id(project))
if not vpn:
return None, None
return (vpn.ip, vpn.port)
network_data = vpn.NetworkData.lookup(Project.safe_id(project))
if not network_data:
raise exception.NotFound('project network data has not been set')
return (network_data.ip, network_data.port)
def delete_project(self, project):
"""Deletes a project"""
@ -757,25 +669,27 @@ class AuthManager(object):
rc = self.__generate_rc(user.access, user.secret, pid)
private_key, signed_cert = self._generate_x509_cert(user.id, pid)
vpn = Vpn.lookup(pid)
if not vpn:
raise exception.Error("No vpn data allocated for project %s" %
project.name)
configfile = open(FLAGS.vpn_client_template,"r")
s = string.Template(configfile.read())
configfile.close()
config = s.substitute(keyfile=FLAGS.credential_key_file,
certfile=FLAGS.credential_cert_file,
ip=vpn.ip,
port=vpn.port)
tmpdir = tempfile.mkdtemp()
zf = os.path.join(tmpdir, "temp.zip")
zippy = zipfile.ZipFile(zf, 'w')
zippy.writestr(FLAGS.credential_rc_file, rc)
zippy.writestr(FLAGS.credential_key_file, private_key)
zippy.writestr(FLAGS.credential_cert_file, signed_cert)
zippy.writestr("nebula-client.conf", config)
network_data = vpn.NetworkData.lookup(pid)
if network_data:
configfile = open(FLAGS.vpn_client_template,"r")
s = string.Template(configfile.read())
configfile.close()
config = s.substitute(keyfile=FLAGS.credential_key_file,
certfile=FLAGS.credential_cert_file,
ip=network_data.ip,
port=network_data.port)
zippy.writestr(FLAGS.credential_vpn_file, config)
else:
logging.warn("No vpn data for project %s" %
pid)
zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id))
zippy.close()
with open(zf, 'rb') as f:
@ -784,6 +698,15 @@ class AuthManager(object):
shutil.rmtree(tmpdir)
return buffer
def get_environment_rc(self, user, project=None):
"""Get credential zip for user in project"""
if not isinstance(user, User):
user = self.get_user(user)
if project is None:
project = user.id
pid = Project.safe_id(project)
return self.__generate_rc(user.access, user.secret, pid)
def __generate_rc(self, access, secret, pid):
"""Generate rc file for user"""
rc = open(FLAGS.credentials_template).read()

View File

@ -41,9 +41,6 @@ True
"""
import datetime
import logging
import time
import redis
import uuid
from nova import datastore
@ -72,19 +69,22 @@ class InstanceDirectory(object):
for instance_id in datastore.Redis.instance().smembers('project:%s:instances' % project):
yield Instance(instance_id)
def by_node(self, node_id):
@datastore.absorb_connection_error
def by_node(self, node):
"""returns a list of instances for a node"""
for instance_id in datastore.Redis.instance().smembers('node:%s:instances' % node):
yield Instance(instance_id)
for instance in self.all:
if instance['node_name'] == node_id:
yield instance
def by_ip(self, ip_address):
def by_ip(self, ip):
"""returns an instance object that is using the IP"""
for instance in self.all:
if instance['private_dns_name'] == ip_address:
return instance
return None
# NOTE(vish): The ip association should be just a single value, but
# to maintain consistency it is using the standard
# association and the ugly method for retrieving
# the first item in the set below.
result = datastore.Redis.instance().smembers('ip:%s:instances' % ip)
if not result:
return None
return Instance(list(result)[0])
def by_volume(self, volume_id):
"""returns the instance a volume is attached to"""
@ -122,7 +122,8 @@ class Instance(datastore.BasicModel):
'instance_id': self.instance_id,
'node_name': 'unassigned',
'project_id': 'unassigned',
'user_id': 'unassigned'}
'user_id': 'unassigned',
'private_dns_name': 'unassigned'}
@property
def identifier(self):
@ -148,19 +149,23 @@ class Instance(datastore.BasicModel):
"""Call into superclass to save object, then save associations"""
# NOTE(todd): doesn't track migration between projects/nodes,
# it just adds the first one
should_update_project = self.is_new_record()
should_update_node = self.is_new_record()
is_new = self.is_new_record()
node_set = (self.state['node_name'] != 'unassigned' and
self.initial_state.get('node_name', 'unassigned')
== 'unassigned')
success = super(Instance, self).save()
if success and should_update_project:
if success and is_new:
self.associate_with("project", self.project)
if success and should_update_node:
self.associate_with("node", self['node_name'])
self.associate_with("ip", self.state['private_dns_name'])
if success and node_set:
self.associate_with("node", self.state['node_name'])
return True
def destroy(self):
"""Destroy associations, then destroy the object"""
self.unassociate_with("project", self.project)
self.unassociate_with("node", self['node_name'])
self.unassociate_with("node", self.state['node_name'])
self.unassociate_with("ip", self.state['private_dns_name'])
return super(Instance, self).destroy()
class Host(datastore.BasicModel):

View File

@ -39,9 +39,9 @@ from nova import service
from nova import utils
from nova.compute import disk
from nova.compute import model
from nova.compute import network
from nova.compute import power_state
from nova.compute.instance_types import INSTANCE_TYPES
from nova.network import service as network_service
from nova.objectstore import image # for image_path flag
from nova.virt import connection as virt_connection
from nova.volume import service as volume_service
@ -117,12 +117,17 @@ class ComputeService(service.Service):
""" launch a new instance with specified options """
logging.debug("Starting instance %s..." % (instance_id))
inst = self.instdir.get(instance_id)
if not FLAGS.simple_network:
# TODO: Get the real security group of launch in here
security_group = "default"
net = network.BridgedNetwork.get_network_for_project(inst['user_id'],
inst['project_id'],
security_group).express()
# TODO: Get the real security group of launch in here
security_group = "default"
# NOTE(vish): passing network type allows us to express the
# network without making a call to network to find
# out which type of network to setup
network_service.setup_compute_network(
inst.get('network_type', 'vlan'),
inst['user_id'],
inst['project_id'],
security_group)
inst['node_name'] = FLAGS.node_name
inst.save()
# TODO(vish) check to make sure the availability zone matches

View File

@ -90,13 +90,15 @@ class BasicModel(object):
@absorb_connection_error
def __init__(self):
self.initial_state = {}
self.state = Redis.instance().hgetall(self.__redis_key)
if self.state:
self.initial_state = self.state
state = Redis.instance().hgetall(self.__redis_key)
if state:
self.initial_state = state
self.state = dict(self.initial_state)
else:
self.initial_state = {}
self.state = self.default_state()
def default_state(self):
"""You probably want to define this in your subclass"""
return {}
@ -239,7 +241,7 @@ class BasicModel(object):
for key, val in self.state.iteritems():
Redis.instance().hset(self.__redis_key, key, val)
self.add_to_index()
self.initial_state = self.state
self.initial_state = dict(self.state)
return True
@absorb_connection_error

View File

@ -36,11 +36,11 @@ from nova import utils
from nova.auth import rbac
from nova.auth import manager
from nova.compute import model
from nova.compute import network
from nova.compute.instance_types import INSTANCE_TYPES
from nova.compute import service as compute_service
from nova.endpoint import images
from nova.volume import service as volume_service
from nova.network import service as network_service
from nova.network import model as network_model
from nova.volume import service
FLAGS = flags.FLAGS
@ -64,7 +64,6 @@ class CloudController(object):
"""
def __init__(self):
self.instdir = model.InstanceDirectory()
self.network = network.PublicNetworkController()
self.setup()
@property
@ -76,7 +75,7 @@ class CloudController(object):
def volumes(self):
""" returns a list of all volumes """
for volume_id in datastore.Redis.instance().smembers("volumes"):
volume = volume_service.get_volume(volume_id)
volume = service.get_volume(volume_id)
yield volume
def __str__(self):
@ -222,7 +221,7 @@ class CloudController(object):
callback=_complete)
return d
except users.UserError, e:
except manager.UserError as e:
raise
@rbac.allow('all')
@ -308,7 +307,7 @@ class CloudController(object):
def _get_address(self, context, public_ip):
# FIXME(vish) this should move into network.py
address = self.network.get_host(public_ip)
address = network_model.PublicAddress.lookup(public_ip)
if address and (context.user.is_admin() or address['project_id'] == context.project.id):
return address
raise exception.NotFound("Address at ip %s not found" % public_ip)
@ -330,7 +329,7 @@ class CloudController(object):
raise exception.NotFound('Instance %s could not be found' % instance_id)
def _get_volume(self, context, volume_id):
volume = volume_service.get_volume(volume_id)
volume = service.get_volume(volume_id)
if context.user.is_admin() or volume['project_id'] == context.project.id:
return volume
raise exception.NotFound('Volume %s could not be found' % volume_id)
@ -417,7 +416,7 @@ class CloudController(object):
'code': instance.get('state', 0),
'name': instance.get('state_description', 'pending')
}
i['public_dns_name'] = self.network.get_public_ip_for_instance(
i['public_dns_name'] = network_model.get_public_ip_for_instance(
i['instance_id'])
i['private_dns_name'] = instance.get('private_dns_name', None)
if not i['public_dns_name']:
@ -452,10 +451,10 @@ class CloudController(object):
def format_addresses(self, context):
addresses = []
for address in self.network.host_objs:
for address in network_model.PublicAddress.all():
# TODO(vish): implement a by_project iterator for addresses
if (context.user.is_admin() or
address['project_id'] == self.project.id):
address['project_id'] == context.project.id):
address_rv = {
'public_ip': address['address'],
'instance_id' : address.get('instance_id', 'free')
@ -470,41 +469,63 @@ class CloudController(object):
return {'addressesSet': addresses}
@rbac.allow('netadmin')
@defer.inlineCallbacks
def allocate_address(self, context, **kwargs):
address = self.network.allocate_ip(
context.user.id, context.project.id, 'public')
return defer.succeed({'addressSet': [{'publicIp' : address}]})
network_topic = yield self._get_network_topic(context)
alloc_result = yield rpc.call(network_topic,
{"method": "allocate_elastic_ip",
"args": {"user_id": context.user.id,
"project_id": context.project.id}})
public_ip = alloc_result['result']
defer.returnValue({'addressSet': [{'publicIp' : public_ip}]})
@rbac.allow('netadmin')
@defer.inlineCallbacks
def release_address(self, context, public_ip, **kwargs):
self.network.deallocate_ip(public_ip)
return defer.succeed({'releaseResponse': ["Address released."]})
# NOTE(vish): Should we make sure this works?
network_topic = yield self._get_network_topic(context)
rpc.cast(network_topic,
{"method": "deallocate_elastic_ip",
"args": {"elastic_ip": public_ip}})
defer.returnValue({'releaseResponse': ["Address released."]})
@rbac.allow('netadmin')
def associate_address(self, context, instance_id, **kwargs):
@defer.inlineCallbacks
def associate_address(self, context, instance_id, public_ip, **kwargs):
instance = self._get_instance(context, instance_id)
self.network.associate_address(
kwargs['public_ip'],
instance['private_dns_name'],
instance_id)
return defer.succeed({'associateResponse': ["Address associated."]})
address = self._get_address(context, public_ip)
network_topic = yield self._get_network_topic(context)
rpc.cast(network_topic,
{"method": "associate_elastic_ip",
"args": {"elastic_ip": address['address'],
"fixed_ip": instance['private_dns_name'],
"instance_id": instance['instance_id']}})
defer.returnValue({'associateResponse': ["Address associated."]})
@rbac.allow('netadmin')
@defer.inlineCallbacks
def disassociate_address(self, context, public_ip, **kwargs):
address = self._get_address(context, public_ip)
self.network.disassociate_address(public_ip)
# TODO - Strip the IP from the instance
return defer.succeed({'disassociateResponse': ["Address disassociated."]})
network_topic = yield self._get_network_topic(context)
rpc.cast(network_topic,
{"method": "disassociate_elastic_ip",
"args": {"elastic_ip": address['address']}})
defer.returnValue({'disassociateResponse': ["Address disassociated."]})
def release_ip(self, context, private_ip, **kwargs):
self.network.release_ip(private_ip)
return defer.succeed({'releaseResponse': ["Address released."]})
def lease_ip(self, context, private_ip, **kwargs):
self.network.lease_ip(private_ip)
return defer.succeed({'leaseResponse': ["Address leased."]})
@defer.inlineCallbacks
def _get_network_topic(self, context):
"""Retrieves the network host for a project"""
host = network_service.get_host_for_project(context.project.id)
if not host:
result = yield rpc.call(FLAGS.network_topic,
{"method": "set_network_host",
"args": {"user_id": context.user.id,
"project_id": context.project.id}})
host = result['result']
defer.returnValue('%s.%s' %(FLAGS.network_topic, host))
@rbac.allow('projectmanager', 'sysadmin')
@defer.inlineCallbacks
def run_instances(self, context, **kwargs):
# make sure user can access the image
# vpn image is private so it doesn't show up on lists
@ -536,15 +557,20 @@ class CloudController(object):
raise exception.ApiError('Key Pair %s not found' %
kwargs['key_name'])
key_data = key_pair.public_key
network_topic = yield self._get_network_topic(context)
# TODO: Get the real security group of launch in here
security_group = "default"
if FLAGS.simple_network:
bridge_name = FLAGS.simple_network_bridge
else:
net = network.BridgedNetwork.get_network_for_project(
context.user.id, context.project.id, security_group)
bridge_name = net['bridge_name']
for num in range(int(kwargs['max_count'])):
vpn = False
if image_id == FLAGS.vpn_image_id:
vpn = True
allocate_result = yield rpc.call(network_topic,
{"method": "allocate_fixed_ip",
"args": {"user_id": context.user.id,
"project_id": context.project.id,
"security_group": security_group,
"vpn": vpn}})
allocate_data = allocate_result['result']
inst = self.instdir.new()
inst['image_id'] = image_id
inst['kernel_id'] = kernel_id
@ -557,24 +583,11 @@ class CloudController(object):
inst['key_name'] = kwargs.get('key_name', '')
inst['user_id'] = context.user.id
inst['project_id'] = context.project.id
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = num
inst['bridge_name'] = bridge_name
if FLAGS.simple_network:
address = network.allocate_simple_ip()
else:
if inst['image_id'] == FLAGS.vpn_image_id:
address = network.allocate_vpn_ip(
inst['user_id'],
inst['project_id'],
mac=inst['mac_address'])
else:
address = network.allocate_ip(
inst['user_id'],
inst['project_id'],
mac=inst['mac_address'])
inst['private_dns_name'] = str(address)
# TODO: allocate expresses on the router node
inst['security_group'] = security_group
for (key, value) in allocate_data.iteritems():
inst[key] = value
inst.save()
rpc.cast(FLAGS.compute_topic,
{"method": "run_instance",
@ -582,40 +595,49 @@ class CloudController(object):
logging.debug("Casting to node for %s's instance with IP of %s" %
(context.user.name, inst['private_dns_name']))
# TODO: Make Network figure out the network name from ip.
return defer.succeed(self._format_instances(
context, reservation_id))
defer.returnValue(self._format_instances(context, reservation_id))
@rbac.allow('projectmanager', 'sysadmin')
@defer.inlineCallbacks
def terminate_instances(self, context, instance_id, **kwargs):
logging.debug("Going to start terminating instances")
network_topic = yield self._get_network_topic(context)
for i in instance_id:
logging.debug("Going to try and terminate %s" % i)
try:
instance = self._get_instance(context, i)
except exception.NotFound:
logging.warning("Instance %s was not found during terminate" % i)
logging.warning("Instance %s was not found during terminate"
% i)
continue
try:
self.network.disassociate_address(
instance.get('public_dns_name', 'bork'))
except:
pass
if instance.get('private_dns_name', None):
logging.debug("Deallocating address %s" % instance.get('private_dns_name', None))
if FLAGS.simple_network:
network.deallocate_simple_ip(instance.get('private_dns_name', None))
else:
try:
self.network.deallocate_ip(instance.get('private_dns_name', None))
except Exception, _err:
pass
if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
elastic_ip = network_model.get_public_ip_for_instance(i)
if elastic_ip:
logging.debug("Disassociating address %s" % elastic_ip)
# NOTE(vish): Right now we don't really care if the ip is
# disassociated. We may need to worry about
# checking this later. Perhaps in the scheduler?
rpc.cast(network_topic,
{"method": "disassociate_elastic_ip",
"args": {"elastic_ip": elastic_ip}})
fixed_ip = instance.get('private_dns_name', None)
if fixed_ip:
logging.debug("Deallocating address %s" % fixed_ip)
# NOTE(vish): Right now we don't really care if the ip is
# actually removed. We may need to worry about
# checking this later. Perhaps in the scheduler?
rpc.cast(network_topic,
{"method": "deallocate_fixed_ip",
"args": {"fixed_ip": fixed_ip}})
if instance.get('node_name', 'unassigned') != 'unassigned':
# NOTE(joshua?): It's also internal default
rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "terminate_instance",
"args" : {"instance_id": i}})
{"method": "terminate_instance",
"args": {"instance_id": i}})
else:
instance.destroy()
return defer.succeed(True)
defer.returnValue(True)
@rbac.allow('projectmanager', 'sysadmin')
def reboot_instances(self, context, instance_id, **kwargs):
@ -677,6 +699,8 @@ class CloudController(object):
# TODO(devcamcar): Support users and groups other than 'all'.
if attribute != 'launchPermission':
raise exception.ApiError('attribute not supported: %s' % attribute)
if not 'user_group' in kwargs:
raise exception.ApiError('user or group not specified')
if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':
raise exception.ApiError('only group "all" is supported')
if not operation_type in ['add', 'remove']:

View File

@ -17,7 +17,7 @@
# under the License.
"""
Exceptions for Compute Node errors, mostly network addressing.
Exceptions for network errors.
"""
from nova.exception import Error

View File

@ -17,7 +17,7 @@
# under the License.
"""
Classes for network control, including VLANs, DHCP, and IP allocation.
Model Classes for network control, including VLANs, DHCP, and IP allocation.
"""
import IPy
@ -26,12 +26,12 @@ import os
import time
from nova import datastore
from nova import exception
from nova import exception as nova_exception
from nova import flags
from nova import utils
from nova.auth import manager
from nova.compute import exception as compute_exception
from nova.compute import linux_net
from nova.network import exception
from nova.network import linux_net
FLAGS = flags.FLAGS
@ -53,26 +53,6 @@ flags.DEFINE_integer('cnt_vpn_clients', 5,
flags.DEFINE_integer('cloudpipe_start_port', 12000,
'Starting port for mapped CloudPipe external ports')
flags.DEFINE_boolean('simple_network', False,
'Use simple networking instead of vlans')
flags.DEFINE_string('simple_network_bridge', 'br100',
'Bridge for simple network instances')
flags.DEFINE_list('simple_network_ips', ['192.168.0.2'],
'Available ips for simple network')
flags.DEFINE_string('simple_network_template',
utils.abspath('compute/interfaces.template'),
'Template file for simple network')
flags.DEFINE_string('simple_network_netmask', '255.255.255.0',
'Netmask for simple network')
flags.DEFINE_string('simple_network_network', '192.168.0.0',
'Network for simple network')
flags.DEFINE_string('simple_network_gateway', '192.168.0.1',
'Broadcast for simple network')
flags.DEFINE_string('simple_network_broadcast', '192.168.0.255',
'Broadcast for simple network')
flags.DEFINE_string('simple_network_dns', '8.8.4.4',
'Dns for simple network')
logging.getLogger().setLevel(logging.DEBUG)
@ -156,7 +136,6 @@ class Vlan(datastore.BasicModel):
# CLEANUP:
# TODO(ja): Save the IPs at the top of each subnet for cloudpipe vpn clients
# TODO(ja): use singleton for usermanager instead of self.manager in vlanpool et al
# TODO(ja): does vlanpool "keeper" need to know the min/max - shouldn't FLAGS always win?
# TODO(joshua): Save the IPs at the top of each subnet for cloudpipe vpn clients
@ -241,7 +220,7 @@ class BaseNetwork(datastore.BasicModel):
for idx in range(self.num_static_ips, len(self.network)-(1 + FLAGS.cnt_vpn_clients)):
address = str(self.network[idx])
if not address in self.hosts.keys():
yield str(address)
yield address
@property
def num_static_ips(self):
@ -253,7 +232,7 @@ class BaseNetwork(datastore.BasicModel):
self._add_host(user_id, project_id, address, mac)
self.express(address=address)
return address
raise compute_exception.NoMoreAddresses("Project %s with network %s" %
raise exception.NoMoreAddresses("Project %s with network %s" %
(project_id, str(self.network)))
def lease_ip(self, ip_str):
@ -261,7 +240,7 @@ class BaseNetwork(datastore.BasicModel):
def release_ip(self, ip_str):
if not ip_str in self.assigned:
raise compute_exception.AddressNotAllocated()
raise exception.AddressNotAllocated()
self.deexpress(address=ip_str)
self._rem_host(ip_str)
@ -349,14 +328,14 @@ class DHCPNetwork(BridgedNetwork):
logging.debug("Not launching dnsmasq: no hosts.")
self.express_cloudpipe()
def allocate_vpn_ip(self, mac):
def allocate_vpn_ip(self, user_id, project_id, mac):
address = str(self.network[2])
self._add_host(self['user_id'], self['project_id'], address, mac)
self._add_host(user_id, project_id, address, mac)
self.express(address=address)
return address
def express_cloudpipe(self):
private_ip = self.network[2]
private_ip = str(self.network[2])
linux_net.confirm_rule("FORWARD -d %s -p udp --dport 1194 -j ACCEPT"
% (private_ip, ))
linux_net.confirm_rule("PREROUTING -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194"
@ -394,6 +373,7 @@ class PublicAddress(datastore.BasicModel):
addr.save()
return addr
DEFAULT_PORTS = [("tcp",80), ("tcp",22), ("udp",1194), ("tcp",443)]
class PublicNetworkController(BaseNetwork):
override_type = 'network'
@ -420,12 +400,6 @@ class PublicNetworkController(BaseNetwork):
for address in self.assigned:
yield PublicAddress(address)
def get_public_ip_for_instance(self, instance_id):
# FIXME: this should be a lookup - iteration won't scale
for address_record in self.host_objs:
if address_record.get('instance_id', 'available') == instance_id:
return address_record['address']
def get_host(self, host):
if host in self.assigned:
return PublicAddress(host)
@ -439,16 +413,20 @@ class PublicNetworkController(BaseNetwork):
PublicAddress(host).destroy()
datastore.Redis.instance().hdel(self._hosts_key, host)
def deallocate_ip(self, ip_str):
# NOTE(vish): cleanup is now done on release by the parent class
self.release_ip(ip_str)
def associate_address(self, public_ip, private_ip, instance_id):
if not public_ip in self.assigned:
raise compute_exception.AddressNotAllocated()
raise exception.AddressNotAllocated()
# TODO(joshua): Keep an index going both ways
for addr in self.host_objs:
if addr.get('private_ip', None) == private_ip:
raise compute_exception.AddressAlreadyAssociated()
raise exception.AddressAlreadyAssociated()
addr = self.get_host(public_ip)
if addr.get('private_ip', 'available') != 'available':
raise compute_exception.AddressAlreadyAssociated()
raise exception.AddressAlreadyAssociated()
addr['private_ip'] = private_ip
addr['instance_id'] = instance_id
addr.save()
@ -456,10 +434,10 @@ class PublicNetworkController(BaseNetwork):
def disassociate_address(self, public_ip):
if not public_ip in self.assigned:
raise compute_exception.AddressNotAllocated()
raise exception.AddressNotAllocated()
addr = self.get_host(public_ip)
if addr.get('private_ip', 'available') == 'available':
raise compute_exception.AddressNotAssociated()
raise exception.AddressNotAssociated()
self.deexpress(address=public_ip)
addr['private_ip'] = 'available'
addr['instance_id'] = 'available'
@ -535,63 +513,42 @@ def get_vlan_for_project(project_id):
return vlan
else:
return Vlan.create(project_id, vnum)
raise compute_exception.AddressNotAllocated("Out of VLANs")
raise exception.AddressNotAllocated("Out of VLANs")
def get_project_network(project_id, security_group='default'):
""" get a project's private network, allocating one if needed """
project = manager.AuthManager().get_project(project_id)
if not project:
raise nova_exception.NotFound("Project %s doesn't exist." % project_id)
manager_id = project.project_manager_id
return DHCPNetwork.get_network_for_project(manager_id,
project.id,
security_group)
def get_network_by_interface(iface, security_group='default'):
vlan = iface.rpartition("br")[2]
return get_project_network(Vlan.dict_by_vlan().get(vlan), security_group)
def get_network_by_address(address):
# TODO(vish): This is completely the wrong way to do this, but
# I'm getting the network binary working before I
# tackle doing this the right way.
logging.debug("Get Network By Address: %s" % address)
for project in manager.AuthManager().get_projects():
net = get_project_network(project.id)
if address in net.assigned:
logging.debug("Found %s in %s" % (address, project.id))
return net
raise compute_exception.AddressNotAllocated()
def allocate_simple_ip():
redis = datastore.Redis.instance()
if not redis.exists('ips') and not len(redis.keys('instances:*')):
for address in FLAGS.simple_network_ips:
redis.sadd('ips', address)
address = redis.spop('ips')
if not address:
raise exception.NoMoreAddresses()
return address
def deallocate_simple_ip(address):
datastore.Redis.instance().sadd('ips', address)
raise exception.AddressNotAllocated()
def allocate_vpn_ip(user_id, project_id, mac):
return get_project_network(project_id).allocate_vpn_ip(mac)
def allocate_ip(user_id, project_id, mac):
return get_project_network(project_id).allocate_ip(user_id, project_id, mac)
def deallocate_ip(address):
return get_network_by_address(address).deallocate_ip(address)
def release_ip(address):
return get_network_by_address(address).release_ip(address)
def lease_ip(address):
return get_network_by_address(address).lease_ip(address)
def get_project_network(project_id, security_group='default'):
""" get a project's private network, allocating one if needed """
# TODO(todd): It looks goofy to get a project from a UserManager.
# Refactor to still use the LDAP backend, but not User specific.
project = manager.AuthManager().get_project(project_id)
if not project:
raise exception.Error("Project %s doesn't exist, uhoh." %
project_id)
return DHCPNetwork.get_network_for_project(project.project_manager_id,
project.id, security_group)
def get_network_by_interface(iface, security_group='default'):
vlan = iface.rpartition("br")[2]
project_id = Vlan.dict_by_vlan().get(vlan)
return get_project_network(project_id, security_group)
def restart_nets():
""" Ensure the network for each user is enabled"""
for project in manager.AuthManager().get_projects():
get_project_network(project.id).express()
def get_public_ip_for_instance(instance_id):
# FIXME: this should be a lookup - iteration won't scale
for address_record in PublicAddress.all():
if address_record.get('instance_id', 'available') == instance_id:
return address_record['address']

View File

@ -20,16 +20,211 @@
Network Nodes are responsible for allocating ips and setting up network
"""
import logging
from nova import datastore
from nova import flags
from nova import service
from nova import utils
from nova.auth import manager
from nova.exception import NotFound
from nova.network import exception
from nova.network import model
from nova.network import vpn
FLAGS = flags.FLAGS
class NetworkService(service.Service):
"""Allocates ips and sets up networks"""
flags.DEFINE_string('network_type',
'flat',
'Service Class for Networking')
flags.DEFINE_string('flat_network_bridge', 'br100',
'Bridge for simple network instances')
flags.DEFINE_list('flat_network_ips',
['192.168.0.2','192.168.0.3','192.168.0.4'],
'Available ips for simple network')
flags.DEFINE_string('flat_network_network', '192.168.0.0',
'Network for simple network')
flags.DEFINE_string('flat_network_netmask', '255.255.255.0',
'Netmask for simple network')
flags.DEFINE_string('flat_network_gateway', '192.168.0.1',
'Broadcast for simple network')
flags.DEFINE_string('flat_network_broadcast', '192.168.0.255',
'Broadcast for simple network')
flags.DEFINE_string('flat_network_dns', '8.8.4.4',
'Dns for simple network')
def __init__(self):
logging.debug("Network node working")
def type_to_class(network_type):
if network_type == 'flat':
return FlatNetworkService
elif network_type == 'vlan':
return VlanNetworkService
raise NotFound("Couldn't find %s network type" % network_type)
def setup_compute_network(network_type, user_id, project_id, security_group):
srv = type_to_class(network_type)
srv.setup_compute_network(network_type, user_id, project_id, security_group)
def get_host_for_project(project_id):
redis = datastore.Redis.instance()
return redis.get(_host_key(project_id))
def _host_key(project_id):
return "network_host:%s" % project_id
class BaseNetworkService(service.Service):
"""Implements common network service functionality
This class must be subclassed.
"""
def __init__(self, *args, **kwargs):
self.network = model.PublicNetworkController()
def set_network_host(self, user_id, project_id, *args, **kwargs):
"""Safely sets the host of the projects network"""
redis = datastore.Redis.instance()
key = _host_key(project_id)
if redis.setnx(key, FLAGS.node_name):
self._on_set_network_host(user_id, project_id,
security_group='default',
*args, **kwargs)
return FLAGS.node_name
else:
return redis.get(key)
def allocate_fixed_ip(self, user_id, project_id,
security_group='default',
*args, **kwargs):
"""Subclass implements getting fixed ip from the pool"""
raise NotImplementedError()
def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs):
"""Subclass implements return of ip to the pool"""
raise NotImplementedError()
def _on_set_network_host(self, user_id, project_id,
*args, **kwargs):
"""Called when this host becomes the host for a project"""
pass
@classmethod
def setup_compute_network(self, user_id, project_id, security_group,
*args, **kwargs):
"""Sets up matching network for compute hosts"""
raise NotImplementedError()
def allocate_elastic_ip(self, user_id, project_id):
"""Gets a elastic ip from the pool"""
# NOTE(vish): Replicating earlier decision to use 'public' as
# mac address name, although this should probably
# be done inside of the PublicNetworkController
return self.network.allocate_ip(user_id, project_id, 'public')
def associate_elastic_ip(self, elastic_ip, fixed_ip, instance_id):
"""Associates an elastic ip to a fixed ip"""
self.network.associate_address(elastic_ip, fixed_ip, instance_id)
def disassociate_elastic_ip(self, elastic_ip):
"""Disassociates a elastic ip"""
self.network.disassociate_address(elastic_ip)
def deallocate_elastic_ip(self, elastic_ip):
"""Returns a elastic ip to the pool"""
self.network.deallocate_ip(elastic_ip)
class FlatNetworkService(BaseNetworkService):
"""Basic network where no vlans are used"""
@classmethod
def setup_compute_network(self, user_id, project_id, security_group,
*args, **kwargs):
"""Network is created manually"""
pass
def allocate_fixed_ip(self, user_id, project_id,
security_group='default',
*args, **kwargs):
"""Gets a fixed ip from the pool
Flat network just grabs the next available ip from the pool
"""
# NOTE(vish): Some automation could be done here. For example,
# creating the flat_network_bridge and setting up
# a gateway. This is all done manually atm
redis = datastore.Redis.instance()
if not redis.exists('ips') and not len(redis.keys('instances:*')):
for fixed_ip in FLAGS.flat_network_ips:
redis.sadd('ips', fixed_ip)
fixed_ip = redis.spop('ips')
if not fixed_ip:
raise exception.NoMoreAddresses()
return {'inject_network': True,
'network_type': FLAGS.network_type,
'mac_address': utils.generate_mac(),
'private_dns_name': str(fixed_ip),
'bridge_name': FLAGS.flat_network_bridge,
'network_network': FLAGS.flat_network_network,
'network_netmask': FLAGS.flat_network_netmask,
'network_gateway': FLAGS.flat_network_gateway,
'network_broadcast': FLAGS.flat_network_broadcast,
'network_dns': FLAGS.flat_network_dns}
def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs):
"""Returns an ip to the pool"""
datastore.Redis.instance().sadd('ips', fixed_ip)
class VlanNetworkService(BaseNetworkService):
"""Vlan network with dhcp"""
# NOTE(vish): A lot of the interactions with network/model.py can be
# simplified and improved. Also there it may be useful
# to support vlans separately from dhcp, instead of having
# both of them together in this class.
def allocate_fixed_ip(self, user_id, project_id,
security_group='default',
vpn=False, *args, **kwargs):
"""Gets a fixed ip from the pool """
mac = utils.generate_mac()
net = model.get_project_network(project_id)
if vpn:
fixed_ip = net.allocate_vpn_ip(user_id, project_id, mac)
else:
fixed_ip = net.allocate_ip(user_id, project_id, mac)
return {'network_type': FLAGS.network_type,
'bridge_name': net['bridge_name'],
'mac_address': mac,
'private_dns_name' : fixed_ip}
def deallocate_fixed_ip(self, fixed_ip,
*args, **kwargs):
"""Returns an ip to the pool"""
return model.get_network_by_address(fixed_ip).deallocate_ip(fixed_ip)
def lease_ip(self, address):
return model.get_network_by_address(address).lease_ip(address)
def release_ip(self, address):
return model.get_network_by_address(address).release_ip(address)
def restart_nets(self):
"""Ensure the network for each user is enabled"""
for project in manager.AuthManager().get_projects():
model.get_project_network(project.id).express()
def _on_set_network_host(self, user_id, project_id,
*args, **kwargs):
"""Called when this host becomes the host for a project"""
vpn.NetworkData.create(project_id)
@classmethod
def setup_compute_network(self, user_id, project_id, security_group,
*args, **kwargs):
"""Sets up matching network for compute hosts"""
# NOTE(vish): Use BridgedNetwork instead of DHCPNetwork because
# we don't want to run dnsmasq on the client machines
net = model.BridgedNetwork.get_network_for_project(
user_id,
project_id,
security_group)
net.express()

116
nova/network/vpn.py Normal file
View File

@ -0,0 +1,116 @@
# 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.
"""Network Data for projects"""
from nova import datastore
from nova import exception
from nova import flags
from nova import utils
FLAGS = flags.FLAGS
flags.DEFINE_string('vpn_ip', utils.get_my_ip(),
'Public IP for the cloudpipe VPN servers')
flags.DEFINE_integer('vpn_start_port', 1000,
'Start port for the cloudpipe VPN servers')
flags.DEFINE_integer('vpn_end_port', 2000,
'End port for the cloudpipe VPN servers')
class NoMorePorts(exception.Error):
pass
class NetworkData(datastore.BasicModel):
"""Manages network host, and vpn ip and port for projects"""
def __init__(self, project_id):
self.project_id = project_id
super(NetworkData, self).__init__()
@property
def identifier(self):
"""Identifier used for key in redis"""
return self.project_id
@classmethod
def create(cls, project_id):
"""Creates a vpn for project
This method finds a free ip and port and stores the associated
values in the datastore.
"""
# TODO(vish): will we ever need multiiple ips per host?
port = cls.find_free_port_for_ip(FLAGS.vpn_ip)
network_data = cls(project_id)
# save ip for project
network_data['host'] = FLAGS.node_name
network_data['project'] = project_id
network_data['ip'] = FLAGS.vpn_ip
network_data['port'] = port
network_data.save()
return network_data
@classmethod
def find_free_port_for_ip(cls, ip):
"""Finds a free port for a given ip from the redis set"""
# TODO(vish): these redis commands should be generalized and
# placed into a base class. Conceptually, it is
# similar to an association, but we are just
# storing a set of values instead of keys that
# should be turned into objects.
redis = datastore.Redis.instance()
key = 'ip:%s:ports' % ip
# TODO(vish): these ports should be allocated through an admin
# command instead of a flag
if (not redis.exists(key) and
not redis.exists(cls._redis_association_name('ip', ip))):
for i in range(FLAGS.vpn_start_port, FLAGS.vpn_end_port + 1):
redis.sadd(key, i)
port = redis.spop(key)
if not port:
raise NoMorePorts()
return port
@classmethod
def num_ports_for_ip(cls, ip):
"""Calculates the number of free ports for a given ip"""
return datastore.Redis.instance().scard('ip:%s:ports' % ip)
@property
def ip(self):
"""The ip assigned to the project"""
return self['ip']
@property
def port(self):
"""The port assigned to the project"""
return int(self['port'])
def save(self):
"""Saves the association to the given ip"""
self.associate_with('ip', self.ip)
super(NetworkData, self).save()
def destroy(self):
"""Cleans up datastore and adds port back to pool"""
self.unassociate_with('ip', self.ip)
datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port)
super(NetworkData, self).destroy()

View File

@ -266,7 +266,8 @@ class ImagesResource(Resource):
""" returns a json listing of all images
that a user has permissions to see """
images = [i for i in image.Image.all() if i.is_authorized(request.context)]
images = [i for i in image.Image.all() \
if i.is_authorized(request.context, readonly=True)]
request.write(json.dumps([i.metadata for i in images]))
request.finish()

View File

@ -65,9 +65,13 @@ class Image(object):
except:
pass
def is_authorized(self, context):
def is_authorized(self, context, readonly=False):
# NOTE(devcamcar): Public images can be read by anyone,
# but only modified by admin or owner.
try:
return self.metadata['isPublic'] or context.user.is_admin() or self.metadata['imageOwnerId'] == context.project.id
return (self.metadata['isPublic'] and readonly) or \
context.user.is_admin() or \
self.metadata['imageOwnerId'] == context.project.id
except:
return False

View File

@ -238,12 +238,12 @@ def send_message(topic, message, wait=True):
exchange=msg_id,
auto_delete=True,
exchange_type="direct",
routing_key=msg_id,
durable=False)
routing_key=msg_id)
consumer.register_callback(generic_response)
publisher = messaging.Publisher(connection=Connection.instance(),
exchange=FLAGS.control_exchange,
durable=False,
exchange_type="topic",
routing_key=topic)
publisher.send(message)

View File

@ -135,10 +135,18 @@ class AuthTestCase(test.BaseTestCase):
self.manager.add_to_project('test2', 'testproj')
self.assertTrue(self.manager.get_project('testproj').has_member('test2'))
def test_208_can_remove_user_from_project(self):
def test_207_can_remove_user_from_project(self):
self.manager.remove_from_project('test2', 'testproj')
self.assertFalse(self.manager.get_project('testproj').has_member('test2'))
def test_208_can_remove_add_user_with_role(self):
self.manager.add_to_project('test2', 'testproj')
self.manager.add_role('test2', 'developer', 'testproj')
self.manager.remove_from_project('test2', 'testproj')
self.assertFalse(self.manager.has_role('test2', 'developer', 'testproj'))
self.manager.add_to_project('test2', 'testproj')
self.manager.remove_from_project('test2', 'testproj')
def test_209_can_generate_x509(self):
# MUST HAVE RUN CLOUD SETUP BY NOW
self.cloud = cloud.CloudController()
@ -179,20 +187,6 @@ class AuthTestCase(test.BaseTestCase):
self.manager.remove_role('test1', 'sysadmin')
self.assertFalse(project.has_role('test1', 'sysadmin'))
def test_212_vpn_ip_and_port_looks_valid(self):
project = self.manager.get_project('testproj')
self.assert_(project.vpn_ip)
self.assert_(project.vpn_port >= FLAGS.vpn_start_port)
self.assert_(project.vpn_port <= FLAGS.vpn_end_port)
def test_213_too_many_vpns(self):
vpns = []
for i in xrange(manager.Vpn.num_ports_for_ip(FLAGS.vpn_ip)):
vpns.append(manager.Vpn.create("vpnuser%s" % i))
self.assertRaises(manager.NoMorePorts, manager.Vpn.create, "boom")
for vpn in vpns:
vpn.destroy()
def test_214_can_retrieve_project_by_user(self):
project = self.manager.create_project('testproj2', 'test2', 'Another test project', ['test2'])
self.assert_(len(self.manager.get_projects()) > 1)

View File

@ -19,9 +19,7 @@
from datetime import datetime, timedelta
import logging
import time
from twisted.internet import defer
from nova import exception
from nova import flags
from nova import test
from nova import utils
@ -49,9 +47,9 @@ class ModelTestCase(test.TrialTestCase):
inst['user_id'] = 'fake'
inst['project_id'] = 'fake'
inst['instance_type'] = 'm1.tiny'
inst['node_name'] = FLAGS.node_name
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
inst['private_dns_name'] = '10.0.0.1'
inst.save()
return inst
@ -71,118 +69,126 @@ class ModelTestCase(test.TrialTestCase):
session_token.save()
return session_token
@defer.inlineCallbacks
def test_create_instance(self):
"""store with create_instace, then test that a load finds it"""
instance = yield self.create_instance()
old = yield model.Instance(instance.identifier)
instance = self.create_instance()
old = model.Instance(instance.identifier)
self.assertFalse(old.is_new_record())
@defer.inlineCallbacks
def test_delete_instance(self):
"""create, then destroy, then make sure loads a new record"""
instance = yield self.create_instance()
yield instance.destroy()
newinst = yield model.Instance('i-test')
instance = self.create_instance()
instance.destroy()
newinst = model.Instance('i-test')
self.assertTrue(newinst.is_new_record())
@defer.inlineCallbacks
def test_instance_added_to_set(self):
"""create, then check that it is listed for the project"""
instance = yield self.create_instance()
"""create, then check that it is listed in global set"""
instance = self.create_instance()
found = False
for x in model.InstanceDirectory().all:
if x.identifier == 'i-test':
found = True
self.assert_(found)
@defer.inlineCallbacks
def test_instance_associates_project(self):
"""create, then check that it is listed for the project"""
instance = yield self.create_instance()
instance = self.create_instance()
found = False
for x in model.InstanceDirectory().by_project(instance.project):
if x.identifier == 'i-test':
found = True
self.assert_(found)
@defer.inlineCallbacks
def test_instance_associates_ip(self):
"""create, then check that it is listed for the ip"""
instance = self.create_instance()
found = False
x = model.InstanceDirectory().by_ip(instance['private_dns_name'])
self.assertEqual(x.identifier, 'i-test')
def test_instance_associates_node(self):
"""create, then check that it is listed for the node_name"""
instance = self.create_instance()
found = False
for x in model.InstanceDirectory().by_node(FLAGS.node_name):
if x.identifier == 'i-test':
found = True
self.assertFalse(found)
instance['node_name'] = 'test_node'
instance.save()
for x in model.InstanceDirectory().by_node('test_node'):
if x.identifier == 'i-test':
found = True
self.assert_(found)
def test_host_class_finds_hosts(self):
host = yield self.create_host()
host = self.create_host()
self.assertEqual('testhost', model.Host.lookup('testhost').identifier)
@defer.inlineCallbacks
def test_host_class_doesnt_find_missing_hosts(self):
rv = yield model.Host.lookup('woahnelly')
rv = model.Host.lookup('woahnelly')
self.assertEqual(None, rv)
@defer.inlineCallbacks
def test_create_host(self):
"""store with create_host, then test that a load finds it"""
host = yield self.create_host()
old = yield model.Host(host.identifier)
host = self.create_host()
old = model.Host(host.identifier)
self.assertFalse(old.is_new_record())
@defer.inlineCallbacks
def test_delete_host(self):
"""create, then destroy, then make sure loads a new record"""
instance = yield self.create_host()
yield instance.destroy()
newinst = yield model.Host('testhost')
instance = self.create_host()
instance.destroy()
newinst = model.Host('testhost')
self.assertTrue(newinst.is_new_record())
@defer.inlineCallbacks
def test_host_added_to_set(self):
"""create, then check that it is included in list"""
instance = yield self.create_host()
instance = self.create_host()
found = False
for x in model.Host.all():
if x.identifier == 'testhost':
found = True
self.assert_(found)
@defer.inlineCallbacks
def test_create_daemon_two_args(self):
"""create a daemon with two arguments"""
d = yield self.create_daemon()
d = self.create_daemon()
d = model.Daemon('testhost', 'nova-testdaemon')
self.assertFalse(d.is_new_record())
@defer.inlineCallbacks
def test_create_daemon_single_arg(self):
"""Create a daemon using the combined host:bin format"""
d = yield model.Daemon("testhost:nova-testdaemon")
d = model.Daemon("testhost:nova-testdaemon")
d.save()
d = model.Daemon('testhost:nova-testdaemon')
self.assertFalse(d.is_new_record())
@defer.inlineCallbacks
def test_equality_of_daemon_single_and_double_args(self):
"""Create a daemon using the combined host:bin arg, find with 2"""
d = yield model.Daemon("testhost:nova-testdaemon")
d = model.Daemon("testhost:nova-testdaemon")
d.save()
d = model.Daemon('testhost', 'nova-testdaemon')
self.assertFalse(d.is_new_record())
@defer.inlineCallbacks
def test_equality_daemon_of_double_and_single_args(self):
"""Create a daemon using the combined host:bin arg, find with 2"""
d = yield self.create_daemon()
d = self.create_daemon()
d = model.Daemon('testhost:nova-testdaemon')
self.assertFalse(d.is_new_record())
@defer.inlineCallbacks
def test_delete_daemon(self):
"""create, then destroy, then make sure loads a new record"""
instance = yield self.create_daemon()
yield instance.destroy()
newinst = yield model.Daemon('testhost', 'nova-testdaemon')
instance = self.create_daemon()
instance.destroy()
newinst = model.Daemon('testhost', 'nova-testdaemon')
self.assertTrue(newinst.is_new_record())
@defer.inlineCallbacks
def test_daemon_heartbeat(self):
"""Create a daemon, sleep, heartbeat, check for update"""
d = yield self.create_daemon()
d = self.create_daemon()
ts = d['updated_at']
time.sleep(2)
d.heartbeat()
@ -190,70 +196,62 @@ class ModelTestCase(test.TrialTestCase):
ts2 = d2['updated_at']
self.assert_(ts2 > ts)
@defer.inlineCallbacks
def test_daemon_added_to_set(self):
"""create, then check that it is included in list"""
instance = yield self.create_daemon()
instance = self.create_daemon()
found = False
for x in model.Daemon.all():
if x.identifier == 'testhost:nova-testdaemon':
found = True
self.assert_(found)
@defer.inlineCallbacks
def test_daemon_associates_host(self):
"""create, then check that it is listed for the host"""
instance = yield self.create_daemon()
instance = self.create_daemon()
found = False
for x in model.Daemon.by_host('testhost'):
if x.identifier == 'testhost:nova-testdaemon':
found = True
self.assertTrue(found)
@defer.inlineCallbacks
def test_create_session_token(self):
"""create"""
d = yield self.create_session_token()
d = self.create_session_token()
d = model.SessionToken(d.token)
self.assertFalse(d.is_new_record())
@defer.inlineCallbacks
def test_delete_session_token(self):
"""create, then destroy, then make sure loads a new record"""
instance = yield self.create_session_token()
yield instance.destroy()
newinst = yield model.SessionToken(instance.token)
instance = self.create_session_token()
instance.destroy()
newinst = model.SessionToken(instance.token)
self.assertTrue(newinst.is_new_record())
@defer.inlineCallbacks
def test_session_token_added_to_set(self):
"""create, then check that it is included in list"""
instance = yield self.create_session_token()
instance = self.create_session_token()
found = False
for x in model.SessionToken.all():
if x.identifier == instance.token:
found = True
self.assert_(found)
@defer.inlineCallbacks
def test_session_token_associates_user(self):
"""create, then check that it is listed for the user"""
instance = yield self.create_session_token()
instance = self.create_session_token()
found = False
for x in model.SessionToken.associated_to('user', 'testuser'):
if x.identifier == instance.identifier:
found = True
self.assertTrue(found)
@defer.inlineCallbacks
def test_session_token_generation(self):
instance = yield model.SessionToken.generate('username', 'TokenType')
instance = model.SessionToken.generate('username', 'TokenType')
self.assertFalse(instance.is_new_record())
@defer.inlineCallbacks
def test_find_generated_session_token(self):
instance = yield model.SessionToken.generate('username', 'TokenType')
found = yield model.SessionToken.lookup(instance.identifier)
instance = model.SessionToken.generate('username', 'TokenType')
found = model.SessionToken.lookup(instance.identifier)
self.assert_(found)
def test_update_session_token_expiry(self):
@ -264,34 +262,29 @@ class ModelTestCase(test.TrialTestCase):
expiry = utils.parse_isotime(instance['expiry'])
self.assert_(expiry > datetime.utcnow())
@defer.inlineCallbacks
def test_session_token_lookup_when_expired(self):
instance = yield model.SessionToken.generate("testuser")
instance = model.SessionToken.generate("testuser")
instance['expiry'] = datetime.utcnow().strftime(utils.TIME_FORMAT)
instance.save()
inst = model.SessionToken.lookup(instance.identifier)
self.assertFalse(inst)
@defer.inlineCallbacks
def test_session_token_lookup_when_not_expired(self):
instance = yield model.SessionToken.generate("testuser")
instance = model.SessionToken.generate("testuser")
inst = model.SessionToken.lookup(instance.identifier)
self.assert_(inst)
@defer.inlineCallbacks
def test_session_token_is_expired_when_expired(self):
instance = yield model.SessionToken.generate("testuser")
instance = model.SessionToken.generate("testuser")
instance['expiry'] = datetime.utcnow().strftime(utils.TIME_FORMAT)
self.assert_(instance.is_expired())
@defer.inlineCallbacks
def test_session_token_is_expired_when_not_expired(self):
instance = yield model.SessionToken.generate("testuser")
instance = model.SessionToken.generate("testuser")
self.assertFalse(instance.is_expired())
@defer.inlineCallbacks
def test_session_token_ttl(self):
instance = yield model.SessionToken.generate("testuser")
instance = model.SessionToken.generate("testuser")
now = datetime.utcnow()
delta = timedelta(hours=1)
instance['expiry'] = (now + delta).strftime(utils.TIME_FORMAT)

View File

@ -24,8 +24,10 @@ from nova import flags
from nova import test
from nova import utils
from nova.auth import manager
from nova.compute import network
from nova.compute.exception import NoMoreAddresses
from nova.network import model
from nova.network import service
from nova.network import vpn
from nova.network.exception import NoMoreAddresses
FLAGS = flags.FLAGS
@ -52,7 +54,8 @@ class NetworkTestCase(test.TrialTestCase):
self.projects.append(self.manager.create_project(name,
'netuser',
name))
self.network = network.PublicNetworkController()
self.network = model.PublicNetworkController()
self.service = service.VlanNetworkService()
def tearDown(self):
super(NetworkTestCase, self).tearDown()
@ -66,16 +69,17 @@ class NetworkTestCase(test.TrialTestCase):
self.assertTrue(IPy.IP(address) in pubnet)
self.assertTrue(IPy.IP(address) in self.network.network)
def test_allocate_deallocate_ip(self):
address = network.allocate_ip(
self.user.id, self.projects[0].id, utils.generate_mac())
def test_allocate_deallocate_fixed_ip(self):
result = yield self.service.allocate_fixed_ip(
self.user.id, self.projects[0].id)
address = result['private_dns_name']
mac = result['mac_address']
logging.debug("Was allocated %s" % (address))
net = network.get_project_network(self.projects[0].id, "default")
net = model.get_project_network(self.projects[0].id, "default")
self.assertEqual(True, is_in_project(address, self.projects[0].id))
mac = utils.generate_mac()
hostname = "test-host"
self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name)
rv = network.deallocate_ip(address)
rv = self.service.deallocate_fixed_ip(address)
# Doesn't go away until it's dhcp released
self.assertEqual(True, is_in_project(address, self.projects[0].id))
@ -84,15 +88,18 @@ class NetworkTestCase(test.TrialTestCase):
self.assertEqual(False, is_in_project(address, self.projects[0].id))
def test_range_allocation(self):
mac = utils.generate_mac()
secondmac = utils.generate_mac()
hostname = "test-host"
address = network.allocate_ip(
self.user.id, self.projects[0].id, mac)
secondaddress = network.allocate_ip(
self.user, self.projects[1].id, secondmac)
net = network.get_project_network(self.projects[0].id, "default")
secondnet = network.get_project_network(self.projects[1].id, "default")
result = yield self.service.allocate_fixed_ip(
self.user.id, self.projects[0].id)
mac = result['mac_address']
address = result['private_dns_name']
result = yield self.service.allocate_fixed_ip(
self.user, self.projects[1].id)
secondmac = result['mac_address']
secondaddress = result['private_dns_name']
net = model.get_project_network(self.projects[0].id, "default")
secondnet = model.get_project_network(self.projects[1].id, "default")
self.assertEqual(True, is_in_project(address, self.projects[0].id))
self.assertEqual(True, is_in_project(secondaddress, self.projects[1].id))
@ -103,46 +110,64 @@ class NetworkTestCase(test.TrialTestCase):
self.dnsmasq.issue_ip(secondmac, secondaddress,
hostname, secondnet.bridge_name)
rv = network.deallocate_ip(address)
rv = self.service.deallocate_fixed_ip(address)
self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
self.assertEqual(False, is_in_project(address, self.projects[0].id))
# First address release shouldn't affect the second
self.assertEqual(True, is_in_project(secondaddress, self.projects[1].id))
rv = network.deallocate_ip(secondaddress)
rv = self.service.deallocate_fixed_ip(secondaddress)
self.dnsmasq.release_ip(secondmac, secondaddress,
hostname, secondnet.bridge_name)
self.assertEqual(False, is_in_project(secondaddress, self.projects[1].id))
def test_subnet_edge(self):
secondaddress = network.allocate_ip(self.user.id, self.projects[0].id,
utils.generate_mac())
result = yield self.service.allocate_fixed_ip(self.user.id,
self.projects[0].id)
firstaddress = result['private_dns_name']
hostname = "toomany-hosts"
for i in range(1,5):
project_id = self.projects[i].id
mac = utils.generate_mac()
mac2 = utils.generate_mac()
mac3 = utils.generate_mac()
address = network.allocate_ip(
self.user, project_id, mac)
address2 = network.allocate_ip(
self.user, project_id, mac2)
address3 = network.allocate_ip(
self.user, project_id, mac3)
result = yield self.service.allocate_fixed_ip(
self.user, project_id)
mac = result['mac_address']
address = result['private_dns_name']
result = yield self.service.allocate_fixed_ip(
self.user, project_id)
mac2 = result['mac_address']
address2 = result['private_dns_name']
result = yield self.service.allocate_fixed_ip(
self.user, project_id)
mac3 = result['mac_address']
address3 = result['private_dns_name']
self.assertEqual(False, is_in_project(address, self.projects[0].id))
self.assertEqual(False, is_in_project(address2, self.projects[0].id))
self.assertEqual(False, is_in_project(address3, self.projects[0].id))
rv = network.deallocate_ip(address)
rv = network.deallocate_ip(address2)
rv = network.deallocate_ip(address3)
net = network.get_project_network(project_id, "default")
rv = self.service.deallocate_fixed_ip(address)
rv = self.service.deallocate_fixed_ip(address2)
rv = self.service.deallocate_fixed_ip(address3)
net = model.get_project_network(project_id, "default")
self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
self.dnsmasq.release_ip(mac2, address2, hostname, net.bridge_name)
self.dnsmasq.release_ip(mac3, address3, hostname, net.bridge_name)
net = network.get_project_network(self.projects[0].id, "default")
rv = network.deallocate_ip(secondaddress)
self.dnsmasq.release_ip(mac, secondaddress, hostname, net.bridge_name)
net = model.get_project_network(self.projects[0].id, "default")
rv = self.service.deallocate_fixed_ip(firstaddress)
self.dnsmasq.release_ip(mac, firstaddress, hostname, net.bridge_name)
def test_212_vpn_ip_and_port_looks_valid(self):
vpn.NetworkData.create(self.projects[0].id)
self.assert_(self.projects[0].vpn_ip)
self.assert_(self.projects[0].vpn_port >= FLAGS.vpn_start_port)
self.assert_(self.projects[0].vpn_port <= FLAGS.vpn_end_port)
def test_too_many_vpns(self):
vpns = []
for i in xrange(vpn.NetworkData.num_ports_for_ip(FLAGS.vpn_ip)):
vpns.append(vpn.NetworkData.create("vpnuser%s" % i))
self.assertRaises(vpn.NoMorePorts, vpn.NetworkData.create, "boom")
for network_datum in vpns:
network_datum.destroy()
def test_release_before_deallocate(self):
pass
@ -169,7 +194,7 @@ class NetworkTestCase(test.TrialTestCase):
NUM_RESERVED_VPN_IPS)
usable addresses
"""
net = network.get_project_network(self.projects[0].id, "default")
net = model.get_project_network(self.projects[0].id, "default")
# Determine expected number of available IP addresses
num_static_ips = net.num_static_ips
@ -183,22 +208,23 @@ class NetworkTestCase(test.TrialTestCase):
macs = {}
addresses = {}
for i in range(0, (num_available_ips - 1)):
macs[i] = utils.generate_mac()
addresses[i] = network.allocate_ip(self.user.id, self.projects[0].id, macs[i])
result = yield self.service.allocate_fixed_ip(self.user.id, self.projects[0].id)
macs[i] = result['mac_address']
addresses[i] = result['private_dns_name']
self.dnsmasq.issue_ip(macs[i], addresses[i], hostname, net.bridge_name)
self.assertRaises(NoMoreAddresses, network.allocate_ip, self.user.id, self.projects[0].id, utils.generate_mac())
self.assertFailure(self.service.allocate_fixed_ip(self.user.id, self.projects[0].id), NoMoreAddresses)
for i in range(0, (num_available_ips - 1)):
rv = network.deallocate_ip(addresses[i])
rv = self.service.deallocate_fixed_ip(addresses[i])
self.dnsmasq.release_ip(macs[i], addresses[i], hostname, net.bridge_name)
def is_in_project(address, project_id):
return address in network.get_project_network(project_id).list_addresses()
return address in model.get_project_network(project_id).list_addresses()
def _get_project_addresses(project_id):
project_addresses = []
for addr in network.get_project_network(project_id).list_addresses():
for addr in model.get_project_network(project_id).list_addresses():
project_addresses.append(addr)
return project_addresses

View File

@ -65,7 +65,7 @@ def _fetch_s3_image(image, path, user, project):
cmd += ['-o', path]
return process.SharedPool().execute(executable=cmd[0], args=cmd[1:])
def _fetch_local_image(image, path, _):
def _fetch_local_image(image, path, user, project):
source = _image_path('%s/image' % image)
return process.simple_execute('cp %s %s' % (source, path))

View File

@ -46,6 +46,9 @@ FLAGS = flags.FLAGS
flags.DEFINE_string('libvirt_xml_template',
utils.abspath('compute/libvirt.xml.template'),
'Libvirt XML Template')
flags.DEFINE_string('injected_network_template',
utils.abspath('compute/interfaces.template'),
'Template file for injected network')
flags.DEFINE_string('libvirt_type',
'kvm',
@ -205,14 +208,14 @@ class LibvirtConnection(object):
key = data['key_data']
net = None
if FLAGS.simple_network:
with open(FLAGS.simple_network_template) as f:
if data.get('inject_network', False):
with open(FLAGS.injected_network_template) as f:
net = f.read() % {'address': data['private_dns_name'],
'network': FLAGS.simple_network_network,
'netmask': FLAGS.simple_network_netmask,
'gateway': FLAGS.simple_network_gateway,
'broadcast': FLAGS.simple_network_broadcast,
'dns': FLAGS.simple_network_dns}
'network': data['network_network'],
'netmask': data['network_netmask'],
'gateway': data['network_gateway'],
'broadcast': data['network_broadcast'],
'dns': data['network_dns']}
if key or net:
logging.info('Injecting data into image %s', data['image_id'])
yield disk.inject_data(basepath('disk-raw'), key, net, execute=execute)