nova/nova/compute/model.py

234 lines
7.2 KiB
Python

# 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.
"""
Datastore Model objects for Compute Instances, with
InstanceDirectory manager.
# Create a new instance?
>>> InstDir = InstanceDirectory()
>>> inst = InstDir.new()
>>> inst.destroy()
True
>>> inst = InstDir['i-123']
>>> inst['ip'] = "192.168.0.3"
>>> inst['project_id'] = "projectA"
>>> inst.save()
True
>>> InstDir['i-123']
<Instance:i-123>
>>> InstDir.all.next()
<Instance:i-123>
>>> inst.destroy()
True
"""
import logging
import time
import redis
from nova import datastore
from nova import exception
from nova import flags
from nova import utils
FLAGS = flags.FLAGS
# TODO(todd): Implement this at the class level for Instance
class InstanceDirectory(object):
"""an api for interacting with the global state of instances"""
def get(self, instance_id):
"""returns an instance object for a given id"""
return Instance(instance_id)
def __getitem__(self, item):
return self.get(item)
@datastore.absorb_connection_error
def by_project(self, project):
"""returns a list of instance objects for a project"""
for instance_id in datastore.Redis.instance().smembers('project:%s:instances' % project):
yield Instance(instance_id)
def by_node(self, node_id):
"""returns a list of instances for a node"""
for instance in self.all:
if instance['node_name'] == node_id:
yield instance
def by_ip(self, ip_address):
"""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
def by_volume(self, volume_id):
"""returns the instance a volume is attached to"""
pass
@datastore.absorb_connection_error
def exists(self, instance_id):
return datastore.Redis.instance().sismember('instances', instance_id)
@property
@datastore.absorb_connection_error
def all(self):
"""returns a list of all instances"""
for instance_id in datastore.Redis.instance().smembers('instances'):
yield Instance(instance_id)
def new(self):
"""returns an empty Instance object, with ID"""
instance_id = utils.generate_uid('i')
return self.get(instance_id)
class Instance(datastore.BasicModel):
"""Wrapper around stored properties of an instance"""
def __init__(self, instance_id):
"""loads an instance from the datastore if exists"""
# set instance data before super call since it uses default_state
self.instance_id = instance_id
super(Instance, self).__init__()
def default_state(self):
return {'state': 0,
'state_description': 'pending',
'instance_id': self.instance_id,
'node_name': 'unassigned',
'project_id': 'unassigned',
'user_id': 'unassigned'}
@property
def identifier(self):
return self.instance_id
@property
def project(self):
if self.state.get('project_id', None):
return self.state['project_id']
return self.state.get('owner_id', 'unassigned')
@property
def volumes(self):
"""returns a list of attached volumes"""
pass
@property
def reservation(self):
"""Returns a reservation object"""
pass
def save(self):
"""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()
success = super(Instance, self).save()
if success and should_update_project:
self.associate_with("project", self.project)
if success and should_update_node:
self.associate_with("node", self['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'])
return super(Instance, self).destroy()
class Host(datastore.BasicModel):
"""A Host is the machine where a Daemon is running."""
def __init__(self, hostname):
"""loads an instance from the datastore if exists"""
# set instance data before super call since it uses default_state
self.hostname = hostname
super(Host, self).__init__()
def default_state(self):
return {"hostname": self.hostname}
@property
def identifier(self):
return self.hostname
class Daemon(datastore.BasicModel):
"""A Daemon is a job (compute, api, network, ...) that runs on a host."""
def __init__(self, host_or_combined, binpath=None):
"""loads an instance from the datastore if exists"""
# set instance data before super call since it uses default_state
# since loading from datastore expects a combined key that
# is equivilent to identifier, we need to expect that, while
# maintaining meaningful semantics (2 arguments) when creating
# from within other code like the bin/nova-* scripts
if binpath:
self.hostname = host_or_combined
self.binary = binpath
else:
self.hostname, self.binary = host_or_combined.split(":")
super(Daemon, self).__init__()
def default_state(self):
return {"hostname": self.hostname,
"binary": self.binary,
"updated_at": utils.isotime()
}
@property
def identifier(self):
return "%s:%s" % (self.hostname, self.binary)
def save(self):
"""Call into superclass to save object, then save associations"""
# NOTE(todd): this makes no attempt to destroy itsself,
# so after termination a record w/ old timestmap remains
success = super(Daemon, self).save()
if success:
self.associate_with("host", self.hostname)
return True
def destroy(self):
"""Destroy associations, then destroy the object"""
self.unassociate_with("host", self.hostname)
return super(Daemon, self).destroy()
def heartbeat(self):
self['updated_at'] = utils.isotime()
return self.save()
@classmethod
def by_host(cls, hostname):
for x in cls.associated_to("host", hostname):
yield x
if __name__ == "__main__":
import doctest
doctest.testmod()