Admin API + Worker Tracking.
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Tornado daemon for the main API endpoint.
|
||||
Tornado daemon for the main API endpoint.
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -34,6 +34,7 @@ from nova import rpc
|
||||
from nova import server
|
||||
from nova import utils
|
||||
from nova.auth import users
|
||||
from nova.compute import model
|
||||
from nova.endpoint import admin
|
||||
from nova.endpoint import api
|
||||
from nova.endpoint import cloud
|
||||
@@ -43,9 +44,10 @@ FLAGS = flags.FLAGS
|
||||
|
||||
def main(_argv):
|
||||
user_manager = users.UserManager()
|
||||
host_manager = model.Host
|
||||
controllers = {
|
||||
'Cloud': cloud.CloudController(),
|
||||
'Admin': admin.AdminController(user_manager)
|
||||
'Admin': admin.AdminController(user_manager, host_manager)
|
||||
}
|
||||
_app = api.APIServerApplication(user_manager, controllers)
|
||||
|
||||
|
@@ -75,8 +75,8 @@ def main():
|
||||
topic='%s.%s' % (FLAGS.compute_topic, FLAGS.node_name),
|
||||
proxy=n)
|
||||
|
||||
# heartbeat = task.LoopingCall(n.report_state)
|
||||
# heartbeat.start(interval=FLAGS.node_report_state_interval, now=False)
|
||||
pulse = task.LoopingCall(n.report_state, FLAGS.node_name, 'nova-compute')
|
||||
pulse.start(interval=FLAGS.node_report_state_interval, now=False)
|
||||
|
||||
injected = consumer_all.attach_to_twisted()
|
||||
injected = consumer_node.attach_to_twisted()
|
||||
|
@@ -21,9 +21,11 @@
|
||||
Nova User API client library.
|
||||
"""
|
||||
|
||||
import base64
|
||||
|
||||
from nova import vendor
|
||||
import boto
|
||||
from boto.ec2.regioninfo import RegionInfo
|
||||
import base64
|
||||
|
||||
class UserInfo(object):
|
||||
""" Information about a Nova user
|
||||
@@ -57,6 +59,30 @@ class UserInfo(object):
|
||||
elif name == 'secretkey':
|
||||
self.secretkey = str(value)
|
||||
|
||||
class HostInfo(object):
|
||||
"""
|
||||
Information about a Nova Host:
|
||||
Disk stats
|
||||
Running Instances
|
||||
Memory stats
|
||||
CPU stats
|
||||
Network address info
|
||||
Firewall info
|
||||
Bridge and devices
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.hostname = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'Host:%s' % self.hostname
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
setattr(self, name, value)
|
||||
|
||||
class NovaAdminClient(object):
|
||||
def __init__(self, clc_ip='127.0.0.1', region='nova', access_key='admin',
|
||||
@@ -91,7 +117,7 @@ class NovaAdminClient(object):
|
||||
|
||||
def get_users(self):
|
||||
""" grabs the list of all users """
|
||||
return self.apiconn.get_list('DescribeUsers', {}, (['item', UserInfo]))
|
||||
return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)])
|
||||
|
||||
def get_user(self, name):
|
||||
""" grab a single user by name """
|
||||
@@ -116,3 +142,6 @@ class NovaAdminClient(object):
|
||||
""" returns the content of a zip file containing novarc and access credentials. """
|
||||
return self.apiconn.get_object('GenerateX509ForUser', {'Name': username}, UserInfo).file
|
||||
|
||||
def get_hosts(self):
|
||||
return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)])
|
||||
|
||||
|
@@ -328,7 +328,7 @@ class UserManager(object):
|
||||
|
||||
user = self.get_user_from_access_key(access_key)
|
||||
if user == None:
|
||||
raise exception.NotFound('No user found for access key')
|
||||
raise exception.NotFound('No user found for access key %s' % access_key)
|
||||
if project_name is '':
|
||||
project_name = user.name
|
||||
|
||||
|
@@ -25,7 +25,7 @@ Admin API controller, exposed through http via the api worker.
|
||||
import base64
|
||||
|
||||
def user_dict(user, base64_file=None):
|
||||
"""Convert the user object to a result dict"""
|
||||
""" Convert the user object to a result dict """
|
||||
if user:
|
||||
return {
|
||||
'username': user.id,
|
||||
@@ -36,23 +36,17 @@ def user_dict(user, base64_file=None):
|
||||
else:
|
||||
return {}
|
||||
|
||||
def node_dict(node):
|
||||
"""Convert a node object to a result dict"""
|
||||
if node:
|
||||
return {
|
||||
'node_id': node.id,
|
||||
'workers': ", ".join(node.workers),
|
||||
'disks': ", ".join(node.disks),
|
||||
'ram': node.memory,
|
||||
'load_average' : node.load_average,
|
||||
}
|
||||
def host_dict(host):
|
||||
""" Convert a host model object to a result dict """
|
||||
if host:
|
||||
return host.state
|
||||
else:
|
||||
return {}
|
||||
|
||||
def admin_only(target):
|
||||
"""Decorator for admin-only API calls"""
|
||||
""" Decorator for admin-only API calls """
|
||||
def wrapper(*args, **kwargs):
|
||||
"""Internal wrapper method for admin-only API calls"""
|
||||
""" Internal wrapper method for admin-only API calls """
|
||||
context = args[1]
|
||||
if context.user.is_admin():
|
||||
return target(*args, **kwargs)
|
||||
@@ -63,27 +57,26 @@ def admin_only(target):
|
||||
|
||||
class AdminController(object):
|
||||
"""
|
||||
API Controller for users, node status, and worker mgmt.
|
||||
API Controller for users, hosts, nodes, and workers.
|
||||
Trivial admin_only wrapper will be replaced with RBAC,
|
||||
allowing project managers to administer project users.
|
||||
"""
|
||||
def __init__(self, user_manager, node_manager=None):
|
||||
|
||||
def __init__(self, user_manager, host_manager):
|
||||
self.user_manager = user_manager
|
||||
self.node_manager = node_manager
|
||||
self.host_manager = host_manager
|
||||
|
||||
def __str__(self):
|
||||
return 'AdminController'
|
||||
|
||||
@admin_only
|
||||
def describe_user(self, _context, name, **_kwargs):
|
||||
"""Returns user data, including access and secret keys.
|
||||
"""
|
||||
""" Returns user data, including access and secret keys. """
|
||||
return user_dict(self.user_manager.get_user(name))
|
||||
|
||||
@admin_only
|
||||
def describe_users(self, _context, **_kwargs):
|
||||
"""Returns all users - should be changed to deal with a list.
|
||||
"""
|
||||
""" Returns all users - should be changed to deal with a list. """
|
||||
return {'userSet':
|
||||
[user_dict(u) for u in self.user_manager.get_users()] }
|
||||
|
||||
@@ -116,7 +109,7 @@ class AdminController(object):
|
||||
return user_dict(user, base64.b64encode(project.get_credentials(user)))
|
||||
|
||||
@admin_only
|
||||
def describe_nodes(self, _context, **_kwargs):
|
||||
def describe_hosts(self, _context, **_kwargs):
|
||||
"""Returns status info for all nodes. Includes:
|
||||
* Disk Space
|
||||
* Instance List
|
||||
@@ -125,11 +118,11 @@ class AdminController(object):
|
||||
* DHCP servers running
|
||||
* Iptables / bridges
|
||||
"""
|
||||
return {'nodeSet':
|
||||
[node_dict(n) for n in self.node_manager.get_nodes()] }
|
||||
return {'hostSet':
|
||||
[host_dict(h) for h in self.host_manager.all()] }
|
||||
|
||||
@admin_only
|
||||
def describe_node(self, _context, name, **_kwargs):
|
||||
def describe_host(self, _context, name, **_kwargs):
|
||||
"""Returns status info for single node.
|
||||
"""
|
||||
return node_dict(self.node_manager.get_node(name))
|
||||
return host_dict(self.host_manager.lookup(name))
|
||||
|
213
nova/tests/model_unittest.py
Normal file
213
nova/tests/model_unittest.py
Normal file
@@ -0,0 +1,213 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# Copyright [2010] [Anso Labs, LLC]
|
||||
#
|
||||
# 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 logging
|
||||
import time
|
||||
|
||||
from nova import vendor
|
||||
from twisted.internet import defer
|
||||
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import test
|
||||
from nova import utils
|
||||
from nova.compute import model
|
||||
from nova.compute import node
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class ModelTestCase(test.TrialTestCase):
|
||||
def setUp(self):
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
super(ModelTestCase, self).setUp()
|
||||
self.flags(fake_libvirt=True,
|
||||
fake_storage=True,
|
||||
fake_users=True)
|
||||
|
||||
def tearDown(self):
|
||||
model.Instance('i-test').destroy()
|
||||
model.Host('testhost').destroy()
|
||||
model.Worker('testhost', 'nova-testworker').destroy()
|
||||
|
||||
def create_instance(self):
|
||||
inst = model.Instance('i-test')
|
||||
inst['reservation_id'] = 'r-test'
|
||||
inst['launch_time'] = '10'
|
||||
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.save()
|
||||
return inst
|
||||
|
||||
def create_host(self):
|
||||
host = model.Host('testhost')
|
||||
host.save()
|
||||
return host
|
||||
|
||||
def create_worker(self):
|
||||
worker = model.Worker('testhost', 'nova-testworker')
|
||||
worker.save()
|
||||
return worker
|
||||
|
||||
@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)
|
||||
self.assertEqual(False, old.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')
|
||||
self.assertEqual(True, newinst.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()
|
||||
found = False
|
||||
for x in model.InstanceDirectory().all:
|
||||
if x.identifier == 'i-test':
|
||||
found = True
|
||||
self.assertEqual(True, found)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_instance_associates_project(self):
|
||||
""" create, then check that it is listed for the project """
|
||||
instance = yield self.create_instance()
|
||||
found = False
|
||||
for x in model.InstanceDirectory().by_project(instance.project):
|
||||
if x.identifier == 'i-test':
|
||||
found = True
|
||||
self.assertEqual(True, found)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_host_class_finds_hosts(self):
|
||||
host = yield 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')
|
||||
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)
|
||||
self.assertEqual(False, old.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')
|
||||
self.assertEqual(True, newinst.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()
|
||||
found = False
|
||||
for x in model.Host.all():
|
||||
if x.identifier == 'testhost':
|
||||
found = True
|
||||
self.assertEqual(True, found)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_create_worker_two_args(self):
|
||||
""" create a worker with two arguments """
|
||||
w = yield self.create_worker()
|
||||
self.assertEqual(
|
||||
False,
|
||||
model.Worker('testhost', 'nova-testworker').new_record()
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_create_worker_single_arg(self):
|
||||
""" Create a worker using the combined host:bin format """
|
||||
w = yield model.Worker("testhost:nova-testworker")
|
||||
w.save()
|
||||
self.assertEqual(
|
||||
False,
|
||||
model.Worker('testhost:nova-testworker').new_record()
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_equality_of_worker_single_and_double_args(self):
|
||||
""" Create a worker using the combined host:bin arg, find with 2 """
|
||||
w = yield model.Worker("testhost:nova-testworker")
|
||||
w.save()
|
||||
self.assertEqual(
|
||||
False,
|
||||
model.Worker('testhost', 'nova-testworker').new_record()
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_equality_worker_of_double_and_single_args(self):
|
||||
""" Create a worker using the combined host:bin arg, find with 2 """
|
||||
w = yield self.create_worker()
|
||||
self.assertEqual(
|
||||
False,
|
||||
model.Worker('testhost:nova-testworker').new_record()
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_delete_worker(self):
|
||||
""" create, then destroy, then make sure loads a new record """
|
||||
instance = yield self.create_worker()
|
||||
yield instance.destroy()
|
||||
newinst = yield model.Worker('testhost', 'nova-testworker')
|
||||
self.assertEqual(True, newinst.new_record())
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_worker_heartbeat(self):
|
||||
""" Create a worker, sleep, heartbeat, check for update """
|
||||
w = yield self.create_worker()
|
||||
ts = w['updated_at']
|
||||
yield time.sleep(2)
|
||||
w.heartbeat()
|
||||
w2 = model.Worker('testhost', 'nova-testworker')
|
||||
ts2 = w2['updated_at']
|
||||
self.assertEqual(True, (ts2 > ts))
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_worker_added_to_set(self):
|
||||
""" create, then check that it is included in list """
|
||||
instance = yield self.create_worker()
|
||||
found = False
|
||||
for x in model.Worker.all():
|
||||
if x.identifier == 'testhost:nova-testworker':
|
||||
found = True
|
||||
self.assertEqual(True, found)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_worker_associates_host(self):
|
||||
""" create, then check that it is listed for the host """
|
||||
instance = yield self.create_worker()
|
||||
found = False
|
||||
for x in model.Worker.by_host('testhost'):
|
||||
if x.identifier == 'testhost:nova-testworker':
|
||||
found = True
|
||||
self.assertEqual(True, found)
|
@@ -61,6 +61,7 @@ from nova.tests.storage_unittest import *
|
||||
from nova.tests.users_unittest import *
|
||||
from nova.tests.datastore_unittest import *
|
||||
from nova.tests.validator_unittest import *
|
||||
from nova.tests.model_unittest import *
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
Reference in New Issue
Block a user