Admin API + Worker Tracking.

This commit is contained in:
Todd Willey
2010-06-25 18:55:14 -04:00
parent c08fe5604c
commit 1de0074dc9
7 changed files with 270 additions and 32 deletions

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)])

View File

@@ -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

View File

@@ -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))

View 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)

View File

@@ -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