From dfe98891b46c4f02f13ea2686979ca7ff4547bd3 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Mon, 25 Oct 2010 23:10:51 +0000 Subject: Making net injection create /etc/network if non-existant --- nova/compute/disk.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/disk.py b/nova/compute/disk.py index e362b4507..f2e5f8570 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -171,6 +171,9 @@ def _inject_key_into_fs(key, fs, execute=None): @defer.inlineCallbacks def _inject_net_into_fs(net, fs, execute=None): - netfile = os.path.join(os.path.join(os.path.join( - fs, 'etc'), 'network'), 'interfaces') + netdir = os.path.join(os.path.join(fs, 'etc'), 'network') + yield execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter + yield execute('sudo chown root:root %s' % netdir) + yield execute('sudo chmod 755 %s' % netdir) + netfile = os.path.join(netdir, 'interfaces') yield execute('sudo tee %s' % netfile, net) -- cgit From a1287cf4e15d469163ec6465ea5f6ce200c20543 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 27 Oct 2010 17:31:46 -0400 Subject: cleanup rrd doc generation. --- nova/auth/fakeldap.py | 1 - nova/auth/manager.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index cf3a84a5d..1a49b73fe 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -79,7 +79,6 @@ def _match_query(query, attrs): &, |, and ! are supported in the query. No syntax checking is performed, so malformed querys will not work correctly. - """ # cut off the parentheses inner = query[1:-1] diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 001a96875..7b2b68161 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -84,12 +84,11 @@ class AuthBase(object): @classmethod def safe_id(cls, obj): - """Safe get object id + """Safely get object id. This method will return the id of the object if the object is of this class, otherwise it will return the original object. This allows methods to accept objects or ids as paramaters. - """ if isinstance(obj, cls): return obj.id -- cgit From e85ba051c27ab7d50914c7bf91db74d7cf7faa97 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 28 Oct 2010 12:00:25 -0400 Subject: clean up the compute documentation a bit. --- nova/compute/manager.py | 24 +++++++++++++++++++----- nova/virt/connection.py | 11 ++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 574feec7c..c5102c35a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -17,7 +17,20 @@ # under the License. """ -Handles all code relating to instances (guest vms) +Handles all processes relating to instances (guest vms). + +The :py:class:`ComputeManager` class is a :py:class:`nova.manager.Manager` that +handles RPC calls relating to creating instances. It is responsible for +building a disk image, launching it via the underlying virtualization driver, +responding to calls to check it state, attaching persistent as well as termination. + +Related Flags +------------- +:instances_path: Where instances are kept on disk +:compute_driver: Name of class that is used to handle virtualization, loaded + by `nova.utils.import_object` +:volume_manager: Name of class that handles persistent storage, loaded by + `nova.utils.import_object` """ import datetime @@ -40,12 +53,12 @@ flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection', class ComputeManager(manager.Manager): - """ - Manages the running instances. - """ + """Manages the running instances from creation to destruction.""" + def __init__(self, compute_driver=None, *args, **kwargs): """Load configuration options and connect to the hypervisor.""" # TODO(vish): sync driver creation logic with the rest of the system + # and redocument the module docstring if not compute_driver: compute_driver = FLAGS.compute_driver self.driver = utils.import_object(compute_driver) @@ -54,7 +67,7 @@ class ComputeManager(manager.Manager): super(ComputeManager, self).__init__(*args, **kwargs) def _update_state(self, context, instance_id): - """Update the state of an instance from the driver info""" + """Update the state of an instance from the driver info.""" # FIXME(ja): include other fields from state? instance_ref = self.db.instance_get(context, instance_id) try: @@ -67,6 +80,7 @@ class ComputeManager(manager.Manager): @defer.inlineCallbacks @exception.wrap_exception def refresh_security_group(self, context, security_group_id, **_kwargs): + """This call passes stright through to the virtualization driver.""" yield self.driver.refresh_security_group(security_group_id) @defer.inlineCallbacks diff --git a/nova/virt/connection.py b/nova/virt/connection.py index 34e37adf7..ceb7f1e4b 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -17,7 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Abstraction of the underlying virtualization API""" +"""Abstraction of the underlying virtualization API.""" import logging import sys @@ -39,6 +39,15 @@ def get_connection(read_only=False): Any object returned here must conform to the interface documented by FakeConnection. + + Related flags + ------------- + :connection_type: A string literal that falls through a if/elif structure + to determine what virtualization mechanism to use. + Values may be: + * fake + * libvirt + * xenapi """ # TODO(termie): maybe lazy load after initial check for permissions # TODO(termie): check whether we can be disconnected -- cgit From 208da85e85131a9b60a1fadea3e4242fa70dcde2 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 28 Oct 2010 12:25:39 -0400 Subject: Whitespace and docstring cleanups --- nova/auth/fakeldap.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index 1a49b73fe..176a00f06 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -15,12 +15,14 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + """ -Fake LDAP server for test harnesses. +Fake LDAP server for test harness, backs to ReDIS. This class does very little error checking, and knows nothing about ldap -class definitions. It implements the minimum emulation of the python ldap +class definitions. It implements the minimum emulation of the python ldap library to work with nova. + """ import json @@ -77,8 +79,8 @@ def initialize(_uri): def _match_query(query, attrs): """Match an ldap query to an attribute dictionary. - &, |, and ! are supported in the query. No syntax checking is performed, - so malformed querys will not work correctly. + The characters &, |, and ! are supported in the query. No syntax checking + is performed, so malformed querys will not work correctly. """ # cut off the parentheses inner = query[1:-1] -- cgit From 4bd42d5ee9eadb9affb40ee6ed0f98b13609c895 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 28 Oct 2010 12:26:29 -0400 Subject: Another heading was too distracting, use instead. --- nova/compute/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c5102c35a..174fb0aca 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -24,8 +24,8 @@ handles RPC calls relating to creating instances. It is responsible for building a disk image, launching it via the underlying virtualization driver, responding to calls to check it state, attaching persistent as well as termination. -Related Flags -------------- +**Related Flags** + :instances_path: Where instances are kept on disk :compute_driver: Name of class that is used to handle virtualization, loaded by `nova.utils.import_object` -- cgit From a592636054511382105dc81d4a6b2a44df0dad9a Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 28 Oct 2010 17:08:13 -0400 Subject: :func: links to python functions in the documentation. --- nova/compute/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 174fb0aca..3346d1299 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -28,9 +28,9 @@ responding to calls to check it state, attaching persistent as well as terminati :instances_path: Where instances are kept on disk :compute_driver: Name of class that is used to handle virtualization, loaded - by `nova.utils.import_object` + by :func:`nova.utils.import_object` :volume_manager: Name of class that handles persistent storage, loaded by - `nova.utils.import_object` + :func:`nova.utils.import_object` """ import datetime -- cgit From 7cc4bcd344221d517054641171f759b88112a459 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 28 Oct 2010 20:13:40 -0400 Subject: Pep-257 cleanups. --- nova/db/api.py | 128 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 70 insertions(+), 58 deletions(-) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index 0731e2e05..7cce591ad 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -16,7 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. """ -Defines interface for DB access +Defines interface for DB access. """ from nova import exception @@ -34,17 +34,17 @@ IMPL = utils.LazyPluggable(FLAGS['db_backend'], class NoMoreAddresses(exception.Error): - """No more available addresses""" + """No more available addresses.""" pass class NoMoreBlades(exception.Error): - """No more available blades""" + """No more available blades.""" pass class NoMoreNetworks(exception.Error): - """No more available networks""" + """No more available networks.""" pass @@ -62,30 +62,33 @@ def service_get(context, service_id): def service_get_all_by_topic(context, topic): - """Get all compute services for a given topic """ + """Get all compute services for a given topic.""" return IMPL.service_get_all_by_topic(context, topic) def service_get_all_compute_sorted(context): - """Get all compute services sorted by instance count + """Get all compute services sorted by instance count. + + Returns a list of (Service, instance_count) tuples. - Returns a list of (Service, instance_count) tuples """ return IMPL.service_get_all_compute_sorted(context) def service_get_all_network_sorted(context): - """Get all network services sorted by network count + """Get all network services sorted by network count. + + Returns a list of (Service, network_count) tuples. - Returns a list of (Service, network_count) tuples """ return IMPL.service_get_all_network_sorted(context) def service_get_all_volume_sorted(context): - """Get all volume services sorted by volume count + """Get all volume services sorted by volume count. + + Returns a list of (Service, volume_count) tuples. - Returns a list of (Service, volume_count) tuples """ return IMPL.service_get_all_volume_sorted(context) @@ -116,6 +119,7 @@ def floating_ip_allocate_address(context, host, project_id): """Allocate free floating ip and return the address. Raises if one is not available. + """ return IMPL.floating_ip_allocate_address(context, host, project_id) @@ -144,6 +148,7 @@ def floating_ip_disassociate(context, address): """Disassociate an floating ip from a fixed ip by address. Returns the address of the existing fixed ip. + """ return IMPL.floating_ip_disassociate(context, address) @@ -182,6 +187,7 @@ def fixed_ip_associate(context, address, instance_id): """Associate fixed ip to instance. Raises if fixed ip is not available. + """ return IMPL.fixed_ip_associate(context, address, instance_id) @@ -190,6 +196,7 @@ def fixed_ip_associate_pool(context, network_id, instance_id): """Find free ip in network and associate it to instance. Raises if one is not available. + """ return IMPL.fixed_ip_associate_pool(context, network_id, instance_id) @@ -205,7 +212,7 @@ def fixed_ip_disassociate(context, address): def fixed_ip_disassociate_all_by_timeout(context, host, time): - """Disassociate old fixed ips from host""" + """Disassociate old fixed ips from host.""" return IMPL.fixed_ip_disassociate_all_by_timeout(context, host, time) @@ -283,7 +290,7 @@ def instance_get_floating_address(context, instance_id): def instance_get_by_internal_id(context, internal_id): - """Get an instance by ec2 id.""" + """Get an instance by internal id.""" return IMPL.instance_get_by_internal_id(context, internal_id) @@ -307,7 +314,7 @@ def instance_update(context, instance_id, values): def instance_add_security_group(context, instance_id, security_group_id): - """Associate the given security group with the given instance""" + """Associate the given security group with the given instance.""" return IMPL.instance_add_security_group(context, instance_id, security_group_id) @@ -369,10 +376,12 @@ def network_count_reserved_ips(context, network_id): def network_create_safe(context, values): - """Create a network from the values dict + """Create a network from the values dict. The network is only returned if the create succeeds. If the create violates - constraints because the network already exists, no exception is raised.""" + constraints because the network already exists, no exception is raised. + + """ return IMPL.network_create_safe(context, values) @@ -413,22 +422,22 @@ def network_get_by_instance(context, instance_id): def network_get_index(context, network_id): - """Get non-conflicting index for network""" + """Get non-conflicting index for network.""" return IMPL.network_get_index(context, network_id) def network_get_vpn_ip(context, network_id): - """Get non-conflicting index for network""" + """Get non-conflicting index for network.""" return IMPL.network_get_vpn_ip(context, network_id) def network_set_cidr(context, network_id, cidr): - """Set the Classless Inner Domain Routing for the network""" + """Set the Classless Inner Domain Routing for the network.""" return IMPL.network_set_cidr(context, network_id, cidr) def network_set_host(context, network_id, host_id): - """Safely set the host for network""" + """Safely set the host for network.""" return IMPL.network_set_host(context, network_id, host_id) @@ -474,7 +483,9 @@ def export_device_create_safe(context, values): The device is not returned. If the create violates the unique constraints because the shelf_id and blade_id already exist, - no exception is raised.""" + no exception is raised. + + """ return IMPL.export_device_create_safe(context, values) @@ -482,17 +493,17 @@ def export_device_create_safe(context, values): def auth_destroy_token(context, token): - """Destroy an auth token""" + """Destroy an auth token.""" return IMPL.auth_destroy_token(context, token) def auth_get_token(context, token_hash): - """Retrieves a token given the hash representing it""" + """Retrieves a token given the hash representing it.""" return IMPL.auth_get_token(context, token_hash) def auth_create_token(context, token): - """Creates a new token""" + """Creates a new token.""" return IMPL.auth_create_token(context, token) @@ -595,47 +606,47 @@ def volume_update(context, volume_id, values): def security_group_get_all(context): - """Get all security groups""" + """Get all security groups.""" return IMPL.security_group_get_all(context) def security_group_get(context, security_group_id): - """Get security group by its internal id""" + """Get security group by its internal id.""" return IMPL.security_group_get(context, security_group_id) def security_group_get_by_name(context, project_id, group_name): - """Returns a security group with the specified name from a project""" + """Returns a security group with the specified name from a project.""" return IMPL.security_group_get_by_name(context, project_id, group_name) def security_group_get_by_project(context, project_id): - """Get all security groups belonging to a project""" + """Get all security groups belonging to a project.""" return IMPL.security_group_get_by_project(context, project_id) def security_group_get_by_instance(context, instance_id): - """Get security groups to which the instance is assigned""" + """Get security groups to which the instance is assigned.""" return IMPL.security_group_get_by_instance(context, instance_id) def security_group_exists(context, project_id, group_name): - """Indicates if a group name exists in a project""" + """Indicates if a group name exists in a project.""" return IMPL.security_group_exists(context, project_id, group_name) def security_group_create(context, values): - """Create a new security group""" + """Create a new security group.""" return IMPL.security_group_create(context, values) def security_group_destroy(context, security_group_id): - """Deletes a security group""" + """Deletes a security group.""" return IMPL.security_group_destroy(context, security_group_id) def security_group_destroy_all(context): - """Deletes a security group""" + """Deletes a security group.""" return IMPL.security_group_destroy_all(context) @@ -643,18 +654,18 @@ def security_group_destroy_all(context): def security_group_rule_create(context, values): - """Create a new security group""" + """Create a new security group.""" return IMPL.security_group_rule_create(context, values) def security_group_rule_get_by_security_group(context, security_group_id): - """Get all rules for a a given security group""" + """Get all rules for a a given security group.""" return IMPL.security_group_rule_get_by_security_group(context, security_group_id) def security_group_rule_destroy(context, security_group_rule_id): - """Deletes a security group rule""" + """Deletes a security group rule.""" return IMPL.security_group_rule_destroy(context, security_group_rule_id) @@ -662,107 +673,107 @@ def security_group_rule_destroy(context, security_group_rule_id): def user_get(context, id): - """Get user by id""" + """Get user by id.""" return IMPL.user_get(context, id) def user_get_by_uid(context, uid): - """Get user by uid""" + """Get user by uid.""" return IMPL.user_get_by_uid(context, uid) def user_get_by_access_key(context, access_key): - """Get user by access key""" + """Get user by access key.""" return IMPL.user_get_by_access_key(context, access_key) def user_create(context, values): - """Create a new user""" + """Create a new user.""" return IMPL.user_create(context, values) def user_delete(context, id): - """Delete a user""" + """Delete a user.""" return IMPL.user_delete(context, id) def user_get_all(context): - """Create a new user""" + """Create a new user.""" return IMPL.user_get_all(context) def user_add_role(context, user_id, role): - """Add another global role for user""" + """Add another global role for user.""" return IMPL.user_add_role(context, user_id, role) def user_remove_role(context, user_id, role): - """Remove global role from user""" + """Remove global role from user.""" return IMPL.user_remove_role(context, user_id, role) def user_get_roles(context, user_id): - """Get global roles for user""" + """Get global roles for user.""" return IMPL.user_get_roles(context, user_id) def user_add_project_role(context, user_id, project_id, role): - """Add project role for user""" + """Add project role for user.""" return IMPL.user_add_project_role(context, user_id, project_id, role) def user_remove_project_role(context, user_id, project_id, role): - """Remove project role from user""" + """Remove project role from user.""" return IMPL.user_remove_project_role(context, user_id, project_id, role) def user_get_roles_for_project(context, user_id, project_id): - """Return list of roles a user holds on project""" + """Return list of roles a user holds on project.""" return IMPL.user_get_roles_for_project(context, user_id, project_id) def user_update(context, user_id, values): - """Update user""" + """Update user.""" return IMPL.user_update(context, user_id, values) def project_get(context, id): - """Get project by id""" + """Get project by id.""" return IMPL.project_get(context, id) def project_create(context, values): - """Create a new project""" + """Create a new project.""" return IMPL.project_create(context, values) def project_add_member(context, project_id, user_id): - """Add user to project""" + """Add user to project.""" return IMPL.project_add_member(context, project_id, user_id) def project_get_all(context): - """Get all projects""" + """Get all projects.""" return IMPL.project_get_all(context) def project_get_by_user(context, user_id): - """Get all projects of which the given user is a member""" + """Get all projects of which the given user is a member.""" return IMPL.project_get_by_user(context, user_id) def project_remove_member(context, project_id, user_id): - """Remove the given user from the given project""" + """Remove the given user from the given project.""" return IMPL.project_remove_member(context, project_id, user_id) def project_update(context, project_id, values): - """Update Remove the given user from the given project""" + """Update Remove the given user from the given project.""" return IMPL.project_update(context, project_id, values) def project_delete(context, project_id): - """Delete project""" + """Delete project.""" return IMPL.project_delete(context, project_id) @@ -771,6 +782,7 @@ def project_delete(context, project_id): def host_get_networks(context, host): """Return all networks for which the given host is the designated - network host + network host. + """ return IMPL.host_get_networks(context, host) -- cgit From 2132c0de46fd3f1b938e4b3b01b73fb2efaf6a38 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 28 Oct 2010 20:28:13 -0400 Subject: Pep-257 --- nova/db/sqlalchemy/models.py | 47 +++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 22 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 2a3cfa94c..894ebcddd 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -15,7 +15,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - """ SQLAlchemy models for nova data """ @@ -35,13 +34,13 @@ from nova import auth from nova import exception from nova import flags -FLAGS = flags.FLAGS +FLAGS = flags.FLAGS BASE = declarative_base() class NovaBase(object): - """Base class for Nova Models""" + """Base class for Nova Models.""" __table_args__ = {'mysql_engine': 'InnoDB'} __table_initialized__ = False created_at = Column(DateTime, default=datetime.datetime.utcnow) @@ -50,7 +49,7 @@ class NovaBase(object): deleted = Column(Boolean, default=False) def save(self, session=None): - """Save this object""" + """Save this object.""" if not session: session = get_session() session.add(self) @@ -63,7 +62,7 @@ class NovaBase(object): raise def delete(self, session=None): - """Delete this object""" + """Delete this object.""" self.deleted = True self.deleted_at = datetime.datetime.utcnow() self.save(session=session) @@ -128,7 +127,8 @@ class NovaBase(object): class Service(BASE, NovaBase): - """Represents a running service on a host""" + """Represents a running service on a host.""" + __tablename__ = 'services' id = Column(Integer, primary_key=True) host = Column(String(255)) # , ForeignKey('hosts.id')) @@ -139,7 +139,7 @@ class Service(BASE, NovaBase): class Instance(BASE, NovaBase): - """Represents a guest vm""" + """Represents a guest vm.""" __tablename__ = 'instances' id = Column(Integer, primary_key=True) internal_id = Column(Integer, unique=True) @@ -215,7 +215,7 @@ class Instance(BASE, NovaBase): class Volume(BASE, NovaBase): - """Represents a block storage device that can be attached to a vm""" + """Represents a block storage device that can be attached to a vm.""" __tablename__ = 'volumes' id = Column(Integer, primary_key=True) ec2_id = Column(String(12), unique=True) @@ -246,7 +246,7 @@ class Volume(BASE, NovaBase): class Quota(BASE, NovaBase): - """Represents quota overrides for a project""" + """Represents quota overrides for a project.""" __tablename__ = 'quotas' id = Column(Integer, primary_key=True) @@ -260,7 +260,7 @@ class Quota(BASE, NovaBase): class ExportDevice(BASE, NovaBase): - """Represates a shelf and blade that a volume can be exported on""" + """Represates a shelf and blade that a volume can be exported on.""" __tablename__ = 'export_devices' __table_args__ = (schema.UniqueConstraint("shelf_id", "blade_id"), {'mysql_engine': 'InnoDB'}) @@ -283,7 +283,7 @@ class SecurityGroupInstanceAssociation(BASE, NovaBase): class SecurityGroup(BASE, NovaBase): - """Represents a security group""" + """Represents a security group.""" __tablename__ = 'security_groups' id = Column(Integer, primary_key=True) @@ -313,7 +313,7 @@ class SecurityGroup(BASE, NovaBase): class SecurityGroupIngressRule(BASE, NovaBase): - """Represents a rule in a security group""" + """Represents a rule in a security group.""" __tablename__ = 'security_group_rules' id = Column(Integer, primary_key=True) @@ -335,7 +335,7 @@ class SecurityGroupIngressRule(BASE, NovaBase): class KeyPair(BASE, NovaBase): - """Represents a public key pair for ssh""" + """Represents a public key pair for ssh.""" __tablename__ = 'key_pairs' id = Column(Integer, primary_key=True) @@ -348,7 +348,7 @@ class KeyPair(BASE, NovaBase): class Network(BASE, NovaBase): - """Represents a network""" + """Represents a network.""" __tablename__ = 'networks' __table_args__ = (schema.UniqueConstraint("vpn_public_address", "vpn_public_port"), @@ -377,9 +377,12 @@ class Network(BASE, NovaBase): class AuthToken(BASE, NovaBase): - """Represents an authorization token for all API transactions. Fields - are a string representing the actual token and a user id for mapping - to the actual user""" + """Represents an authorization token for all API transactions. + + Fields are a string representing the actual token and a user id for + mapping to the actual user + + """ __tablename__ = 'auth_tokens' token_hash = Column(String(255), primary_key=True) user_id = Column(Integer) @@ -390,7 +393,7 @@ class AuthToken(BASE, NovaBase): # TODO(vish): can these both come from the same baseclass? class FixedIp(BASE, NovaBase): - """Represents a fixed ip for an instance""" + """Represents a fixed ip for an instance.""" __tablename__ = 'fixed_ips' id = Column(Integer, primary_key=True) address = Column(String(255)) @@ -409,7 +412,7 @@ class FixedIp(BASE, NovaBase): class User(BASE, NovaBase): - """Represents a user""" + """Represents a user.""" __tablename__ = 'users' id = Column(String(255), primary_key=True) @@ -421,7 +424,7 @@ class User(BASE, NovaBase): class Project(BASE, NovaBase): - """Represents a project""" + """Represents a project.""" __tablename__ = 'projects' id = Column(String(255), primary_key=True) name = Column(String(255)) @@ -469,7 +472,7 @@ class UserProjectAssociation(BASE, NovaBase): class FloatingIp(BASE, NovaBase): - """Represents a floating ip that dynamically forwards to a fixed ip""" + """Represents a floating ip that dynamically forwards to a fixed ip.""" __tablename__ = 'floating_ips' id = Column(Integer, primary_key=True) address = Column(String(255)) @@ -485,7 +488,7 @@ class FloatingIp(BASE, NovaBase): def register_models(): - """Register Models and create metadata""" + """Register Models and create metadata.""" from sqlalchemy import create_engine models = (Service, Instance, Volume, ExportDevice, FixedIp, FloatingIp, Network, SecurityGroup, -- cgit From 133cd9973e17458bea3594490e70ccd3c524cf12 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 29 Oct 2010 11:58:57 -0400 Subject: Document Fakes --- nova/auth/fakeldap.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index 176a00f06..46e0135b4 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -15,9 +15,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - -""" -Fake LDAP server for test harness, backs to ReDIS. +"""Fake LDAP server for test harness, backs to ReDIS. This class does very little error checking, and knows nothing about ldap class definitions. It implements the minimum emulation of the python ldap -- cgit From 3ec095bed60490c844067c8d58ed43dbedee5f0a Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 29 Oct 2010 12:35:46 -0400 Subject: Update database page a bit. --- nova/db/sqlalchemy/api.py | 2 +- nova/db/sqlalchemy/models.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index a3d8dde2f..d9b98655e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -16,7 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. """ -Implementation of SQLAlchemy backend +Implementation of SQLAlchemy backend. """ import random diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 894ebcddd..29c3b74da 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -16,7 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. """ -SQLAlchemy models for nova data +SQLAlchemy models for nova data. """ import datetime @@ -488,7 +488,11 @@ class FloatingIp(BASE, NovaBase): def register_models(): - """Register Models and create metadata.""" + """Register Models and create metadata. + + Called from nova.db.sqlalchemy.__init__ as part of loading the driver, + it will never need to be called explicitly elsewhere. + """ from sqlalchemy import create_engine models = (Service, Instance, Volume, ExportDevice, FixedIp, FloatingIp, Network, SecurityGroup, -- cgit From 489ddea1668c742f62acd6fd3e9af78f2f782912 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 29 Oct 2010 15:30:39 -0400 Subject: Update database docs. --- nova/db/api.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index 7cce591ad..659bfd6b8 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -17,6 +17,16 @@ # under the License. """ Defines interface for DB access. + +The underlying driver is loaded as a :class:`LazyPluggable`. + +**Related Flags** + +:db_backend: string to lookup in the list of LazyPluggable backends. + `sqlalchemy` is the only supported backend right now. + +:sql_connection: string specifying the sqlalchemy connection to use, like: + `sqlite:///var/lib/nova/nova.sqlite`. """ from nova import exception -- cgit From 7ca505a729ee3caae968cf25059721ab51d1327c Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 29 Oct 2010 16:18:00 -0400 Subject: Volume documentation. --- nova/tests/volume_unittest.py | 21 ++++++++++++--------- nova/volume/driver.py | 24 +++++++++++++----------- nova/volume/manager.py | 39 +++++++++++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 28 deletions(-) (limited to 'nova') diff --git a/nova/tests/volume_unittest.py b/nova/tests/volume_unittest.py index fdee30b48..896800cea 100644 --- a/nova/tests/volume_unittest.py +++ b/nova/tests/volume_unittest.py @@ -16,7 +16,8 @@ # License for the specific language governing permissions and limitations # under the License. """ -Tests for Volume Code +Tests for Volume Code. + """ import logging @@ -33,7 +34,8 @@ FLAGS = flags.FLAGS class VolumeTestCase(test.TrialTestCase): - """Test Case for volumes""" + """Test Case for volumes.""" + def setUp(self): logging.getLogger().setLevel(logging.DEBUG) super(VolumeTestCase, self).setUp() @@ -44,7 +46,7 @@ class VolumeTestCase(test.TrialTestCase): @staticmethod def _create_volume(size='0'): - """Create a volume object""" + """Create a volume object.""" vol = {} vol['size'] = size vol['user_id'] = 'fake' @@ -56,7 +58,7 @@ class VolumeTestCase(test.TrialTestCase): @defer.inlineCallbacks def test_create_delete_volume(self): - """Test volume can be created and deleted""" + """Test volume can be created and deleted.""" volume_id = self._create_volume() yield self.volume.create_volume(self.context, volume_id) self.assertEqual(volume_id, db.volume_get(context.get_admin_context(), @@ -70,7 +72,7 @@ class VolumeTestCase(test.TrialTestCase): @defer.inlineCallbacks def test_too_big_volume(self): - """Ensure failure if a too large of a volume is requested""" + """Ensure failure if a too large of a volume is requested.""" # FIXME(vish): validation needs to move into the data layer in # volume_create defer.returnValue(True) @@ -83,7 +85,7 @@ class VolumeTestCase(test.TrialTestCase): @defer.inlineCallbacks def test_too_many_volumes(self): - """Ensure that NoMoreBlades is raised when we run out of volumes""" + """Ensure that NoMoreBlades is raised when we run out of volumes.""" vols = [] total_slots = FLAGS.num_shelves * FLAGS.blades_per_shelf for _index in xrange(total_slots): @@ -100,7 +102,7 @@ class VolumeTestCase(test.TrialTestCase): @defer.inlineCallbacks def test_run_attach_detach_volume(self): - """Make sure volume can be attached and detached from instance""" + """Make sure volume can be attached and detached from instance.""" inst = {} inst['image_id'] = 'ami-test' inst['reservation_id'] = 'r-fakeres' @@ -149,12 +151,13 @@ class VolumeTestCase(test.TrialTestCase): @defer.inlineCallbacks def test_concurrent_volumes_get_different_blades(self): - """Ensure multiple concurrent volumes get different blades""" + """Ensure multiple concurrent volumes get different blades.""" + volume_ids = [] shelf_blades = [] def _check(volume_id): - """Make sure blades aren't duplicated""" + """Make sure blades aren't duplicated.""" volume_ids.append(volume_id) admin_context = context.get_admin_context() (shelf_id, blade_id) = db.volume_get_shelf_and_blade(admin_context, diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 3fa29ba37..b99089374 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -15,9 +15,9 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - """ -Drivers for volumes +Drivers for volumes. + """ import logging @@ -39,7 +39,8 @@ flags.DEFINE_string('num_shell_tries', 3, class AOEDriver(object): - """Executes commands relating to AOE volumes""" + """Executes commands relating to AOE volumes.""" + def __init__(self, execute=process.simple_execute, *args, **kwargs): self._execute = execute @@ -63,7 +64,7 @@ class AOEDriver(object): @defer.inlineCallbacks def create_volume(self, volume_name, size): - """Creates a logical volume""" + """Creates a logical volume.""" # NOTE(vish): makes sure that the volume group exists yield self._execute("vgs %s" % FLAGS.volume_group) if int(size) == 0: @@ -77,14 +78,14 @@ class AOEDriver(object): @defer.inlineCallbacks def delete_volume(self, volume_name): - """Deletes a logical volume""" + """Deletes a logical volume.""" yield self._try_execute("sudo lvremove -f %s/%s" % (FLAGS.volume_group, volume_name)) @defer.inlineCallbacks def create_export(self, volume_name, shelf_id, blade_id): - """Creates an export for a logical volume""" + """Creates an export for a logical volume.""" yield self._try_execute( "sudo vblade-persist setup %s %s %s /dev/%s/%s" % (shelf_id, @@ -95,13 +96,13 @@ class AOEDriver(object): @defer.inlineCallbacks def discover_volume(self, _volume_name): - """Discover volume on a remote host""" + """Discover volume on a remote host.""" yield self._execute("sudo aoe-discover") yield self._execute("sudo aoe-stat") @defer.inlineCallbacks def remove_export(self, _volume_name, shelf_id, blade_id): - """Removes an export for a logical volume""" + """Removes an export for a logical volume.""" yield self._try_execute("sudo vblade-persist stop %s %s" % (shelf_id, blade_id)) yield self._try_execute("sudo vblade-persist destroy %s %s" % @@ -109,7 +110,7 @@ class AOEDriver(object): @defer.inlineCallbacks def ensure_exports(self): - """Runs all existing exports""" + """Runs all existing exports.""" # NOTE(vish): The standard _try_execute does not work here # because these methods throw errors if other # volumes on this host are in the process of @@ -125,11 +126,12 @@ class AOEDriver(object): class FakeAOEDriver(AOEDriver): - """Logs calls instead of executing""" + """Logs calls instead of executing.""" + def __init__(self, *args, **kwargs): super(FakeAOEDriver, self).__init__(self.fake_execute) @staticmethod def fake_execute(cmd, *_args, **_kwargs): - """Execute that simply logs the command""" + """Execute that simply logs the command.""" logging.debug("FAKE AOE: %s", cmd) diff --git a/nova/volume/manager.py b/nova/volume/manager.py index 2874459f9..f6e220c5f 100644 --- a/nova/volume/manager.py +++ b/nova/volume/manager.py @@ -15,10 +15,31 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - """ -Volume manager manages creating, attaching, detaching, and -destroying persistent storage volumes, ala EBS. +Volume manager manages creating, attaching, detaching, and persistent storage. + +Persistant storage volumes keep their state independent of instances. You can +attach to an instance, terminate the instance, spawn a new instance (even +one from a different image) and re-attach the volume with the same data +intact. + +**Related Flags** + +:volume_topic: What :mod:`rpc` topic to listen to (default: `volume`). +:volume_manager: The module name of a class derived from + :class:`manager.Manager` (default: + :class:`nova.volume.manager.AOEManager`). +:storage_availability_zone: Defaults to `nova`. +:volume_driver: Used by :class:`AOEManager`. Defaults to + :class:`nova.volume.driver.AOEDriver`. +:num_shelves: Number of shelves for AoE (default: 100). +:num_blades: Number of vblades per shelf to allocate AoE storage from + (default: 16). +:volume_group: Name of the group that will contain exported volumes (default: + `nova-volumes`) +:aoe_eth_dev: Device name the volumes will be exported on (default: `eth0`). +:num_shell_tries: Number of times to attempt to run AoE commands (default: 3) + """ import logging @@ -47,15 +68,17 @@ flags.DEFINE_integer('blades_per_shelf', class AOEManager(manager.Manager): - """Manages Ata-Over_Ethernet volumes""" + """Manages Ata-Over_Ethernet volumes.""" + def __init__(self, volume_driver=None, *args, **kwargs): + """Load the driver from the one specified in args, or from flags.""" if not volume_driver: volume_driver = FLAGS.volume_driver self.driver = utils.import_object(volume_driver) super(AOEManager, self).__init__(*args, **kwargs) def _ensure_blades(self, context): - """Ensure that blades have been created in datastore""" + """Ensure that blades have been created in datastore.""" total_blades = FLAGS.num_shelves * FLAGS.blades_per_shelf if self.db.export_device_count(context) >= total_blades: return @@ -66,7 +89,7 @@ class AOEManager(manager.Manager): @defer.inlineCallbacks def create_volume(self, context, volume_id): - """Creates and exports the volume""" + """Creates and exports the volume.""" context = context.elevated() logging.info("volume %s: creating", volume_id) @@ -104,7 +127,7 @@ class AOEManager(manager.Manager): @defer.inlineCallbacks def delete_volume(self, context, volume_id): - """Deletes and unexports volume""" + """Deletes and unexports volume.""" context = context.elevated() volume_ref = self.db.volume_get(context, volume_id) if volume_ref['attach_status'] == "attached": @@ -123,7 +146,7 @@ class AOEManager(manager.Manager): @defer.inlineCallbacks def setup_compute_volume(self, context, volume_id): - """Setup remote volume on compute host + """Setup remote volume on compute host. Returns path to device. """ -- cgit From bf15a6eb3de8c688dc1364959dd3e00d3e26a563 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 30 Oct 2010 20:05:31 -0400 Subject: Update compute/disk.py docs. --- nova/compute/disk.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/compute/disk.py b/nova/compute/disk.py index e362b4507..0b8568d33 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -15,10 +15,11 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - """ Utility methods to resize, repartition, and modify disk images. + Includes injection of SSH PGP keys into authorized_keys file. + """ import logging @@ -41,20 +42,23 @@ flags.DEFINE_integer('block_size', 1024 * 1024 * 256, @defer.inlineCallbacks def partition(infile, outfile, local_bytes=0, resize=True, local_type='ext2', execute=None): - """Takes a single partition represented by infile and writes a bootable - drive image into outfile. + """ + Turns a partition (infile) into a bootable drive image (outfile). The first 63 sectors (0-62) of the resulting image is a master boot record. Infile becomes the first primary partition. If local bytes is specified, a second primary partition is created and formatted as ext2. - In the diagram below, dashes represent drive sectors. - +-----+------. . .-------+------. . .------+ - | 0 a| b c|d e| - +-----+------. . .-------+------. . .------+ - | mbr | primary partiton | local partition | - +-----+------. . .-------+------. . .------+ + :: + + In the diagram below, dashes represent drive sectors. + +-----+------. . .-------+------. . .------+ + | 0 a| b c|d e| + +-----+------. . .-------+------. . .------+ + | mbr | primary partiton | local partition | + +-----+------. . .-------+------. . .------+ + """ sector_size = 512 file_size = os.path.getsize(infile) -- cgit From 878eb4d25075f8d78f24ad9f78eb5d43702192ca Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 1 Nov 2010 16:13:18 -0400 Subject: Virt documentation. --- nova/virt/connection.py | 20 ++++++++++++-------- nova/virt/fake.py | 7 +++++-- nova/virt/libvirt_conn.py | 22 +++++++++++++++++++++- nova/virt/xenapi.py | 12 ++++++++++++ 4 files changed, 50 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/virt/connection.py b/nova/virt/connection.py index ceb7f1e4b..11f0fa8ce 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -32,19 +32,23 @@ FLAGS = flags.FLAGS def get_connection(read_only=False): - """Returns an object representing the connection to a virtualization - platform. This could be nova.virt.fake.FakeConnection in test mode, - a connection to KVM or QEMU via libvirt, or a connection to XenServer - or Xen Cloud Platform via XenAPI. + """ + Returns an object representing the connection to a virtualization + platform. + + This could be :mod:`nova.virt.fake.FakeConnection` in test mode, + a connection to KVM, QEMU, or UML via :mod:`libvirt_conn`, or a connection + to XenServer or Xen Cloud Platform via :mod:`xenapi`. Any object returned here must conform to the interface documented by - FakeConnection. + :mod:`FakeConnection`. + + **Related flags** - Related flags - ------------- :connection_type: A string literal that falls through a if/elif structure to determine what virtualization mechanism to use. - Values may be: + Values may be + * fake * libvirt * xenapi diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 66eff4c66..f855523d3 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -18,8 +18,11 @@ # under the License. """ -A fake (in-memory) hypervisor+api. Allows nova testing w/o a hypervisor. -This module also documents the semantics of real hypervisor connections. +A fake (in-memory) hypervisor+api. + +Allows nova testing w/o a hypervisor. This module also documents the +semantics of real hypervisor connections. + """ from twisted.internet import defer diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index e32945fa5..0170fc6d1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -18,7 +18,27 @@ # under the License. """ -A connection to a hypervisor (e.g. KVM) through libvirt. +A connection to a hypervisor through libvirt. + +Supports KVM, QEMU, UML, and XEN. + +**Related Flags** + +:libvirt_type: Libvirt domain type. Can be kvm, qemu, uml, xen + (default: kvm). +:libvirt_uri: Override for the default libvirt URI (depends on libvirt_type). +:libvirt_xml_template: Libvirt XML Template (QEmu/KVM). +:libvirt_xen_xml_template: Libvirt XML Template (Xen). +:libvirt_uml_xml_template: Libvirt XML Template (User Mode Linux). +:libvirt_rescue_xml_template: XML template for rescue mode (KVM & QEMU). +:libvirt_rescue_xen_xml_template: XML templage for rescue mode (XEN). +:libvirt_rescue_uml_xml_template: XML template for rescue mode (UML). +:rescue_image_id: Rescue ami image (default: ami-rescue). +:rescue_kernel_id: Rescue aki image (default: aki-rescue). +:rescue_ramdisk_id: Rescue ari image (default: ari-rescue). +:injected_network_template: Template file for injected network +:allow_project_net_traffic: Whether to allow in project network traffic + """ import logging diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index a17e405ab..0f563aa41 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -33,6 +33,18 @@ long-running operations. FIXME: get_info currently doesn't conform to these rules, and will block the reactor thread if the VM.get_by_name_label or VM.get_record calls block. + +**Related Flags** + +:xenapi_connection_url: URL for connection to XenServer/Xen Cloud Platform. +:xenapi_connection_username: Username for connection to XenServer/Xen Cloud + Platform (default: root). +:xenapi_connection_password: Password for connection to XenServer/Xen Cloud + Platform. +:xenapi_task_poll_interval: The interval (seconds) used for polling of + remote tasks (Async.VM.start, etc) + (default: 0.5). + """ import logging -- cgit From c8e2341c98ffacfafffbadb7d204f10ff87cf89c Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 1 Nov 2010 20:33:03 -0400 Subject: API endpoint documentation. --- nova/api/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 8a1d9fe32..27b8199db 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -15,9 +15,15 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - """ Root WSGI middleware for all API controllers. + +**Related Flags** + +:osapi_subdomain: subdomain running the OpenStack API (default: api) +:ec2api_subdomain: subdomain running the EC2 API (default: ec2) +:FAKE_subdomain: set to 'api' or 'ec2', requests default to that endpoint + """ import routes -- cgit From e0f889443f5c0732db28871f350c45e7c8e8d031 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 1 Nov 2010 21:47:16 -0400 Subject: Add ec2 api docs. --- nova/api/ec2/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 0df4d3710..816314901 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -15,8 +15,10 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +""" +Starting point for routing EC2 requests. -"""Starting point for routing EC2 requests""" +""" import logging import routes -- cgit From e493e324eb9a9fe31e72551b34bab768b507bc1d Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 2 Nov 2010 18:05:47 -0400 Subject: Document final undocumented python modules. --- nova/image/service.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/image/service.py b/nova/image/service.py index 37cadddcc..52ddd4e0f 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -45,13 +45,9 @@ class BaseImageService(object): Returns a sequence of mappings of id and name information about images. - :retval a sequence of mappings with the following signature: - - [ - {'id': opaque id of image, - 'name': name of image - }, ... - ] + :rtype: array + :retval: a sequence of mappings with the following signature + {'id': opaque id of image, 'name': name of image} """ raise NotImplementedError @@ -60,19 +56,17 @@ class BaseImageService(object): """ Returns a sequence of mappings of detailed information about images. - :retval a sequence of mappings with the following signature: - - [ - {'id': opaque id of image, - 'name': name of image, - 'created_at': creation timestamp, - 'updated_at': modification timestamp, - 'deleted_at': deletion timestamp or None, - 'deleted': boolean indicating if image has been deleted, - 'status': string description of image status, - 'is_public': boolean indicating if image is public - }, ... - ] + :rtype: array + :retval: a sequence of mappings with the following signature + {'id': opaque id of image, + 'name': name of image, + 'created_at': creation timestamp, + 'updated_at': modification timestamp, + 'deleted_at': deletion timestamp or None, + 'deleted': boolean indicating if image has been deleted, + 'status': string description of image status, + 'is_public': boolean indicating if image is public + } If the service does not implement a method that provides a detailed set of information about images, then the method should raise -- cgit From 583d1b1c4d039f1f9751c8a2cc0cf59bb77551e0 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 2 Nov 2010 20:31:17 -0400 Subject: Fixes after trunk merge. --- nova/tests/fake_flags.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py index 4bbef8832..bc377f0da 100644 --- a/nova/tests/fake_flags.py +++ b/nova/tests/fake_flags.py @@ -24,6 +24,7 @@ flags.DECLARE('volume_driver', 'nova.volume.manager') FLAGS.volume_driver = 'nova.volume.driver.FakeAOEDriver' FLAGS.connection_type = 'fake' FLAGS.fake_rabbit = True +flags.DECLARE('auth_driver', 'nova.auth.manager') FLAGS.auth_driver = 'nova.auth.dbdriver.DbDriver' flags.DECLARE('network_size', 'nova.network.manager') flags.DECLARE('num_networks', 'nova.network.manager') -- cgit From 2cbef8ffd80546f1dcd850322621b04395591d69 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 3 Nov 2010 14:30:13 -0400 Subject: Doc cleanups. --- nova/network/manager.py | 116 +++++++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 46 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 8a20cb491..b033bb0a4 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -17,7 +17,30 @@ # under the License. """ -Network Hosts are responsible for allocating ips and setting up network +Network Hosts are responsible for allocating ips and setting up network. + +There are multiple backend drivers that handle specific types of networking +topologies. All of the network commands are issued to a subclass of +:class:`NetworkManager`. + +**Related Flags** + +:network_driver: Driver to use for network creation +:flat_network_bridge: Bridge device for simple network instances +:flat_network_dns: Dns for simple network +:flat_network_dhcp_start: Dhcp start for FlatDhcp +:vlan_start: First VLAN for private networks +:vpn_ip: Public IP for the cloudpipe VPN servers +:vpn_start: First Vpn port for private networks +:cnt_vpn_clients: Number of addresses reserved for vpn clients +:network_size: Number of addresses in each private subnet +:floating_range: Floating IP address block +:fixed_range: Fixed IP address block +:date_dhcp_on_disassociate: Whether to update dhcp when fixed_ip + is disassociated +:fixed_ip_disassociate_timeout: Seconds after which a deallocated ip + is disassociated + """ import datetime @@ -63,15 +86,16 @@ flags.DEFINE_integer('fixed_ip_disassociate_timeout', 600, class AddressAlreadyAllocated(exception.Error): - """Address was already allocated""" + """Address was already allocated.""" pass class NetworkManager(manager.Manager): - """Implements common network manager functionality + """Implements common network manager functionality. - This class must be subclassed. + This class must be subclassed to support specific topologies. """ + def __init__(self, network_driver=None, *args, **kwargs): if not network_driver: network_driver = FLAGS.network_driver @@ -86,7 +110,7 @@ class NetworkManager(manager.Manager): self._on_set_network_host(ctxt, network['id']) def set_network_host(self, context, network_id): - """Safely sets the host of the network""" + """Safely sets the host of the network.""" logging.debug("setting network host") host = self.db.network_set_host(context, network_id, @@ -95,34 +119,34 @@ class NetworkManager(manager.Manager): return host def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): - """Gets a fixed ip from the pool""" + """Gets a fixed ip from the pool.""" raise NotImplementedError() def deallocate_fixed_ip(self, context, address, *args, **kwargs): - """Returns a fixed ip to the pool""" + """Returns a fixed ip to the pool.""" raise NotImplementedError() def setup_fixed_ip(self, context, address): - """Sets up rules for fixed ip""" + """Sets up rules for fixed ip.""" raise NotImplementedError() def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a network""" + """Called when this host becomes the host for a network.""" raise NotImplementedError() def setup_compute_network(self, context, instance_id): - """Sets up matching network for compute hosts""" + """Sets up matching network for compute hosts.""" raise NotImplementedError() def allocate_floating_ip(self, context, project_id): - """Gets an floating ip from the pool""" + """Gets an floating ip from the pool.""" # TODO(vish): add floating ips through manage command return self.db.floating_ip_allocate_address(context, self.host, project_id) def associate_floating_ip(self, context, floating_address, fixed_address): - """Associates an floating ip to a fixed ip""" + """Associates an floating ip to a fixed ip.""" self.db.floating_ip_fixed_ip_associate(context, floating_address, fixed_address) @@ -130,18 +154,18 @@ class NetworkManager(manager.Manager): self.driver.ensure_floating_forward(floating_address, fixed_address) def disassociate_floating_ip(self, context, floating_address): - """Disassociates a floating ip""" + """Disassociates a floating ip.""" fixed_address = self.db.floating_ip_disassociate(context, floating_address) self.driver.unbind_floating_ip(floating_address) self.driver.remove_floating_forward(floating_address, fixed_address) def deallocate_floating_ip(self, context, floating_address): - """Returns an floating ip to the pool""" + """Returns an floating ip to the pool.""" self.db.floating_ip_deallocate(context, floating_address) def lease_fixed_ip(self, context, mac, address): - """Called by dhcp-bridge when ip is leased""" + """Called by dhcp-bridge when ip is leased.""" logging.debug("Leasing IP %s", address) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] @@ -158,7 +182,7 @@ class NetworkManager(manager.Manager): logging.warn("IP %s leased that was already deallocated", address) def release_fixed_ip(self, context, mac, address): - """Called by dhcp-bridge when ip is released""" + """Called by dhcp-bridge when ip is released.""" logging.debug("Releasing IP %s", address) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] @@ -183,26 +207,26 @@ class NetworkManager(manager.Manager): self.driver.update_dhcp(context, network_ref['id']) def get_network(self, context): - """Get the network for the current context""" + """Get the network for the current context.""" raise NotImplementedError() def create_networks(self, context, num_networks, network_size, *args, **kwargs): - """Create networks based on parameters""" + """Create networks based on parameters.""" raise NotImplementedError() @property def _bottom_reserved_ips(self): # pylint: disable-msg=R0201 - """Number of reserved ips at the bottom of the range""" + """Number of reserved ips at the bottom of the range.""" return 2 # network, gateway @property def _top_reserved_ips(self): # pylint: disable-msg=R0201 - """Number of reserved ips at the top of the range""" + """Number of reserved ips at the top of the range.""" return 1 # broadcast def _create_fixed_ips(self, context, network_id): - """Create all fixed ips for network""" + """Create all fixed ips for network.""" network_ref = self.db.network_get(context, network_id) # NOTE(vish): Should these be properties of the network as opposed # to properties of the manager class? @@ -222,10 +246,10 @@ class NetworkManager(manager.Manager): class FlatManager(NetworkManager): - """Basic network where no vlans are used""" + """Basic network where no vlans are used.""" def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): - """Gets a fixed ip from the pool""" + """Gets a fixed ip from the pool.""" # TODO(vish): when this is called by compute, we can associate compute # with a network, or a cluster of computes with a network # and use that network here with a method like @@ -239,21 +263,21 @@ class FlatManager(NetworkManager): return address def deallocate_fixed_ip(self, context, address, *args, **kwargs): - """Returns a fixed ip to the pool""" + """Returns a fixed ip to the pool.""" self.db.fixed_ip_update(context, address, {'allocated': False}) self.db.fixed_ip_disassociate(context.elevated(), address) def setup_compute_network(self, context, instance_id): - """Network is created manually""" + """Network is created manually.""" pass def setup_fixed_ip(self, context, address): - """Currently no setup""" + """Currently no setup.""" pass def create_networks(self, context, cidr, num_networks, network_size, *args, **kwargs): - """Create networks based on parameters""" + """Create networks based on parameters.""" fixed_net = IPy.IP(cidr) for index in range(num_networks): start = index * network_size @@ -271,7 +295,7 @@ class FlatManager(NetworkManager): self._create_fixed_ips(context, network_ref['id']) def get_network(self, context): - """Get the network for the current context""" + """Get the network for the current context.""" # NOTE(vish): To support mutilple network hosts, This could randomly # select from multiple networks instead of just # returning the one. It could also potentially be done @@ -280,7 +304,7 @@ class FlatManager(NetworkManager): FLAGS.flat_network_bridge) def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a network""" + """Called when this host becomes the host for a network.""" net = {} net['injected'] = True net['bridge'] = FLAGS.flat_network_bridge @@ -289,19 +313,19 @@ class FlatManager(NetworkManager): class FlatDHCPManager(NetworkManager): - """Flat networking with dhcp""" + """Flat networking with dhcp.""" def setup_fixed_ip(self, context, address): - """Setup dhcp for this network""" + """Setup dhcp for this network.""" network_ref = db.fixed_ip_get_by_address(context, address) self.driver.update_dhcp(context, network_ref['id']) def deallocate_fixed_ip(self, context, address, *args, **kwargs): - """Returns a fixed ip to the pool""" + """Returns a fixed ip to the pool.""" self.db.fixed_ip_update(context, address, {'allocated': False}) def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a project""" + """Called when this host becomes the host for a project.""" super(FlatDHCPManager, self)._on_set_network_host(context, network_id) network_ref = self.db.network_get(context, network_id) self.db.network_update(context, @@ -313,11 +337,11 @@ class FlatDHCPManager(NetworkManager): class VlanManager(NetworkManager): - """Vlan network with dhcp""" + """Vlan network with dhcp.""" @defer.inlineCallbacks def periodic_tasks(self, context=None): - """Tasks to be run at a periodic interval""" + """Tasks to be run at a periodic interval.""" yield super(VlanManager, self).periodic_tasks(context) now = datetime.datetime.utcnow() timeout = FLAGS.fixed_ip_disassociate_timeout @@ -330,13 +354,13 @@ class VlanManager(NetworkManager): def init_host(self): """Do any initialization that needs to be run if this is a - standalone service. + standalone service. """ super(VlanManager, self).init_host() self.driver.init_host() def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): - """Gets a fixed ip from the pool""" + """Gets a fixed ip from the pool.""" # TODO(vish): This should probably be getting project_id from # the instance, but it is another trip to the db. # Perhaps this method should take an instance_ref. @@ -356,11 +380,11 @@ class VlanManager(NetworkManager): return address def deallocate_fixed_ip(self, context, address, *args, **kwargs): - """Returns a fixed ip to the pool""" + """Returns a fixed ip to the pool.""" self.db.fixed_ip_update(context, address, {'allocated': False}) def setup_fixed_ip(self, context, address): - """Sets forwarding rules and dhcp for fixed ip""" + """Sets forwarding rules and dhcp for fixed ip.""" fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) network_ref = self.db.fixed_ip_get_network(context, address) if self.db.instance_is_vpn(context, fixed_ip_ref['instance_id']): @@ -370,19 +394,19 @@ class VlanManager(NetworkManager): self.driver.update_dhcp(context, network_ref['id']) def setup_compute_network(self, context, instance_id): - """Sets up matching network for compute hosts""" + """Sets up matching network for compute hosts.""" network_ref = db.network_get_by_instance(context, instance_id) self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge']) def restart_nets(self): - """Ensure the network for each user is enabled""" + """Ensure the network for each user is enabled.""" # TODO(vish): Implement this pass def create_networks(self, context, cidr, num_networks, network_size, vlan_start, vpn_start): - """Create networks based on parameters""" + """Create networks based on parameters.""" fixed_net = IPy.IP(cidr) for index in range(num_networks): vlan = vlan_start + index @@ -407,12 +431,12 @@ class VlanManager(NetworkManager): self._create_fixed_ips(context, network_ref['id']) def get_network(self, context): - """Get the network for the current context""" + """Get the network for the current context.""" return self.db.project_get_network(context.elevated(), context.project_id) def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a network""" + """Called when this host becomes the host for a network.""" network_ref = self.db.network_get(context, network_id) net = {} net['vpn_public_address'] = FLAGS.vpn_ip @@ -424,11 +448,11 @@ class VlanManager(NetworkManager): @property def _bottom_reserved_ips(self): - """Number of reserved ips at the bottom of the range""" + """Number of reserved ips at the bottom of the range.""" return super(VlanManager, self)._bottom_reserved_ips + 1 # vpn server @property def _top_reserved_ips(self): - """Number of reserved ips at the top of the range""" + """Number of reserved ips at the top of the range.""" parent_reserved = super(VlanManager, self)._top_reserved_ips return parent_reserved + FLAGS.cnt_vpn_clients -- cgit From 60c82177da9c4ebbb89e5534959d0d5a52bfa49a Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 3 Nov 2010 12:38:15 -0700 Subject: Fix for bug#613264, allowing hosts to be specified for nova-api and objectstore listeners. --- nova/objectstore/handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index b26906001..aaf207db4 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -438,6 +438,7 @@ def get_application(): # Disabled because of lack of proper introspection in Twisted # or possibly different versions of twisted? # pylint: disable-msg=E1101 - objectStoreService = internet.TCPServer(FLAGS.s3_port, factory) + objectStoreService = internet.TCPServer(FLAGS.s3_port, factory, + interface=FLAGS.s3_host) objectStoreService.setServiceParent(application) return application -- cgit From d65c35bcadc6cc4e4d1fc61502d43fd001ce2f0e Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 3 Nov 2010 13:13:59 -0700 Subject: Added an extra argument to the objectstore listen to separate out the listening host from the connecting host. --- nova/objectstore/handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index aaf207db4..c8920b00c 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -61,6 +61,7 @@ from nova.objectstore import image FLAGS = flags.FLAGS +flags.DEFINE_string('s3_listen_host', '', 'Host to listen on.') def render_xml(request, value): @@ -439,6 +440,6 @@ def get_application(): # or possibly different versions of twisted? # pylint: disable-msg=E1101 objectStoreService = internet.TCPServer(FLAGS.s3_port, factory, - interface=FLAGS.s3_host) + interface=FLAGS.s3_listen_host) objectStoreService.setServiceParent(application) return application -- cgit From 817690b03f2e498fb08eba3ca455719229f24640 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 3 Nov 2010 15:06:00 -0700 Subject: pep8 whitespace and line length fixes --- nova/adminclient.py | 4 ++-- nova/compute/manager.py | 3 ++- nova/db/api.py | 4 ++-- nova/db/sqlalchemy/models.py | 4 ++-- nova/volume/driver.py | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/adminclient.py b/nova/adminclient.py index 0227cddd7..af55197fc 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -273,10 +273,10 @@ class NovaAdminClient(object): def get_user_roles(self, user, project=None): """Returns a list of roles for the given user. - + Omitting project will return any global roles that the user has. Specifying project will return only project specific roles. - + """ params = {'User': user} if project: diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3aed1e5a5..890d79fba 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -22,7 +22,8 @@ Handles all processes relating to instances (guest vms). The :py:class:`ComputeManager` class is a :py:class:`nova.manager.Manager` that handles RPC calls relating to creating instances. It is responsible for building a disk image, launching it via the underlying virtualization driver, -responding to calls to check it state, attaching persistent as well as termination. +responding to calls to check it state, attaching persistent as well as +termination. **Related Flags** diff --git a/nova/db/api.py b/nova/db/api.py index 6f1043d8f..8f9dc2443 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -395,7 +395,7 @@ def network_create_safe(context, values): The network is only returned if the create succeeds. If the create violates constraints because the network already exists, no exception is raised. - + """ return IMPL.network_create_safe(context, values) @@ -499,7 +499,7 @@ def export_device_create_safe(context, values): The device is not returned. If the create violates the unique constraints because the shelf_id and blade_id already exist, no exception is raised. - + """ return IMPL.export_device_create_safe(context, values) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index bc9a7480e..01b5cf350 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -411,10 +411,10 @@ class Network(BASE, NovaBase): class AuthToken(BASE, NovaBase): """Represents an authorization token for all API transactions. - + Fields are a string representing the actual token and a user id for mapping to the actual user - + """ __tablename__ = 'auth_tokens' token_hash = Column(String(255), primary_key=True) diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 89e5c8d57..156aad2a0 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -114,7 +114,7 @@ class VolumeDriver(object): escaped_name)) def ensure_export(self, context, volume): - """Safely and synchronously recreates an export for a logical volume.""" + """Synchronously recreates an export for a logical volume.""" raise NotImplementedError() @defer.inlineCallbacks @@ -228,7 +228,7 @@ class ISCSIDriver(VolumeDriver): """Executes commands relating to ISCSI volumes.""" def ensure_export(self, context, volume): - """Safely and synchronously recreates an export for a logical volume.""" + """Synchronously recreates an export for a logical volume.""" iscsi_target = self.db.volume_get_iscsi_target_num(context, volume['id']) iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name']) -- cgit From a8a61d61db0b00e0b397c807ac8ca89e39a26c5b Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 5 Nov 2010 19:52:12 -0400 Subject: Fix docstrings for wsigfied methods. --- nova/api/__init__.py | 3 +++ nova/utils.py | 8 ++++++++ 2 files changed, 11 insertions(+) (limited to 'nova') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index f693225d9..1dabd3d21 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -30,6 +30,7 @@ import routes import webob.dec from nova import flags +from nova import utils from nova import wsgi from nova.api import cloudpipe from nova.api import ec2 @@ -83,6 +84,7 @@ class API(wsgi.Router): mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API()) super(API, self).__init__(mapper) + @utils.fix_wsgify_docstr @webob.dec.wsgify def osapi_versions(self, req): """Respond to a request for all OpenStack API versions.""" @@ -94,6 +96,7 @@ class API(wsgi.Router): "attributes": dict(version=["status", "id"])}} return wsgi.Serializer(req.environ, metadata).to_content_type(response) + @utils.fix_wsgify_docstr @webob.dec.wsgify def ec2api_versions(self, req): """Respond to a request for all EC2 versions.""" diff --git a/nova/utils.py b/nova/utils.py index e7892a212..d7ebe5b4c 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -21,6 +21,7 @@ System-level utilities and helper functions. """ import datetime +import functools import inspect import logging import os @@ -232,3 +233,10 @@ def utf8(value): return value.encode("utf-8") assert isinstance(value, str) return value + +def fix_wsgify_docstr(wsgified_func): + """A decorator to re-assign docstrings that webob.dec.wsgify clobbers.""" + @functools.wraps(wsgified_func.func) + def _f(*args, **kwargs): + wsgified_func(*args, **kwargs) + return _f -- cgit From 7ca2d7593e7faf36bab9d6cb1c8b0ba16494679f Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 8 Nov 2010 02:54:28 +0900 Subject: Normalization of Dev reference docs. --- nova/manager.py | 34 +++++++++++++++++++++++++++++++++- nova/service.py | 6 +++++- 2 files changed, 38 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/manager.py b/nova/manager.py index 4244b2db4..a6efb8732 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -15,8 +15,40 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + """ -Base class for managers of different parts of the system +Managers are responsible for a certain aspect of the sytem. It is a logical +grouping of code relating to a portion of the system. In general other +components should be using the manager to make changes to the components that +it is responsible for. + +For example, other components that need to deal with volumes in some way, +should do so by calling methods on the VolumeManager instead of directly +changing fields in the database. This allows us to keep all of the code +relating to volumes in the same place. + +We have adopted a basic strategy of Smart managers and dumb data, which means +rather than attaching methods to data objects, components should call manager +methods that act on the data. + +Methods on managers that can be executed locally should be called directly. If +a particular method must execute on a remote host, this should be done via rpc +to the service that wraps the manager + +Managers should be responsible for most of the db access, and +non-implementation specific data. Anything implementation specific that can't +be generalized should be done by the Driver. + +In general, we prefer to have one manager with multiple drivers for different +implementations, but sometimes it makes sense to have multiple managers. You +can think of it this way: Abstract different overall strategies at the manager +level(FlatNetwork vs VlanNetwork), and different implementations at the driver +level(LinuxNetDriver vs CiscoNetDriver). + +Managers will often provide methods for initial setup of a host or periodic +tasksto a wrapping service. + +This module provides Manager, a base class for managers. """ from nova import utils diff --git a/nova/service.py b/nova/service.py index d53d92b65..0eb3a2762 100644 --- a/nova/service.py +++ b/nova/service.py @@ -17,7 +17,11 @@ # under the License. """ -Generic Node baseclass for all workers that run on hosts +A service is a very thin wrapper around a Manager object. It exposes the +manager's public methods to other components of the system via rpc. It will +report state periodically to the database and is responsible for initiating any periodic tasts that need to be executed on a given host. + +This module contains Service, a generic baseclass for all workers. """ import inspect -- cgit From a999c8de9e51da2beda13fdbb66dfb0bad42f250 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sun, 7 Nov 2010 14:46:17 -0500 Subject: back out stacked merge --- nova/manager.py | 34 +--------------------------------- nova/service.py | 6 +----- 2 files changed, 2 insertions(+), 38 deletions(-) (limited to 'nova') diff --git a/nova/manager.py b/nova/manager.py index a6efb8732..4244b2db4 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -15,40 +15,8 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - """ -Managers are responsible for a certain aspect of the sytem. It is a logical -grouping of code relating to a portion of the system. In general other -components should be using the manager to make changes to the components that -it is responsible for. - -For example, other components that need to deal with volumes in some way, -should do so by calling methods on the VolumeManager instead of directly -changing fields in the database. This allows us to keep all of the code -relating to volumes in the same place. - -We have adopted a basic strategy of Smart managers and dumb data, which means -rather than attaching methods to data objects, components should call manager -methods that act on the data. - -Methods on managers that can be executed locally should be called directly. If -a particular method must execute on a remote host, this should be done via rpc -to the service that wraps the manager - -Managers should be responsible for most of the db access, and -non-implementation specific data. Anything implementation specific that can't -be generalized should be done by the Driver. - -In general, we prefer to have one manager with multiple drivers for different -implementations, but sometimes it makes sense to have multiple managers. You -can think of it this way: Abstract different overall strategies at the manager -level(FlatNetwork vs VlanNetwork), and different implementations at the driver -level(LinuxNetDriver vs CiscoNetDriver). - -Managers will often provide methods for initial setup of a host or periodic -tasksto a wrapping service. - -This module provides Manager, a base class for managers. +Base class for managers of different parts of the system """ from nova import utils diff --git a/nova/service.py b/nova/service.py index 0eb3a2762..d53d92b65 100644 --- a/nova/service.py +++ b/nova/service.py @@ -17,11 +17,7 @@ # under the License. """ -A service is a very thin wrapper around a Manager object. It exposes the -manager's public methods to other components of the system via rpc. It will -report state periodically to the database and is responsible for initiating any periodic tasts that need to be executed on a given host. - -This module contains Service, a generic baseclass for all workers. +Generic Node baseclass for all workers that run on hosts """ import inspect -- cgit From 2c01c325719473fc764deec607a2b634ada5579a Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sun, 7 Nov 2010 14:51:40 -0500 Subject: Merge lp:~termie/nova/trunkdoc (via patch, since bzr though it was already merged) --- nova/manager.py | 34 +++++++++++++++++++++++++++++++++- nova/service.py | 6 +++++- 2 files changed, 38 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/manager.py b/nova/manager.py index 4244b2db4..a6efb8732 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -15,8 +15,40 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + """ -Base class for managers of different parts of the system +Managers are responsible for a certain aspect of the sytem. It is a logical +grouping of code relating to a portion of the system. In general other +components should be using the manager to make changes to the components that +it is responsible for. + +For example, other components that need to deal with volumes in some way, +should do so by calling methods on the VolumeManager instead of directly +changing fields in the database. This allows us to keep all of the code +relating to volumes in the same place. + +We have adopted a basic strategy of Smart managers and dumb data, which means +rather than attaching methods to data objects, components should call manager +methods that act on the data. + +Methods on managers that can be executed locally should be called directly. If +a particular method must execute on a remote host, this should be done via rpc +to the service that wraps the manager + +Managers should be responsible for most of the db access, and +non-implementation specific data. Anything implementation specific that can't +be generalized should be done by the Driver. + +In general, we prefer to have one manager with multiple drivers for different +implementations, but sometimes it makes sense to have multiple managers. You +can think of it this way: Abstract different overall strategies at the manager +level(FlatNetwork vs VlanNetwork), and different implementations at the driver +level(LinuxNetDriver vs CiscoNetDriver). + +Managers will often provide methods for initial setup of a host or periodic +tasksto a wrapping service. + +This module provides Manager, a base class for managers. """ from nova import utils diff --git a/nova/service.py b/nova/service.py index d53d92b65..0eb3a2762 100644 --- a/nova/service.py +++ b/nova/service.py @@ -17,7 +17,11 @@ # under the License. """ -Generic Node baseclass for all workers that run on hosts +A service is a very thin wrapper around a Manager object. It exposes the +manager's public methods to other components of the system via rpc. It will +report state periodically to the database and is responsible for initiating any periodic tasts that need to be executed on a given host. + +This module contains Service, a generic baseclass for all workers. """ import inspect -- cgit From 3b695e11da34247123ea919e71096e53393f227b Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Thu, 11 Nov 2010 19:52:36 -0600 Subject: Added a .mailmap that maps addresses in bzr to people's real, preferred e-mail addresses. (I made a few guesses along the way, feel free to adjust according to what is actually the preferred e-mail) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a couple of methods to nova.utils to parse said .mailmap and do the appropriate (though highly naïve) replacement. Apply mailmap replacement in changelog generation in setup.py. Add a unit test that checks everyone is properly listed in Authors. Add sleepsonthefloor to Authors. If anyone knows the real name, please add it. --- nova/tests/misc_unittest.py | 48 +++++++++++++++++++++++++++++++++++++++++++++ nova/utils.py | 16 +++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 nova/tests/misc_unittest.py (limited to 'nova') diff --git a/nova/tests/misc_unittest.py b/nova/tests/misc_unittest.py new file mode 100644 index 000000000..856060afa --- /dev/null +++ b/nova/tests/misc_unittest.py @@ -0,0 +1,48 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack 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 os +import subprocess + +from nova import test +from nova.utils import parse_mailmap, str_dict_replace + + +class ProjectTestCase(test.TrialTestCase): + def test_authors_up_to_date(self): + if os.path.exists('../.bzr'): + log_cmd = subprocess.Popen(["bzr", "log", "-n0"], + stdout=subprocess.PIPE) + changelog = log_cmd.communicate()[0] + mailmap = parse_mailmap('../.mailmap') + + contributors = set() + for l in changelog.split('\n'): + l = l.strip() + if (l.startswith('author:') or l.startswith('committer:') + and not l == 'committer: Tarmac'): + email = l.split(' ')[-1] + contributors.add(str_dict_replace(email, mailmap)) + + authors_file = open('../Authors', 'r').read() + + missing = set() + for contributor in contributors: + if not contributor in authors_file: + missing.add(contributor) + + self.assertTrue(len(missing) == 0, + '%r not listed in Authors' % missing) diff --git a/nova/utils.py b/nova/utils.py index e7892a212..b63237c10 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -173,6 +173,22 @@ def isotime(at=None): def parse_isotime(timestr): return datetime.datetime.strptime(timestr, TIME_FORMAT) +def parse_mailmap(mailmap='.mailmap'): + mapping = {} + if os.path.exists(mailmap): + fp = open(mailmap, 'r') + for l in fp: + l = l.strip() + if not l.startswith('#') and ' ' in l: + canonical_email, alias = l.split(' ') + mapping[alias] = canonical_email + return mapping + +def str_dict_replace(s, mapping): + for s1, s2 in mapping.iteritems(): + s = s.replace(s1, s2) + return s + class LazyPluggable(object): """A pluggable backend loaded lazily based on some value.""" -- cgit From f2c84807600dd49458e7b342b70a4bb8f1bb2232 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 15 Nov 2010 14:43:50 -0500 Subject: pep8 --- nova/service.py | 3 ++- nova/utils.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/service.py b/nova/service.py index 0eb3a2762..9454d4049 100644 --- a/nova/service.py +++ b/nova/service.py @@ -19,7 +19,8 @@ """ A service is a very thin wrapper around a Manager object. It exposes the manager's public methods to other components of the system via rpc. It will -report state periodically to the database and is responsible for initiating any periodic tasts that need to be executed on a given host. +report state periodically to the database and is responsible for initiating +any periodic tasts that need to be executed on a given host. This module contains Service, a generic baseclass for all workers. """ diff --git a/nova/utils.py b/nova/utils.py index d7ebe5b4c..1207f52a4 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -234,6 +234,7 @@ def utf8(value): assert isinstance(value, str) return value + def fix_wsgify_docstr(wsgified_func): """A decorator to re-assign docstrings that webob.dec.wsgify clobbers.""" @functools.wraps(wsgified_func.func) -- cgit From f3744b0de85a1bc5be77f37a770144d3244bca86 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 15 Nov 2010 19:13:45 -0500 Subject: Change how wsgified doc wrapping happens to fix test. --- nova/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/utils.py b/nova/utils.py index 1207f52a4..d074cab75 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -237,7 +237,6 @@ def utf8(value): def fix_wsgify_docstr(wsgified_func): """A decorator to re-assign docstrings that webob.dec.wsgify clobbers.""" - @functools.wraps(wsgified_func.func) - def _f(*args, **kwargs): - wsgified_func(*args, **kwargs) - return _f + wsgified_func.__doc__ = wsgified_func.func.__doc__ + wsgified_func.func_name = wsgified_func.func.func_name + return wsgified_func -- cgit From ff3ec33010ce8ece87523f7cf3ef2e4a0a23006e Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 15 Nov 2010 21:08:08 -0500 Subject: The docs are just going to be wrong for now. I'll file a bug upstream. --- nova/api/__init__.py | 2 -- nova/utils.py | 7 ------- 2 files changed, 9 deletions(-) (limited to 'nova') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 1dabd3d21..7e75445a8 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -84,7 +84,6 @@ class API(wsgi.Router): mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API()) super(API, self).__init__(mapper) - @utils.fix_wsgify_docstr @webob.dec.wsgify def osapi_versions(self, req): """Respond to a request for all OpenStack API versions.""" @@ -96,7 +95,6 @@ class API(wsgi.Router): "attributes": dict(version=["status", "id"])}} return wsgi.Serializer(req.environ, metadata).to_content_type(response) - @utils.fix_wsgify_docstr @webob.dec.wsgify def ec2api_versions(self, req): """Respond to a request for all EC2 versions.""" diff --git a/nova/utils.py b/nova/utils.py index d074cab75..2970b93bb 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -233,10 +233,3 @@ def utf8(value): return value.encode("utf-8") assert isinstance(value, str) return value - - -def fix_wsgify_docstr(wsgified_func): - """A decorator to re-assign docstrings that webob.dec.wsgify clobbers.""" - wsgified_func.__doc__ = wsgified_func.func.__doc__ - wsgified_func.func_name = wsgified_func.func.func_name - return wsgified_func -- cgit From 777663e9673310880e0aaf47093ceedd153eedeb Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 16 Nov 2010 13:26:59 -0500 Subject: pep8 --- nova/compute/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 7f1e2b25f..925c20eee 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -176,7 +176,7 @@ def _inject_key_into_fs(key, fs, execute=None): @defer.inlineCallbacks def _inject_net_into_fs(net, fs, execute=None): netdir = os.path.join(os.path.join(fs, 'etc'), 'network') - yield execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter + yield execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter yield execute('sudo chown root:root %s' % netdir) yield execute('sudo chmod 755 %s' % netdir) netfile = os.path.join(netdir, 'interfaces') -- cgit From 11bca7a4f07c2e7037c8b08b2383a7c6e296b15a Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 16 Nov 2010 13:32:16 -0500 Subject: Add docstrings to any methods I touch --- nova/compute/disk.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'nova') diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 925c20eee..61c9c077f 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -165,6 +165,11 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): @defer.inlineCallbacks def _inject_key_into_fs(key, fs, execute=None): + """Add the given public ssh key to root's authorized_keys. + + key is an ssh key string. + fs is the path to the base of the filesystem into which to inject the key. + """ sshdir = os.path.join(os.path.join(fs, 'root'), '.ssh') yield execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter yield execute('sudo chown root %s' % sshdir) @@ -175,6 +180,10 @@ def _inject_key_into_fs(key, fs, execute=None): @defer.inlineCallbacks def _inject_net_into_fs(net, fs, execute=None): + """Inject /etc/network/interfaces into the filesystem rooted at fs. + + net is the contents of /etc/network/interfaces. + """ netdir = os.path.join(os.path.join(fs, 'etc'), 'network') yield execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter yield execute('sudo chown root:root %s' % netdir) -- cgit From e72ae82362fb8a93d599c0c4473aa41c96837cf5 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 16 Nov 2010 13:49:18 -0500 Subject: pep8 --- nova/compute/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 61c9c077f..4338d39f0 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -166,7 +166,7 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): @defer.inlineCallbacks def _inject_key_into_fs(key, fs, execute=None): """Add the given public ssh key to root's authorized_keys. - + key is an ssh key string. fs is the path to the base of the filesystem into which to inject the key. """ -- cgit From 7b4733b36d5351a2ba42c82b4d2b821a3b1d12cd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 16 Nov 2010 23:38:37 +0000 Subject: fixes errors in describe address and associate address. Adds test cases --- nova/api/ec2/cloud.py | 6 +++--- nova/tests/cloud_unittest.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index fbe4caa48..e2eaa7c5c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -679,7 +679,7 @@ class CloudController(object): context.project_id) for floating_ip_ref in iterator: address = floating_ip_ref['address'] - instance_id = None + ec2_id = None if (floating_ip_ref['fixed_ip'] and floating_ip_ref['fixed_ip']['instance']): internal_id = floating_ip_ref['fixed_ip']['instance']['ec2_id'] @@ -717,8 +717,8 @@ class CloudController(object): "args": {"floating_address": floating_ip_ref['address']}}) return {'releaseResponse': ["Address released."]} - def associate_address(self, context, ec2_id, public_ip, **kwargs): - internal_id = ec2_id_to_internal_id(ec2_id) + def associate_address(self, context, instance_id, public_ip, **kwargs): + internal_id = ec2_id_to_internal_id(instance_id) instance_ref = db.instance_get_by_internal_id(context, internal_id) fixed_address = db.instance_get_fixed_address(context, instance_ref['id']) diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 2d61d2675..938210fa7 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -91,6 +91,35 @@ class CloudTestCase(test.TrialTestCase): # NOTE(vish): create depends on pool, so just call helper directly return cloud._gen_key(self.context, self.context.user.id, name) + def test_describe_addresses(self): + """Makes sure describe addresses runs without raising an exception""" + address = "10.10.10.10" + db.floating_ip_create(context.get_admin_context(), + {'address': address, + 'host': FLAGS.host}) + self.cloud.allocate_address(self.context) + self.cloud.describe_addresses(self.context) + self.cloud.release_address(self.context, + public_ip=address) + + def test_associate_disassociate_address(self): + """Verifies associate runs cleanly without raising an exception""" + address = "10.10.10.10" + db.floating_ip_create(context.get_admin_context(), + {'address': address, + 'host': FLAGS.host}) + self.cloud.allocate_address(self.context) + inst = db.instance_create(self.context, {}) + ec2_id = cloud.internal_id_to_ec2_id(inst['internal_id']) + self.cloud.associate_address(self.context, + instance_id=ec2_id, + public_ip=address) + self.cloud.disassociate_address(self.context, + public_ip=address) + self.cloud.release_address(self.context, + public_ip=address) + db.instance_destroy(self.context, inst['id']) + def test_console_output(self): image_id = FLAGS.default_image instance_type = FLAGS.default_instance_type -- cgit From 5bd44c890be3fa5c632897c106409ff556e6a19a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 17 Nov 2010 02:23:20 +0000 Subject: fix leaking floating ip from network unittests and use of fakeldap driver --- nova/tests/network_unittest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index b7caed4fd..6f4705719 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -41,7 +41,6 @@ class NetworkTestCase(test.TrialTestCase): # flags in the corresponding section in nova-dhcpbridge self.flags(connection_type='fake', fake_network=True, - auth_driver='nova.auth.ldapdriver.FakeLdapDriver', network_size=16, num_networks=5) logging.getLogger().setLevel(logging.DEBUG) @@ -127,6 +126,7 @@ class NetworkTestCase(test.TrialTestCase): self.network.deallocate_floating_ip(self.context, float_addr) self.network.deallocate_fixed_ip(self.context, fix_addr) release_ip(fix_addr) + db.floating_ip_destroy(context.get_admin_context(), float_addr) def test_allocate_deallocate_fixed_ip(self): """Makes sure that we can allocate and deallocate a fixed ip""" -- cgit From db8c0a153df467c645df82b2ed6c2b282eae6850 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 17 Nov 2010 02:41:04 +0000 Subject: delete floating ips after tests --- nova/tests/cloud_unittest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 938210fa7..2c6d9959b 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -94,18 +94,19 @@ class CloudTestCase(test.TrialTestCase): def test_describe_addresses(self): """Makes sure describe addresses runs without raising an exception""" address = "10.10.10.10" - db.floating_ip_create(context.get_admin_context(), + db.floating_ip_create(self.context, {'address': address, 'host': FLAGS.host}) self.cloud.allocate_address(self.context) self.cloud.describe_addresses(self.context) self.cloud.release_address(self.context, public_ip=address) + db.floating_ip_destroy(self.context, address) def test_associate_disassociate_address(self): """Verifies associate runs cleanly without raising an exception""" address = "10.10.10.10" - db.floating_ip_create(context.get_admin_context(), + db.floating_ip_create(self.context, {'address': address, 'host': FLAGS.host}) self.cloud.allocate_address(self.context) @@ -119,6 +120,7 @@ class CloudTestCase(test.TrialTestCase): self.cloud.release_address(self.context, public_ip=address) db.instance_destroy(self.context, inst['id']) + db.floating_ip_destroy(self.context, address) def test_console_output(self): image_id = FLAGS.default_image -- cgit From 973b2d6892dfacb3d9a2f7e87514f6e18faa37e4 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 17 Nov 2010 13:56:42 -0600 Subject: Improved Pylint Score --- nova/db/sqlalchemy/api.py | 9 ++++----- nova/image/services/glance/__init__.py | 1 + nova/network/linux_net.py | 2 +- nova/objectstore/bucket.py | 4 ++-- nova/objectstore/image.py | 4 ++-- nova/virt/libvirt_conn.py | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b8f999af4..2a5442d9b 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -44,7 +44,6 @@ def is_admin_context(context): warnings.warn('Use of empty request context is deprecated', DeprecationWarning) raise Exception('die') - return True return context.is_admin @@ -502,14 +501,14 @@ def fixed_ip_get_by_address(context, address, session=None): @require_context def fixed_ip_get_instance(context, address): - fixed_ip_ref = fixed_ip_get_by_address(context, address) - return fixed_ip_ref.instance + fixed_ip_ref = fixed_ip_get_by_address(context, address) + return fixed_ip_ref.instance @require_admin_context def fixed_ip_get_network(context, address): - fixed_ip_ref = fixed_ip_get_by_address(context, address) - return fixed_ip_ref.network + fixed_ip_ref = fixed_ip_get_by_address(context, address) + return fixed_ip_ref.network @require_context diff --git a/nova/image/services/glance/__init__.py b/nova/image/services/glance/__init__.py index f1d05f0bc..9617ab7a3 100644 --- a/nova/image/services/glance/__init__.py +++ b/nova/image/services/glance/__init__.py @@ -19,6 +19,7 @@ import httplib import json +import logging import urlparse import webob.exc diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 7b323efa1..4ea24cda6 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -244,7 +244,7 @@ def _confirm_rule(chain, cmd): def _remove_rule(chain, cmd): """Remove iptables rule""" if FLAGS.use_nova_chains: - chain = "%S" % chain.lower() + chain = "%s" % chain.lower() _execute("sudo iptables --delete %s %s" % (chain, cmd)) diff --git a/nova/objectstore/bucket.py b/nova/objectstore/bucket.py index 0ba4934d1..697982538 100644 --- a/nova/objectstore/bucket.py +++ b/nova/objectstore/bucket.py @@ -78,8 +78,8 @@ class Bucket(object): path = os.path.abspath(os.path.join( FLAGS.buckets_path, bucket_name)) if not path.startswith(os.path.abspath(FLAGS.buckets_path)) or \ - os.path.exists(path): - raise exception.NotAuthorized() + os.path.exists(path): + raise exception.NotAuthorized() os.makedirs(path) diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index b7b2ec6ab..4554444fa 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -48,8 +48,8 @@ class Image(object): self.image_id = image_id self.path = os.path.abspath(os.path.join(FLAGS.images_path, image_id)) if not self.path.startswith(os.path.abspath(FLAGS.images_path)) or \ - not os.path.isdir(self.path): - raise exception.NotFound + not os.path.isdir(self.path): + raise exception.NotFound @property def image_path(self): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 535a3b53e..18085089f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -420,7 +420,7 @@ class LibvirtConnection(object): @defer.inlineCallbacks def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None): # syntactic nicety - basepath = lambda fname='', prefix=prefix: os.path.join( + basepath = lambda fname = '', prefix = prefix: os.path.join( FLAGS.instances_path, inst['name'], prefix + fname) @@ -457,7 +457,7 @@ class LibvirtConnection(object): yield images.fetch(inst.ramdisk_id, basepath('ramdisk'), user, project) - execute = lambda cmd, process_input=None, check_exit_code=True: \ + execute = lambda cmd, process_input = None, check_exit_code = True: \ process.simple_execute(cmd=cmd, process_input=process_input, check_exit_code=check_exit_code) -- cgit From 1dfdd7477647e45b96b5781789b846d5c9066663 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 17 Nov 2010 21:23:12 +0000 Subject: fix greenthread race conditions in trunk and floating ip leakage --- nova/db/sqlalchemy/api.py | 2 +- nova/tests/cloud_unittest.py | 4 ++++ nova/tests/quota_unittest.py | 8 +++----- 3 files changed, 8 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b8f999af4..dfa4efc8f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -390,7 +390,7 @@ def floating_ip_get_by_address(context, address, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound('No fixed ip for address %s' % address) + raise exception.NotFound('No floating ip for address %s' % address) return result diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 2c6d9959b..9886a2449 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -101,6 +101,7 @@ class CloudTestCase(test.TrialTestCase): self.cloud.describe_addresses(self.context) self.cloud.release_address(self.context, public_ip=address) + greenthread.sleep(0.3) db.floating_ip_destroy(self.context, address) def test_associate_disassociate_address(self): @@ -111,6 +112,7 @@ class CloudTestCase(test.TrialTestCase): 'host': FLAGS.host}) self.cloud.allocate_address(self.context) inst = db.instance_create(self.context, {}) + fixed = self.network.allocate_fixed_ip(self.context, inst['id']) ec2_id = cloud.internal_id_to_ec2_id(inst['internal_id']) self.cloud.associate_address(self.context, instance_id=ec2_id, @@ -119,6 +121,8 @@ class CloudTestCase(test.TrialTestCase): public_ip=address) self.cloud.release_address(self.context, public_ip=address) + greenthread.sleep(0.3) + self.network.deallocate_fixed_ip(self.context, fixed) db.instance_destroy(self.context, inst['id']) db.floating_ip_destroy(self.context, address) diff --git a/nova/tests/quota_unittest.py b/nova/tests/quota_unittest.py index 9e3afbf4e..b7c1d2acc 100644 --- a/nova/tests/quota_unittest.py +++ b/nova/tests/quota_unittest.py @@ -138,11 +138,8 @@ class QuotaTestCase(test.TrialTestCase): def test_too_many_addresses(self): address = '192.168.0.100' - try: - db.floating_ip_get_by_address(context.get_admin_context(), address) - except exception.NotFound: - db.floating_ip_create(context.get_admin_context(), - {'address': address, 'host': FLAGS.host}) + db.floating_ip_create(context.get_admin_context(), + {'address': address, 'host': FLAGS.host}) float_addr = self.network.allocate_floating_ip(self.context, self.project.id) # NOTE(vish): This assert never fails. When cloud attempts to @@ -151,3 +148,4 @@ class QuotaTestCase(test.TrialTestCase): # that is breaking. self.assertRaises(cloud.QuotaError, self.cloud.allocate_address, self.context) + db.floating_ip_destroy(context.get_admin_context(), address) -- cgit From 659058bef7913254eca63e7f67a5d74ffe146e57 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 18 Nov 2010 00:11:45 +0000 Subject: fixes flatdhcp, updates nova.sh, allows for empty bridge device --- nova/network/linux_net.py | 7 ++++--- nova/network/manager.py | 24 +++++++++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 7b323efa1..68037ed9a 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -42,7 +42,7 @@ flags.DEFINE_string('networks_path', utils.abspath('../networks'), 'Location to keep network config files') flags.DEFINE_string('public_interface', 'vlan1', 'Interface for public IP addresses') -flags.DEFINE_string('bridge_dev', 'eth0', +flags.DEFINE_string('bridge_dev', None, 'network device for bridges') flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'), 'location of nova-dhcpbridge') @@ -142,12 +142,13 @@ def ensure_vlan(vlan_num): def ensure_bridge(bridge, interface, net_attrs=None): """Create a bridge unless it already exists""" if not _device_exists(bridge): - logging.debug("Starting Bridge inteface for %s", interface) + logging.debug("Starting Bridge interface for %s", interface) _execute("sudo brctl addbr %s" % bridge) _execute("sudo brctl setfd %s 0" % bridge) # _execute("sudo brctl setageing %s 10" % bridge) _execute("sudo brctl stp %s off" % bridge) - _execute("sudo brctl addif %s %s" % (bridge, interface)) + if interface: + _execute("sudo brctl addif %s %s" % (bridge, interface)) if net_attrs: _execute("sudo ifconfig %s %s broadcast %s netmask %s up" % \ (bridge, diff --git a/nova/network/manager.py b/nova/network/manager.py index b033bb0a4..96f8cf50b 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -63,7 +63,7 @@ flags.DEFINE_string('flat_network_bridge', 'br100', 'Bridge for simple network instances') flags.DEFINE_string('flat_network_dns', '8.8.4.4', 'Dns for simple network') -flags.DEFINE_string('flat_network_dhcp_start', '192.168.0.2', +flags.DEFINE_string('flat_network_dhcp_start', '10.0.0.2', 'Dhcp start for FlatDhcp') flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks') flags.DEFINE_integer('num_networks', 1000, 'Number of networks to support') @@ -285,6 +285,7 @@ class FlatManager(NetworkManager): cidr = "%s/%s" % (fixed_net[start], significant_bits) project_net = IPy.IP(cidr) net = {} + net['bridge'] = FLAGS.flat_network_bridge net['cidr'] = cidr net['netmask'] = str(project_net.netmask()) net['gateway'] = str(project_net[1]) @@ -307,17 +308,23 @@ class FlatManager(NetworkManager): """Called when this host becomes the host for a network.""" net = {} net['injected'] = True - net['bridge'] = FLAGS.flat_network_bridge net['dns'] = FLAGS.flat_network_dns self.db.network_update(context, network_id, net) -class FlatDHCPManager(NetworkManager): +class FlatDHCPManager(FlatManager): """Flat networking with dhcp.""" + def setup_compute_network(self, context, instance_id): + """Sets up matching network for compute hosts.""" + network_ref = db.network_get_by_instance(context, instance_id) + self.driver.ensure_bridge(network_ref['bridge'], + FLAGS.bridge_dev, + network_ref) + def setup_fixed_ip(self, context, address): """Setup dhcp for this network.""" - network_ref = db.fixed_ip_get_by_address(context, address) + network_ref = db.fixed_ip_get_network(context, address) self.driver.update_dhcp(context, network_ref['id']) def deallocate_fixed_ip(self, context, address, *args, **kwargs): @@ -326,11 +333,10 @@ class FlatDHCPManager(NetworkManager): def _on_set_network_host(self, context, network_id): """Called when this host becomes the host for a project.""" - super(FlatDHCPManager, self)._on_set_network_host(context, network_id) - network_ref = self.db.network_get(context, network_id) - self.db.network_update(context, - network_id, - {'dhcp_start': FLAGS.flat_network_dhcp_start}) + net = {} + net['dhcp_start'] = FLAGS.flat_network_dhcp_start + self.db.network_update(context, network_id, net) + network_ref = db.network_get(context, network_id) self.driver.ensure_bridge(network_ref['bridge'], FLAGS.bridge_dev, network_ref) -- cgit From 70e10503472d37f08f9f0880c87e10afc3abc851 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 18 Nov 2010 10:52:54 -0800 Subject: Make sure that the response body is a string and not unicode --- nova/api/ec2/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index b7664ec71..a6ee16c33 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -244,8 +244,8 @@ class Executor(wsgi.Application): resp = webob.Response() resp.status = 400 resp.headers['Content-Type'] = 'text/xml' - resp.body = ('\n' + resp.body = str('\n' '%s' '%s' - '?') % (code, message) + '?' % (code, message)) return resp -- cgit From 8e1b88cc228f9ed55c3b6e4fdd790a572d63e6fe Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 18 Nov 2010 13:27:52 -0800 Subject: First step to getting the image APIs consolidated. The EC2 API was using a one-off S3 image service wrapper, but this should be moved into the nova.image space and use the same interface as the others. There are still some mismatches between the various image service implementations, but this patch was getting large and wanted to keep it within a resonable size. --- nova/api/ec2/cloud.py | 24 ++-- nova/api/ec2/images.py | 123 ----------------- nova/api/openstack/images.py | 13 +- nova/flags.py | 2 +- nova/image/glance.py | 227 ++++++++++++++++++++++++++++++++ nova/image/local.py | 88 +++++++++++++ nova/image/s3.py | 108 +++++++++++++++ nova/image/service.py | 97 +------------- nova/image/services/__init__.py | 0 nova/image/services/glance/__init__.py | 216 ------------------------------ nova/tests/api/openstack/fakes.py | 20 ++- nova/tests/api/openstack/test_images.py | 40 +++--- 12 files changed, 481 insertions(+), 477 deletions(-) delete mode 100644 nova/api/ec2/images.py create mode 100644 nova/image/glance.py create mode 100644 nova/image/local.py create mode 100644 nova/image/s3.py delete mode 100644 nova/image/services/__init__.py delete mode 100644 nova/image/services/glance/__init__.py (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e2eaa7c5c..9327bf0d4 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -41,7 +41,7 @@ from nova import rpc from nova import utils from nova.compute.instance_types import INSTANCE_TYPES from nova.api import cloud -from nova.api.ec2 import images +from nova.image.s3 import S3ImageService FLAGS = flags.FLAGS @@ -100,6 +100,7 @@ class CloudController(object): def __init__(self): self.network_manager = utils.import_object(FLAGS.network_manager) self.compute_manager = utils.import_object(FLAGS.compute_manager) + self.image_service = S3ImageService() self.setup() def __str__(self): @@ -785,7 +786,7 @@ class CloudController(object): vpn = kwargs['image_id'] == FLAGS.vpn_image_id if not vpn: - image = images.get(context, kwargs['image_id']) + image = self.image_service.show(context, kwargs['image_id']) # FIXME(ja): if image is vpn, this breaks # get defaults from imagestore @@ -798,8 +799,8 @@ class CloudController(object): ramdisk_id = kwargs.get('ramdisk_id', ramdisk_id) # make sure we have access to kernel and ramdisk - images.get(context, kernel_id) - images.get(context, ramdisk_id) + self.image_service.show(context, kernel_id) + self.image_service.show(context, ramdisk_id) logging.debug("Going to run %s instances...", num_instances) launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) @@ -993,20 +994,17 @@ class CloudController(object): return True def describe_images(self, context, image_id=None, **kwargs): - # The objectstore does its own authorization for describe - imageSet = images.list(context, image_id) + imageSet = self.image_service.index(context, image_id) return {'imagesSet': imageSet} def deregister_image(self, context, image_id, **kwargs): - # FIXME: should the objectstore be doing these authorization checks? - images.deregister(context, image_id) + self.image_service.deregister(context, image_id) return {'imageId': image_id} def register_image(self, context, image_location=None, **kwargs): - # FIXME: should the objectstore be doing these authorization checks? if image_location is None and 'name' in kwargs: image_location = kwargs['name'] - image_id = images.register(context, image_location) + image_id = self.image_service.register(context, image_location) logging.debug("Registered %s as %s" % (image_location, image_id)) return {'imageId': image_id} @@ -1014,7 +1012,7 @@ class CloudController(object): if attribute != 'launchPermission': raise exception.ApiError('attribute not supported: %s' % attribute) try: - image = images.list(context, image_id)[0] + image = self.image_service.show(context, image_id) except IndexError: raise exception.ApiError('invalid id: %s' % image_id) result = {'image_id': image_id, 'launchPermission': []} @@ -1033,8 +1031,8 @@ class CloudController(object): raise exception.ApiError('only group "all" is supported') if not operation_type in ['add', 'remove']: raise exception.ApiError('operation_type must be add or remove') - return images.modify(context, image_id, operation_type) + return self.image_service.modify(context, image_id, operation_type) def update_image(self, context, image_id, **kwargs): - result = images.update(context, image_id, dict(kwargs)) + result = self.image_service.update(context, image_id, dict(kwargs)) return result diff --git a/nova/api/ec2/images.py b/nova/api/ec2/images.py deleted file mode 100644 index 60f9008e9..000000000 --- a/nova/api/ec2/images.py +++ /dev/null @@ -1,123 +0,0 @@ -# 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. - -""" -Proxy AMI-related calls from the cloud controller, to the running -objectstore service. -""" - -import json -import urllib - -import boto.s3.connection - -from nova import exception -from nova import flags -from nova import utils -from nova.auth import manager - - -FLAGS = flags.FLAGS - - -def modify(context, image_id, operation): - conn(context).make_request( - method='POST', - bucket='_images', - query_args=qs({'image_id': image_id, 'operation': operation})) - - return True - - -def update(context, image_id, attributes): - """update an image's attributes / info.json""" - attributes.update({"image_id": image_id}) - conn(context).make_request( - method='POST', - bucket='_images', - query_args=qs(attributes)) - return True - - -def register(context, image_location): - """ rpc call to register a new image based from a manifest """ - - image_id = utils.generate_uid('ami') - conn(context).make_request( - method='PUT', - bucket='_images', - query_args=qs({'image_location': image_location, - 'image_id': image_id})) - - return image_id - - -def list(context, filter_list=[]): - """ return a list of all images that a user can see - - optionally filtered by a list of image_id """ - - if FLAGS.connection_type == 'fake': - return [{'imageId': 'bar'}] - - # FIXME: send along the list of only_images to check for - response = conn(context).make_request( - method='GET', - bucket='_images') - - result = json.loads(response.read()) - if not filter_list is None: - return [i for i in result if i['imageId'] in filter_list] - return result - - -def get(context, image_id): - """return a image object if the context has permissions""" - result = list(context, [image_id]) - if not result: - raise exception.NotFound('Image %s could not be found' % image_id) - image = result[0] - return image - - -def deregister(context, image_id): - """ unregister an image """ - conn(context).make_request( - method='DELETE', - bucket='_images', - query_args=qs({'image_id': image_id})) - - -def conn(context): - access = manager.AuthManager().get_access_key(context.user, - context.project) - secret = str(context.user.secret) - calling = boto.s3.connection.OrdinaryCallingFormat() - return boto.s3.connection.S3Connection(aws_access_key_id=access, - aws_secret_access_key=secret, - is_secure=False, - calling_format=calling, - port=FLAGS.s3_port, - host=FLAGS.s3_host) - - -def qs(params): - pairs = [] - for key in params.keys(): - pairs.append(key + '=' + urllib.quote(params[key])) - return '&'.join(pairs) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 5bc915e63..cdbdc9bdd 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -17,6 +17,7 @@ from webob import exc +from nova import context from nova import flags from nova import utils from nova import wsgi @@ -46,19 +47,23 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail.""" + user_id = req.environ['nova.context']['user']['id'] + ctxt = context.RequestContext(user_id, user_id) try: - images = self._service.detail() + images = self._service.detail(ctxt) images = nova.api.openstack.limited(images, req) except NotImplementedError: # Emulate detail() using repeated calls to show() - images = self._service.index() + images = self._service.index(ctxt) images = nova.api.openstack.limited(images, req) - images = [self._service.show(i['id']) for i in images] + images = [self._service.show(ctxt, i['id']) for i in images] return dict(images=images) def show(self, req, id): """Return data about the given image id.""" - return dict(image=self._service.show(id)) + user_id = req.environ['nova.context']['user']['id'] + ctxt = context.RequestContext(user_id, user_id) + return dict(image=self._service.show(ctxt, id)) def delete(self, req, id): # Only public images are supported for now. diff --git a/nova/flags.py b/nova/flags.py index 4ae86d9b2..07b469bca 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -232,7 +232,7 @@ DEFINE_string('scheduler_manager', 'nova.scheduler.manager.SchedulerManager', 'Manager for scheduler') # The service to use for image search and retrieval -DEFINE_string('image_service', 'nova.image.service.LocalImageService', +DEFINE_string('image_service', 'nova.image.local.LocalImageService', 'The service to use for retrieving and searching for images.') DEFINE_string('host', socket.gethostname(), diff --git a/nova/image/glance.py b/nova/image/glance.py new file mode 100644 index 000000000..547381a4a --- /dev/null +++ b/nova/image/glance.py @@ -0,0 +1,227 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Implementation of an image service that uses Glance as the backend""" + +import httplib +import json +import urlparse + +import webob.exc + +from nova import utils +from nova import flags +from nova import exception +import nova.image.service + +FLAGS = flags.FLAGS + + +flags.DEFINE_string('glance_teller_address', 'http://127.0.0.1', + 'IP address or URL where Glance\'s Teller service resides') +flags.DEFINE_string('glance_teller_port', '9191', + 'Port for Glance\'s Teller service') +flags.DEFINE_string('glance_parallax_address', 'http://127.0.0.1', + 'IP address or URL where Glance\'s Parallax service ' + 'resides') +flags.DEFINE_string('glance_parallax_port', '9292', + 'Port for Glance\'s Parallax service') + + +class TellerClient(object): + + def __init__(self): + self.address = FLAGS.glance_teller_address + self.port = FLAGS.glance_teller_port + url = urlparse.urlparse(self.address) + self.netloc = url.netloc + self.connection_type = {'http': httplib.HTTPConnection, + 'https': httplib.HTTPSConnection}[url.scheme] + + +class ParallaxClient(object): + + def __init__(self): + self.address = FLAGS.glance_parallax_address + self.port = FLAGS.glance_parallax_port + url = urlparse.urlparse(self.address) + self.netloc = url.netloc + self.connection_type = {'http': httplib.HTTPConnection, + 'https': httplib.HTTPSConnection}[url.scheme] + + def get_image_index(self): + """ + Returns a list of image id/name mappings from Parallax + """ + try: + c = self.connection_type(self.netloc, self.port) + c.request("GET", "images") + res = c.getresponse() + if res.status == 200: + # Parallax returns a JSONified dict(images=image_list) + data = json.loads(res.read())['images'] + return data + else: + logging.warn("Parallax returned HTTP error %d from " + "request for /images", res.status_int) + return [] + finally: + c.close() + + def get_image_details(self): + """ + Returns a list of detailed image data mappings from Parallax + """ + try: + c = self.connection_type(self.netloc, self.port) + c.request("GET", "images/detail") + res = c.getresponse() + if res.status == 200: + # Parallax returns a JSONified dict(images=image_list) + data = json.loads(res.read())['images'] + return data + else: + logging.warn("Parallax returned HTTP error %d from " + "request for /images/detail", res.status_int) + return [] + finally: + c.close() + + def get_image_metadata(self, image_id): + """ + Returns a mapping of image metadata from Parallax + """ + try: + c = self.connection_type(self.netloc, self.port) + c.request("GET", "images/%s" % image_id) + res = c.getresponse() + if res.status == 200: + # Parallax returns a JSONified dict(image=image_info) + data = json.loads(res.read())['image'] + return data + else: + # TODO(jaypipes): log the error? + return None + finally: + c.close() + + def add_image_metadata(self, image_metadata): + """ + Tells parallax about an image's metadata + """ + try: + c = self.connection_type(self.netloc, self.port) + body = json.dumps(image_metadata) + c.request("POST", "images", body) + res = c.getresponse() + if res.status == 200: + # Parallax returns a JSONified dict(image=image_info) + data = json.loads(res.read())['image'] + return data['id'] + else: + # TODO(jaypipes): log the error? + return None + finally: + c.close() + + def update_image_metadata(self, image_id, image_metadata): + """ + Updates Parallax's information about an image + """ + try: + c = self.connection_type(self.netloc, self.port) + body = json.dumps(image_metadata) + c.request("PUT", "images/%s" % image_id, body) + res = c.getresponse() + return res.status == 200 + finally: + c.close() + + def delete_image_metadata(self, image_id): + """ + Deletes Parallax's information about an image + """ + try: + c = self.connection_type(self.netloc, self.port) + c.request("DELETE", "images/%s" % image_id) + res = c.getresponse() + return res.status == 200 + finally: + c.close() + + +class GlanceImageService(nova.image.service.BaseImageService): + """Provides storage and retrieval of disk image objects within Glance.""" + + def __init__(self): + self.teller = TellerClient() + self.parallax = ParallaxClient() + + def index(self, context): + """ + Calls out to Parallax for a list of images available + """ + images = self.parallax.get_image_index() + return images + + def detail(self, context): + """ + Calls out to Parallax for a list of detailed image information + """ + images = self.parallax.get_image_details() + return images + + def show(self, context, id): + """ + Returns a dict containing image data for the given opaque image id. + """ + image = self.parallax.get_image_metadata(id) + if image: + return image + raise exception.NotFound + + def create(self, context, data): + """ + Store the image data and return the new image id. + + :raises AlreadyExists if the image already exist. + + """ + return self.parallax.add_image_metadata(data) + + def update(self, context, image_id, data): + """Replace the contents of the given image with the new data. + + :raises NotFound if the image does not exist. + + """ + self.parallax.update_image_metadata(image_id, data) + + def delete(self, context, image_id): + """ + Delete the given image. + + :raises NotFound if the image does not exist. + + """ + self.parallax.delete_image_metadata(image_id) + + def delete_all(self): + """ + Clears out all images + """ + pass diff --git a/nova/image/local.py b/nova/image/local.py new file mode 100644 index 000000000..9b0cdcc50 --- /dev/null +++ b/nova/image/local.py @@ -0,0 +1,88 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import cPickle as pickle +import os.path +import random + +from nova import exception +from nova.image import service + + +class LocalImageService(service.BaseImageService): + + """Image service storing images to local disk. + + It assumes that image_ids are integers.""" + + def __init__(self): + self._path = "/tmp/nova/images" + try: + os.makedirs(self._path) + except OSError: # Exists + pass + + def _path_to(self, image_id): + return os.path.join(self._path, str(image_id)) + + def _ids(self): + """The list of all image ids.""" + return [int(i) for i in os.listdir(self._path)] + + def index(self, context): + return [dict(id=i['id'], name=i['name']) for i in self.detail(context)] + + def detail(self, context): + return [self.show(context, id) for id in self._ids()] + + def show(self, context, id): + try: + return pickle.load(open(self._path_to(id))) + except IOError: + raise exception.NotFound + + def create(self, context, data): + """ + Store the image data and return the new image id. + """ + id = random.randint(0, 2 ** 32 - 1) + data['id'] = id + self.update(context, id, data) + return id + + def update(self, context, image_id, data): + """Replace the contents of the given image with the new data.""" + try: + pickle.dump(data, open(self._path_to(image_id), 'w')) + except IOError: + raise exception.NotFound + + def delete(self, context, image_id): + """ + Delete the given image. Raises OSError if the image does not exist. + """ + try: + os.unlink(self._path_to(image_id)) + except IOError: + raise exception.NotFound + + def delete_all(self): + """ + Clears out all images in local directory + """ + for id in self._ids(): + os.unlink(self._path_to(id)) diff --git a/nova/image/s3.py b/nova/image/s3.py new file mode 100644 index 000000000..ba1086aca --- /dev/null +++ b/nova/image/s3.py @@ -0,0 +1,108 @@ +# 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. + +""" +Proxy AMI-related calls from the cloud controller, to the running +objectstore service. +""" + +import json +import urllib + +import boto.s3.connection + +from nova import exception +from nova import flags +from nova import utils +from nova.auth import manager +from nova.image import service + + +FLAGS = flags.FLAGS + + +class S3ImageService(service.BaseImageService): + + def modify(self, context, image_id, operation): + self._conn(context).make_request( + method='POST', + bucket='_images', + query_args=self._qs({'image_id': image_id, 'operation': operation})) + return True + + def update(self, context, image_id, attributes): + """update an image's attributes / info.json""" + attributes.update({"image_id": image_id}) + self._conn(context).make_request( + method='POST', + bucket='_images', + query_args=self._qs(attributes)) + return True + + def register(self, context, image_location): + """ rpc call to register a new image based from a manifest """ + image_id = utils.generate_uid('ami') + self._conn(context).make_request( + method='PUT', + bucket='_images', + query_args=self._qs({'image_location': image_location, + 'image_id': image_id})) + return image_id + + def index(self, context): + """Return a list of all images that a user can see.""" + response = self._conn(context).make_request( + method='GET', + bucket='_images') + return json.loads(response.read()) + + def show(self, context, image_id): + """return a image object if the context has permissions""" + if FLAGS.connection_type == 'fake': + return {'imageId': 'bar'} + result = self.index(context) + result = [i for i in result if i['imageId'] == image_id] + if not result: + raise exception.NotFound('Image %s could not be found' % image_id) + image = result[0] + return image + + def deregister(self, context, image_id): + """ unregister an image """ + self._conn(context).make_request( + method='DELETE', + bucket='_images', + query_args=self._qs({'image_id': image_id})) + + def _conn(self, context): + access = manager.AuthManager().get_access_key(context.user, + context.project) + secret = str(context.user.secret) + calling = boto.s3.connection.OrdinaryCallingFormat() + return boto.s3.connection.S3Connection(aws_access_key_id=access, + aws_secret_access_key=secret, + is_secure=False, + calling_format=calling, + port=FLAGS.s3_port, + host=FLAGS.s3_host) + + def _qs(self, params): + pairs = [] + for key in params.keys(): + pairs.append(key + '=' + urllib.quote(params[key])) + return '&'.join(pairs) diff --git a/nova/image/service.py b/nova/image/service.py index 52ddd4e0f..ebee2228d 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -15,32 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. -import cPickle as pickle -import os.path -import random - -from nova import flags -from nova import exception - -FLAGS = flags.FLAGS - - -flags.DEFINE_string('glance_teller_address', 'http://127.0.0.1', - 'IP address or URL where Glance\'s Teller service resides') -flags.DEFINE_string('glance_teller_port', '9191', - 'Port for Glance\'s Teller service') -flags.DEFINE_string('glance_parallax_address', 'http://127.0.0.1', - 'IP address or URL where Glance\'s Parallax service ' - 'resides') -flags.DEFINE_string('glance_parallax_port', '9292', - 'Port for Glance\'s Parallax service') - class BaseImageService(object): """Base class for providing image search and retrieval services""" - def index(self): + def index(self, context): """ Returns a sequence of mappings of id and name information about images. @@ -52,7 +32,7 @@ class BaseImageService(object): """ raise NotImplementedError - def detail(self): + def detail(self, context): """ Returns a sequence of mappings of detailed information about images. @@ -76,7 +56,7 @@ class BaseImageService(object): """ raise NotImplementedError - def show(self, id): + def show(self, context, id): """ Returns a dict containing image data for the given opaque image id. @@ -96,7 +76,7 @@ class BaseImageService(object): """ raise NotImplementedError - def create(self, data): + def create(self, context, data): """ Store the image data and return the new image id. @@ -105,7 +85,7 @@ class BaseImageService(object): """ raise NotImplementedError - def update(self, image_id, data): + def update(self, context, image_id, data): """Replace the contents of the given image with the new data. :raises NotFound if the image does not exist. @@ -113,7 +93,7 @@ class BaseImageService(object): """ raise NotImplementedError - def delete(self, image_id): + def delete(self, context, image_id): """ Delete the given image. @@ -121,68 +101,3 @@ class BaseImageService(object): """ raise NotImplementedError - - -class LocalImageService(BaseImageService): - - """Image service storing images to local disk. - - It assumes that image_ids are integers.""" - - def __init__(self): - self._path = "/tmp/nova/images" - try: - os.makedirs(self._path) - except OSError: # Exists - pass - - def _path_to(self, image_id): - return os.path.join(self._path, str(image_id)) - - def _ids(self): - """The list of all image ids.""" - return [int(i) for i in os.listdir(self._path)] - - def index(self): - return [dict(id=i['id'], name=i['name']) for i in self.detail()] - - def detail(self): - return [self.show(id) for id in self._ids()] - - def show(self, id): - try: - return pickle.load(open(self._path_to(id))) - except IOError: - raise exception.NotFound - - def create(self, data): - """ - Store the image data and return the new image id. - """ - id = random.randint(0, 2 ** 32 - 1) - data['id'] = id - self.update(id, data) - return id - - def update(self, image_id, data): - """Replace the contents of the given image with the new data.""" - try: - pickle.dump(data, open(self._path_to(image_id), 'w')) - except IOError: - raise exception.NotFound - - def delete(self, image_id): - """ - Delete the given image. Raises OSError if the image does not exist. - """ - try: - os.unlink(self._path_to(image_id)) - except IOError: - raise exception.NotFound - - def delete_all(self): - """ - Clears out all images in local directory - """ - for id in self._ids(): - os.unlink(self._path_to(id)) diff --git a/nova/image/services/__init__.py b/nova/image/services/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/nova/image/services/glance/__init__.py b/nova/image/services/glance/__init__.py deleted file mode 100644 index f1d05f0bc..000000000 --- a/nova/image/services/glance/__init__.py +++ /dev/null @@ -1,216 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Implementation of an image service that uses Glance as the backend""" - -import httplib -import json -import urlparse - -import webob.exc - -from nova import utils -from nova import flags -from nova import exception -import nova.image.service - -FLAGS = flags.FLAGS - - -class TellerClient(object): - - def __init__(self): - self.address = FLAGS.glance_teller_address - self.port = FLAGS.glance_teller_port - url = urlparse.urlparse(self.address) - self.netloc = url.netloc - self.connection_type = {'http': httplib.HTTPConnection, - 'https': httplib.HTTPSConnection}[url.scheme] - - -class ParallaxClient(object): - - def __init__(self): - self.address = FLAGS.glance_parallax_address - self.port = FLAGS.glance_parallax_port - url = urlparse.urlparse(self.address) - self.netloc = url.netloc - self.connection_type = {'http': httplib.HTTPConnection, - 'https': httplib.HTTPSConnection}[url.scheme] - - def get_image_index(self): - """ - Returns a list of image id/name mappings from Parallax - """ - try: - c = self.connection_type(self.netloc, self.port) - c.request("GET", "images") - res = c.getresponse() - if res.status == 200: - # Parallax returns a JSONified dict(images=image_list) - data = json.loads(res.read())['images'] - return data - else: - logging.warn("Parallax returned HTTP error %d from " - "request for /images", res.status_int) - return [] - finally: - c.close() - - def get_image_details(self): - """ - Returns a list of detailed image data mappings from Parallax - """ - try: - c = self.connection_type(self.netloc, self.port) - c.request("GET", "images/detail") - res = c.getresponse() - if res.status == 200: - # Parallax returns a JSONified dict(images=image_list) - data = json.loads(res.read())['images'] - return data - else: - logging.warn("Parallax returned HTTP error %d from " - "request for /images/detail", res.status_int) - return [] - finally: - c.close() - - def get_image_metadata(self, image_id): - """ - Returns a mapping of image metadata from Parallax - """ - try: - c = self.connection_type(self.netloc, self.port) - c.request("GET", "images/%s" % image_id) - res = c.getresponse() - if res.status == 200: - # Parallax returns a JSONified dict(image=image_info) - data = json.loads(res.read())['image'] - return data - else: - # TODO(jaypipes): log the error? - return None - finally: - c.close() - - def add_image_metadata(self, image_metadata): - """ - Tells parallax about an image's metadata - """ - try: - c = self.connection_type(self.netloc, self.port) - body = json.dumps(image_metadata) - c.request("POST", "images", body) - res = c.getresponse() - if res.status == 200: - # Parallax returns a JSONified dict(image=image_info) - data = json.loads(res.read())['image'] - return data['id'] - else: - # TODO(jaypipes): log the error? - return None - finally: - c.close() - - def update_image_metadata(self, image_id, image_metadata): - """ - Updates Parallax's information about an image - """ - try: - c = self.connection_type(self.netloc, self.port) - body = json.dumps(image_metadata) - c.request("PUT", "images/%s" % image_id, body) - res = c.getresponse() - return res.status == 200 - finally: - c.close() - - def delete_image_metadata(self, image_id): - """ - Deletes Parallax's information about an image - """ - try: - c = self.connection_type(self.netloc, self.port) - c.request("DELETE", "images/%s" % image_id) - res = c.getresponse() - return res.status == 200 - finally: - c.close() - - -class GlanceImageService(nova.image.service.BaseImageService): - """Provides storage and retrieval of disk image objects within Glance.""" - - def __init__(self): - self.teller = TellerClient() - self.parallax = ParallaxClient() - - def index(self): - """ - Calls out to Parallax for a list of images available - """ - images = self.parallax.get_image_index() - return images - - def detail(self): - """ - Calls out to Parallax for a list of detailed image information - """ - images = self.parallax.get_image_details() - return images - - def show(self, id): - """ - Returns a dict containing image data for the given opaque image id. - """ - image = self.parallax.get_image_metadata(id) - if image: - return image - raise exception.NotFound - - def create(self, data): - """ - Store the image data and return the new image id. - - :raises AlreadyExists if the image already exist. - - """ - return self.parallax.add_image_metadata(data) - - def update(self, image_id, data): - """Replace the contents of the given image with the new data. - - :raises NotFound if the image does not exist. - - """ - self.parallax.update_image_metadata(image_id, data) - - def delete(self, image_id): - """ - Delete the given image. - - :raises NotFound if the image does not exist. - - """ - self.parallax.delete_image_metadata(image_id) - - def delete_all(self): - """ - Clears out all images - """ - pass diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 52b392601..639a2ebe4 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -29,7 +29,7 @@ from nova import flags from nova import exception as exc import nova.api.openstack.auth from nova.image import service -from nova.image.services import glance +from nova.image import glance from nova.tests import fake_flags from nova.wsgi import Router @@ -76,7 +76,7 @@ def stub_out_image_service(stubs): def fake_image_show(meh, id): return dict(kernelId=1, ramdiskId=1) - stubs.Set(nova.image.service.LocalImageService, 'show', fake_image_show) + stubs.Set(nova.image.local.LocalImageService, 'show', fake_image_show) def stub_out_auth(stubs): @@ -151,21 +151,19 @@ def stub_out_glance(stubs, initial_fixtures=[]): self.fixtures = [] fake_parallax_client = FakeParallaxClient(initial_fixtures) - stubs.Set(nova.image.services.glance.ParallaxClient, 'get_image_index', + stubs.Set(nova.image.glance.ParallaxClient, 'get_image_index', fake_parallax_client.fake_get_image_index) - stubs.Set(nova.image.services.glance.ParallaxClient, 'get_image_details', + stubs.Set(nova.image.glance.ParallaxClient, 'get_image_details', fake_parallax_client.fake_get_image_details) - stubs.Set(nova.image.services.glance.ParallaxClient, 'get_image_metadata', + stubs.Set(nova.image.glance.ParallaxClient, 'get_image_metadata', fake_parallax_client.fake_get_image_metadata) - stubs.Set(nova.image.services.glance.ParallaxClient, 'add_image_metadata', + stubs.Set(nova.image.glance.ParallaxClient, 'add_image_metadata', fake_parallax_client.fake_add_image_metadata) - stubs.Set(nova.image.services.glance.ParallaxClient, - 'update_image_metadata', + stubs.Set(nova.image.glance.ParallaxClient, 'update_image_metadata', fake_parallax_client.fake_update_image_metadata) - stubs.Set(nova.image.services.glance.ParallaxClient, - 'delete_image_metadata', + stubs.Set(nova.image.glance.ParallaxClient, 'delete_image_metadata', fake_parallax_client.fake_delete_image_metadata) - stubs.Set(nova.image.services.glance.GlanceImageService, 'delete_all', + stubs.Set(nova.image.glance.GlanceImageService, 'delete_all', fake_parallax_client.fake_delete_all) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 0f3941c29..207947b3c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -28,6 +28,7 @@ import unittest import stubout import webob +from nova import context from nova import exception from nova import flags from nova import utils @@ -52,12 +53,12 @@ class BaseImageServiceTests(object): 'serverId': None, 'progress': None} - num_images = len(self.service.index()) + num_images = len(self.service.index(self.context)) - id = self.service.create(fixture) + id = self.service.create(self.context, fixture) self.assertNotEquals(None, id) - self.assertEquals(num_images + 1, len(self.service.index())) + self.assertEquals(num_images + 1, len(self.service.index(self.context))) def test_create_and_show_non_existing_image(self): @@ -68,14 +69,15 @@ class BaseImageServiceTests(object): 'serverId': None, 'progress': None} - num_images = len(self.service.index()) + num_images = len(self.service.index(self.context)) - id = self.service.create(fixture) + id = self.service.create(self.context, fixture) self.assertNotEquals(None, id) self.assertRaises(exception.NotFound, self.service.show, + self.context, 'bad image id') def test_update(self): @@ -87,12 +89,12 @@ class BaseImageServiceTests(object): 'serverId': None, 'progress': None} - id = self.service.create(fixture) + id = self.service.create(self.context, fixture) fixture['status'] = 'in progress' - self.service.update(id, fixture) - new_image_data = self.service.show(id) + self.service.update(self.context, id, fixture) + new_image_data = self.service.show(self.context, id) self.assertEquals('in progress', new_image_data['status']) def test_delete(self): @@ -111,20 +113,20 @@ class BaseImageServiceTests(object): 'serverId': None, 'progress': None}] - num_images = len(self.service.index()) - self.assertEquals(0, num_images, str(self.service.index())) + num_images = len(self.service.index(self.context)) + self.assertEquals(0, num_images, str(self.service.index(self.context))) ids = [] for fixture in fixtures: - new_id = self.service.create(fixture) + new_id = self.service.create(self.context, fixture) ids.append(new_id) - num_images = len(self.service.index()) - self.assertEquals(2, num_images, str(self.service.index())) + num_images = len(self.service.index(self.context)) + self.assertEquals(2, num_images, str(self.service.index(self.context))) - self.service.delete(ids[0]) + self.service.delete(self.context, ids[0]) - num_images = len(self.service.index()) + num_images = len(self.service.index(self.context)) self.assertEquals(1, num_images) @@ -135,8 +137,9 @@ class LocalImageServiceTest(unittest.TestCase, def setUp(self): self.stubs = stubout.StubOutForTesting() - service_class = 'nova.image.service.LocalImageService' + service_class = 'nova.image.local.LocalImageService' self.service = utils.import_object(service_class) + self.context = context.RequestContext(None, None) def tearDown(self): self.service.delete_all() @@ -151,8 +154,9 @@ class GlanceImageServiceTest(unittest.TestCase, def setUp(self): self.stubs = stubout.StubOutForTesting() fakes.stub_out_glance(self.stubs) - service_class = 'nova.image.services.glance.GlanceImageService' + service_class = 'nova.image.glance.GlanceImageService' self.service = utils.import_object(service_class) + self.context = context.RequestContext(None, None) self.service.delete_all() def tearDown(self): @@ -187,7 +191,7 @@ class ImageControllerWithGlanceServiceTest(unittest.TestCase): def setUp(self): self.orig_image_service = FLAGS.image_service - FLAGS.image_service = 'nova.image.services.glance.GlanceImageService' + FLAGS.image_service = 'nova.image.glance.GlanceImageService' self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} -- cgit From 9c7ddf24acfdbdb220bcc56d8e4d6421cd46e1d7 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 18 Nov 2010 21:27:00 -0800 Subject: PEP8 fixes, 2 lines were too long. --- nova/image/s3.py | 3 ++- nova/tests/api/openstack/test_images.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/image/s3.py b/nova/image/s3.py index ba1086aca..0a25161de 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -42,7 +42,8 @@ class S3ImageService(service.BaseImageService): self._conn(context).make_request( method='POST', bucket='_images', - query_args=self._qs({'image_id': image_id, 'operation': operation})) + query_args=self._qs({'image_id': image_id, + 'operation': operation})) return True def update(self, context, image_id, attributes): diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 207947b3c..f610cbf9c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -58,7 +58,8 @@ class BaseImageServiceTests(object): id = self.service.create(self.context, fixture) self.assertNotEquals(None, id) - self.assertEquals(num_images + 1, len(self.service.index(self.context))) + self.assertEquals(num_images + 1, + len(self.service.index(self.context))) def test_create_and_show_non_existing_image(self): -- cgit From aa433547ff797678cd2aad17d70c1c0b569d1e37 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Fri, 19 Nov 2010 17:32:38 +0000 Subject: first cut of fixes for bug #676128 --- nova/virt/xenapi.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'nova') diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 0f563aa41..2f115c14b 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -286,11 +286,21 @@ class XenAPIConnection(object): # Don't complain, just return. This lets us clean up instances # that have already disappeared from the underlying platform. defer.returnValue(None) + # Get the VDIs related to the VM + vdis = yield self._lookup_vm_vdis(vm) try: task = yield self._call_xenapi('Async.VM.hard_shutdown', vm) yield self._wait_for_task(task) except Exception, exc: logging.warn(exc) + # Disk clean-up + if vdis: + for vdi in vdis: + try: + task = yield self._call_xenapi('Async.VDI.destroy', vdi) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) try: task = yield self._call_xenapi('Async.VM.destroy', vm) yield self._wait_for_task(task) @@ -325,6 +335,24 @@ class XenAPIConnection(object): else: return vms[0] + @utils.deferredToThread + def _lookup_vm_vdis(self, vm): + return self._lookup_vm_vdis_blocking(vm) + + def _lookup_vm_vdis_blocking(self, vm): + # Firstly we get the VBDs, then the VDIs. + # TODO: do we leave the read-only devices? + vbds = self._conn.xenapi.VM.get_VBDs(vm) + vdis = [] + if vbds: + for vbd in vbds: + vdis.append(self._conn.xenapi.VBD.get_VDI(vbd)) + if len(vdis) > 0: + return vdis + else: + return None + + def _wait_for_task(self, task): """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" -- cgit From 300e6e3517d0e50c7fd6775eff10f1b0d677f25a Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 19 Nov 2010 15:47:24 -0600 Subject: Check for running AMQP instances --- nova/rpc.py | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index ea36d69f4..0c79c7f76 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -24,6 +24,7 @@ No fan-out support yet. import json import logging import sys +import time import uuid from carrot import connection as carrot_connection @@ -82,31 +83,35 @@ class Consumer(messaging.Consumer): Contains methods for connecting the fetch method to async loops """ def __init__(self, *args, **kwargs): - self.failed_connection = False - super(Consumer, self).__init__(*args, **kwargs) + while True: + try: + super(Consumer, self).__init__(*args, **kwargs) + break + except: + logging.warning("AMQP server on %s:%d is unreachable. " \ + "Trying again in 30 seconds." % ( + FLAGS.rabbit_host, + FLAGS.rabbit_port + )) + time.sleep(30) + continue def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): """Wraps the parent fetch with some logic for failed connections""" - # TODO(vish): the logic for failed connections and logging should be - # refactored into some sort of connection manager object try: - if self.failed_connection: - # NOTE(vish): conn is defined in the parent class, we can - # recreate it as long as we create the backend too - # pylint: disable-msg=W0201 - self.conn = Connection.recreate() - self.backend = self.conn.create_backend() super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) - if self.failed_connection: - logging.error("Reconnected to queue") - self.failed_connection = False - # NOTE(vish): This is catching all errors because we really don't - # exceptions to be logged 10 times a second if some - # persistent failure occurs. - except Exception: # pylint: disable-msg=W0703 - if not self.failed_connection: - logging.exception("Failed to fetch message from queue") - self.failed_connection = True + except: + try: + self.connection = Connection.recreate() + self.backend = self.connection.create_backend() + self.declare() + except: + logging.warning("AMQP server on %s:%d is unreachable. " \ + "Trying again in 30 seconds." % ( + FLAGS.rabbit_host, + FLAGS.rabbit_port + )) + time.sleep(30) def attach_to_eventlet(self): """Only needed for unit tests!""" -- cgit From 281008b1ce3f92507622d856e0a310690ea37ab3 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 19 Nov 2010 16:01:55 -0600 Subject: Added some comments --- nova/rpc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index 0c79c7f76..d9097afaa 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -100,12 +100,12 @@ class Consumer(messaging.Consumer): """Wraps the parent fetch with some logic for failed connections""" try: super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) - except: + except: # Catching all because carrot sucks try: self.connection = Connection.recreate() self.backend = self.connection.create_backend() self.declare() - except: + except: # Catching all because carrot sucks logging.warning("AMQP server on %s:%d is unreachable. " \ "Trying again in 30 seconds." % ( FLAGS.rabbit_host, -- cgit From aa9fe71ed82889f1f3ca306c5836414966d08539 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 19 Nov 2010 16:13:04 -0600 Subject: Added some comments --- nova/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index d9097afaa..cf3889a47 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -87,7 +87,7 @@ class Consumer(messaging.Consumer): try: super(Consumer, self).__init__(*args, **kwargs) break - except: + except: # Catching all because carrot sucks logging.warning("AMQP server on %s:%d is unreachable. " \ "Trying again in 30 seconds." % ( FLAGS.rabbit_host, -- cgit From dec8aab1001d85ff7845f7521faf1f610700bd09 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 19 Nov 2010 16:42:37 -0600 Subject: Reverted some changes --- nova/rpc.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index cf3889a47..c7ff9ce6b 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -83,35 +83,43 @@ class Consumer(messaging.Consumer): Contains methods for connecting the fetch method to async loops """ def __init__(self, *args, **kwargs): + self.failed_connection = False + while True: try: super(Consumer, self).__init__(*args, **kwargs) break - except: # Catching all because carrot sucks + except: # Catching all because carrot sucks logging.warning("AMQP server on %s:%d is unreachable. " \ "Trying again in 30 seconds." % ( - FLAGS.rabbit_host, - FLAGS.rabbit_port - )) + FLAGS.rabbit_host, + FLAGS.rabbit_port)) time.sleep(30) continue def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): """Wraps the parent fetch with some logic for failed connections""" + # TODO(vish): the logic for failed connections and logging should be + # refactored into some sort of connection manager object try: - super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) - except: # Catching all because carrot sucks - try: + if self.failed_connection: + # NOTE(vish): conn is defined in the parent class, we can + # recreate it as long as we create the backend too + # pylint: disable-msg=W0201 self.connection = Connection.recreate() self.backend = self.connection.create_backend() self.declare() - except: # Catching all because carrot sucks - logging.warning("AMQP server on %s:%d is unreachable. " \ - "Trying again in 30 seconds." % ( - FLAGS.rabbit_host, - FLAGS.rabbit_port - )) - time.sleep(30) + super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) + if self.failed_connection: + logging.error("Reconnected to queue") + self.failed_connection = False + # NOTE(vish): This is catching all errors because we really don't + # exceptions to be logged 10 times a second if some + # persistent failure occurs. + except Exception: # pylint: disable-msg=W0703 + if not self.failed_connection: + logging.exception("Failed to fetch message from queue") + self.failed_connection = True def attach_to_eventlet(self): """Only needed for unit tests!""" -- cgit From 874edc5103e1ebbe1def1639ef056574dcd406e9 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Sat, 20 Nov 2010 12:16:54 -0600 Subject: Use logging.exception instead --- nova/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index c7ff9ce6b..9938b0838 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -90,7 +90,7 @@ class Consumer(messaging.Consumer): super(Consumer, self).__init__(*args, **kwargs) break except: # Catching all because carrot sucks - logging.warning("AMQP server on %s:%d is unreachable. " \ + logging.exception("AMQP server on %s:%d is unreachable. " \ "Trying again in 30 seconds." % ( FLAGS.rabbit_host, FLAGS.rabbit_port)) -- cgit From 958591ab2996443ffb6d2f92f928eaad277aa2db Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 22 Nov 2010 13:11:00 +0000 Subject: pep8 violations fix --- nova/virt/xenapi.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index c3a0c2c7a..3169562a5 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -338,28 +338,27 @@ class XenAPIConnection(object): @utils.deferredToThread def _lookup_vm_vdis(self, vm): return self._lookup_vm_vdis_blocking(vm) - + def _lookup_vm_vdis_blocking(self, vm): # Firstly we get the VBDs, then the VDIs. # TODO: do we leave the read-only devices? vbds = self._conn.xenapi.VM.get_VBDs(vm) vdis = [] if vbds: - for vbd in vbds: - try: - vdi = self._conn.xenapi.VBD.get_VDI(vbd) - # Test valid VDI - record = self._conn.xenapi.VDI.get_record(vdi) - except Exception, exc: - logging.warn(exc) - else: - vdis.append(vdi) - if len(vdis) > 0: - return vdis - else: - return None - - + for vbd in vbds: + try: + vdi = self._conn.xenapi.VBD.get_VDI(vbd) + # Test valid VDI + record = self._conn.xenapi.VDI.get_record(vdi) + except Exception, exc: + logging.warn(exc) + else: + vdis.append(vdi) + if len(vdis) > 0: + return vdis + else: + return None + def _wait_for_task(self, task): """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" -- cgit From 51be7159574d3e0cba8a81b8ea3e9706ce74ac3a Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 22 Nov 2010 14:45:05 -0600 Subject: Set and use AMQP retry interval and max retry constants --- nova/rpc.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index 9938b0838..c2d18cb61 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -38,8 +38,11 @@ from nova import fakerabbit from nova import flags from nova import context + FLAGS = flags.FLAGS +AMQP_RETRY_INT = 10 +AMQP_MAX_RETRIES = 12 LOG = logging.getLogger('amqplib') LOG.setLevel(logging.DEBUG) @@ -85,17 +88,23 @@ class Consumer(messaging.Consumer): def __init__(self, *args, **kwargs): self.failed_connection = False - while True: + for i in range(AMQP_MAX_RETRIES): try: super(Consumer, self).__init__(*args, **kwargs) break except: # Catching all because carrot sucks - logging.exception("AMQP server on %s:%d is unreachable. " \ - "Trying again in 30 seconds." % ( - FLAGS.rabbit_host, - FLAGS.rabbit_port)) - time.sleep(30) - continue + if i + 1 == AMQP_MAX_RETRIES: + logging.exception("Unable to connect to AMQP server" \ + " after %d tries. Shutting down." % AMQP_MAX_RETRIES) + sys.exit(1) + else: + logging.exception("AMQP server on %s:%d is unreachable." \ + " Trying again in %d seconds." % ( + FLAGS.rabbit_host, + FLAGS.rabbit_port, + AMQP_RETRY_INT)) + time.sleep(AMQP_RETRY_INT) + continue def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): """Wraps the parent fetch with some logic for failed connections""" @@ -103,7 +112,7 @@ class Consumer(messaging.Consumer): # refactored into some sort of connection manager object try: if self.failed_connection: - # NOTE(vish): conn is defined in the parent class, we can + # NOTE(vish): connection is defined in the parent class, we can # recreate it as long as we create the backend too # pylint: disable-msg=W0201 self.connection = Connection.recreate() -- cgit From 14e4ba7f0e10fc3c2f532b445c1f656f53c8aa95 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 22 Nov 2010 15:22:02 -0600 Subject: Refactor AMQP retry loop --- nova/rpc.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index c2d18cb61..961b56de6 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -86,25 +86,25 @@ class Consumer(messaging.Consumer): Contains methods for connecting the fetch method to async loops """ def __init__(self, *args, **kwargs): - self.failed_connection = False - - for i in range(AMQP_MAX_RETRIES): + for i in xrange(AMQP_MAX_RETRIES): + if i > 0: + time.sleep(AMQP_RETRY_INT) try: super(Consumer, self).__init__(*args, **kwargs) + self.failed_connection = False break except: # Catching all because carrot sucks - if i + 1 == AMQP_MAX_RETRIES: - logging.exception("Unable to connect to AMQP server" \ - " after %d tries. Shutting down." % AMQP_MAX_RETRIES) - sys.exit(1) - else: - logging.exception("AMQP server on %s:%d is unreachable." \ - " Trying again in %d seconds." % ( - FLAGS.rabbit_host, - FLAGS.rabbit_port, - AMQP_RETRY_INT)) - time.sleep(AMQP_RETRY_INT) - continue + logging.exception("AMQP server on %s:%d is unreachable." \ + " Trying again in %d seconds." % ( + FLAGS.rabbit_host, + FLAGS.rabbit_port, + AMQP_RETRY_INT)) + self.failed_connection = True + continue + if self.failed_connection: + logging.exception("Unable to connect to AMQP server" \ + " after %d tries. Shutting down." % AMQP_MAX_RETRIES) + sys.exit(1) def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): """Wraps the parent fetch with some logic for failed connections""" -- cgit From a8497abaf24436a92a85129d9771a12f046f2f42 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 22 Nov 2010 16:04:21 -0600 Subject: Removed unnecessary continue --- nova/rpc.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index 961b56de6..57e522ad3 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -100,7 +100,6 @@ class Consumer(messaging.Consumer): FLAGS.rabbit_port, AMQP_RETRY_INT)) self.failed_connection = True - continue if self.failed_connection: logging.exception("Unable to connect to AMQP server" \ " after %d tries. Shutting down." % AMQP_MAX_RETRIES) -- cgit From deac609ceb1cd6e081445bfc4d8f8c3222b97774 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 22 Nov 2010 16:28:28 -0600 Subject: Make time.sleep() non-blocking --- nova/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index b04b487ea..c7ee9ed14 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -28,7 +28,7 @@ from xml.dom import minidom import eventlet import eventlet.wsgi -eventlet.patcher.monkey_patch(all=False, socket=True) +eventlet.patcher.monkey_patch(all=False, socket=True, time=True) import routes import routes.middleware import webob -- cgit From f0f990495428c028401ba9a4740e6b7a0441213c Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 22 Nov 2010 16:48:44 -0600 Subject: Use FLAGS instead of constants --- nova/flags.py | 2 ++ nova/rpc.py | 11 ++++------- 2 files changed, 6 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 4ae86d9b2..a39f22273 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -196,6 +196,8 @@ DEFINE_integer('rabbit_port', 5672, 'rabbit port') DEFINE_string('rabbit_userid', 'guest', 'rabbit userid') DEFINE_string('rabbit_password', 'guest', 'rabbit password') DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') +DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') +DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('cc_host', '127.0.0.1', 'ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') diff --git a/nova/rpc.py b/nova/rpc.py index 57e522ad3..86a29574f 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -41,9 +41,6 @@ from nova import context FLAGS = flags.FLAGS -AMQP_RETRY_INT = 10 -AMQP_MAX_RETRIES = 12 - LOG = logging.getLogger('amqplib') LOG.setLevel(logging.DEBUG) @@ -86,9 +83,9 @@ class Consumer(messaging.Consumer): Contains methods for connecting the fetch method to async loops """ def __init__(self, *args, **kwargs): - for i in xrange(AMQP_MAX_RETRIES): + for i in xrange(FLAGS.rabbit_max_retries): if i > 0: - time.sleep(AMQP_RETRY_INT) + time.sleep(FLAGS.rabbit_retry_interval) try: super(Consumer, self).__init__(*args, **kwargs) self.failed_connection = False @@ -98,11 +95,11 @@ class Consumer(messaging.Consumer): " Trying again in %d seconds." % ( FLAGS.rabbit_host, FLAGS.rabbit_port, - AMQP_RETRY_INT)) + FLAGS.rabbit_retry_interval)) self.failed_connection = True if self.failed_connection: logging.exception("Unable to connect to AMQP server" \ - " after %d tries. Shutting down." % AMQP_MAX_RETRIES) + " after %d tries. Shutting down." % FLAGS.rabbit_max_retries) sys.exit(1) def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): -- cgit From 89f56207de1ffe2f1f9d5c3cad3ab71ba324d133 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 23 Nov 2010 13:48:57 +0100 Subject: Add a --logdir flag that will be prepended to the logfile setting. This makes it easier to share a flagfile between multiple workers while still having separate log files. --- nova/server.py | 3 +++ nova/twistd.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/server.py b/nova/server.py index cb424caa1..4d0f6e4da 100644 --- a/nova/server.py +++ b/nova/server.py @@ -42,6 +42,7 @@ flags.DEFINE_bool('daemonize', False, 'daemonize this process') # clutter. flags.DEFINE_bool('use_syslog', True, 'output to syslog when daemonizing') flags.DEFINE_string('logfile', None, 'log file to output to') +flags.DEFINE_string('logdir', None, 'directory to keep log files in (will be prepended to $logfile)') flags.DEFINE_string('pidfile', None, 'pid file to output to') flags.DEFINE_string('working_directory', './', 'working directory...') flags.DEFINE_integer('uid', os.getuid(), 'uid under which to run') @@ -119,6 +120,8 @@ def daemonize(args, name, main): else: if not FLAGS.logfile: FLAGS.logfile = '%s.log' % name + if FLAGS.logdir: + FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile) logfile = logging.FileHandler(FLAGS.logfile) logfile.setFormatter(formatter) logger.addHandler(logfile) diff --git a/nova/twistd.py b/nova/twistd.py index 3ec0ff61e..6c6cb955f 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -43,7 +43,7 @@ else: FLAGS = flags.FLAGS - +flags.DEFINE_string('logdir', None, 'directory to keep log files in (will be prepended to $logfile)') class TwistdServerOptions(ServerOptions): def parseArgs(self, *args): @@ -246,6 +246,8 @@ def serve(filename): FLAGS.logfile = '%s.log' % name elif FLAGS.logfile.endswith('twistd.log'): FLAGS.logfile = FLAGS.logfile.replace('twistd.log', '%s.log' % name) + if FLAGS.logdir: + FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile) if not FLAGS.prefix: FLAGS.prefix = name elif FLAGS.prefix.endswith('twisted'): -- cgit From 693624831066af08dcf488d1528b017048fbde71 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Nov 2010 17:56:43 +0000 Subject: changed bridge_dev to vlan_interface --- nova/network/linux_net.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 68037ed9a..d39ed9f86 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -42,8 +42,8 @@ flags.DEFINE_string('networks_path', utils.abspath('../networks'), 'Location to keep network config files') flags.DEFINE_string('public_interface', 'vlan1', 'Interface for public IP addresses') -flags.DEFINE_string('bridge_dev', None, - 'network device for bridges') +flags.DEFINE_string('vlan_interface', 'eth0', + 'network device for vlans') flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'), 'location of nova-dhcpbridge') flags.DEFINE_string('routing_source_ip', '127.0.0.1', @@ -134,7 +134,7 @@ def ensure_vlan(vlan_num): if not _device_exists(interface): logging.debug("Starting VLAN inteface %s", interface) _execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD") - _execute("sudo vconfig add %s %s" % (FLAGS.bridge_dev, vlan_num)) + _execute("sudo vconfig add %s %s" % (FLAGS.vlan_interface, vlan_num)) _execute("sudo ifconfig %s up" % interface) return interface -- cgit From 6811f824f7c1edd1b3882621d80fba54a2bf019d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Nov 2010 17:57:12 +0000 Subject: added flat_interface for flat_dhcp binding --- nova/network/manager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 96f8cf50b..8f13a8230 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -61,6 +61,8 @@ from nova import utils FLAGS = flags.FLAGS flags.DEFINE_string('flat_network_bridge', 'br100', 'Bridge for simple network instances') +flags.DEFINE_string('flat_interface', None, + 'flat_dhcp will bridge into this interface if set') flags.DEFINE_string('flat_network_dns', '8.8.4.4', 'Dns for simple network') flags.DEFINE_string('flat_network_dhcp_start', '10.0.0.2', @@ -319,7 +321,7 @@ class FlatDHCPManager(FlatManager): """Sets up matching network for compute hosts.""" network_ref = db.network_get_by_instance(context, instance_id) self.driver.ensure_bridge(network_ref['bridge'], - FLAGS.bridge_dev, + FLAGS.flat_interface, network_ref) def setup_fixed_ip(self, context, address): @@ -338,7 +340,7 @@ class FlatDHCPManager(FlatManager): self.db.network_update(context, network_id, net) network_ref = db.network_get(context, network_id) self.driver.ensure_bridge(network_ref['bridge'], - FLAGS.bridge_dev, + FLAGS.flat_interface, network_ref) -- cgit From 513e4eb76a8d21108484bbc08e3ff755190cb2d9 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 23 Nov 2010 12:04:34 -0600 Subject: Make aws_access_key_id and aws_secret_access_key configurable --- nova/adminclient.py | 16 +++++++++++----- nova/compute/monitor.py | 4 ++-- nova/flags.py | 2 ++ 3 files changed, 15 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/adminclient.py b/nova/adminclient.py index af55197fc..5a62cce7d 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -22,13 +22,15 @@ Nova User API client library. import base64 import boto import httplib + +from nova import flags from boto.ec2.regioninfo import RegionInfo +FLAGS = flags.FLAGS + DEFAULT_CLC_URL = 'http://127.0.0.1:8773' DEFAULT_REGION = 'nova' -DEFAULT_ACCESS_KEY = 'admin' -DEFAULT_SECRET_KEY = 'admin' class UserInfo(object): @@ -192,9 +194,13 @@ class HostInfo(object): class NovaAdminClient(object): - def __init__(self, clc_url=DEFAULT_CLC_URL, region=DEFAULT_REGION, - access_key=DEFAULT_ACCESS_KEY, secret_key=DEFAULT_SECRET_KEY, - **kwargs): + def __init__( + self, + clc_url=DEFAULT_CLC_URL, + region=DEFAULT_REGION, + access_key=FLAGS.aws_access_key_id, + secret_key=FLAGS.aws_secret_access_key, + **kwargs): parts = self.split_clc_url(clc_url) self.clc_url = clc_url diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py index d0154600f..ce45b14f6 100644 --- a/nova/compute/monitor.py +++ b/nova/compute/monitor.py @@ -211,8 +211,8 @@ def store_graph(instance_id, filename): # the response we can make our own client that does the actual # request and hands it off to the response parser. s3 = boto.s3.connection.S3Connection( - aws_access_key_id='admin', - aws_secret_access_key='admin', + aws_access_key_id=FLAGS.aws_access_key_id, + aws_secret_access_key=FLAGS.aws_secret_access_key, is_secure=False, calling_format=boto.s3.connection.OrdinaryCallingFormat(), port=FLAGS.s3_port, diff --git a/nova/flags.py b/nova/flags.py index 121b9ca25..f7ae26050 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -179,6 +179,8 @@ DEFINE_list('region_list', [], 'list of region=url pairs separated by commas') DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') +DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') +DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '127.0.0.1', 's3 host') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') -- cgit From 9c57e5ce37c1f2405fcf7a1ba322946e6d84efeb Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 23 Nov 2010 12:46:07 -0600 Subject: Remove FAKE_subdomain from docs --- nova/api/__init__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 7e75445a8..80f9f2109 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -22,7 +22,6 @@ Root WSGI middleware for all API controllers. :osapi_subdomain: subdomain running the OpenStack API (default: api) :ec2api_subdomain: subdomain running the EC2 API (default: ec2) -:FAKE_subdomain: set to 'api' or 'ec2', requests default to that endpoint """ -- cgit From edd7e3ed3bee6c11156569ab13b4eb5b3a1f7152 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 23 Nov 2010 21:52:00 +0100 Subject: Address PEP8 complaints. --- nova/server.py | 3 ++- nova/twistd.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/server.py b/nova/server.py index 4d0f6e4da..a0ee54681 100644 --- a/nova/server.py +++ b/nova/server.py @@ -42,7 +42,8 @@ flags.DEFINE_bool('daemonize', False, 'daemonize this process') # clutter. flags.DEFINE_bool('use_syslog', True, 'output to syslog when daemonizing') flags.DEFINE_string('logfile', None, 'log file to output to') -flags.DEFINE_string('logdir', None, 'directory to keep log files in (will be prepended to $logfile)') +flags.DEFINE_string('logdir', None, 'directory to keep log files in ' + '(will be prepended to $logfile)') flags.DEFINE_string('pidfile', None, 'pid file to output to') flags.DEFINE_string('working_directory', './', 'working directory...') flags.DEFINE_integer('uid', os.getuid(), 'uid under which to run') diff --git a/nova/twistd.py b/nova/twistd.py index 6c6cb955f..cb5648ce6 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -43,7 +43,9 @@ else: FLAGS = flags.FLAGS -flags.DEFINE_string('logdir', None, 'directory to keep log files in (will be prepended to $logfile)') +flags.DEFINE_string('logdir', None, 'directory to keep log files in ' + '(will be prepended to $logfile)') + class TwistdServerOptions(ServerOptions): def parseArgs(self, *args): -- cgit From 3df7b85265b123080387f1a844e067026410a9bc Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 23 Nov 2010 21:58:46 +0100 Subject: Address pep8 complaints. --- nova/utils.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/utils.py b/nova/utils.py index 820d5bb8a..142584df8 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -174,6 +174,7 @@ def isotime(at=None): def parse_isotime(timestr): return datetime.datetime.strptime(timestr, TIME_FORMAT) + def parse_mailmap(mailmap='.mailmap'): mapping = {} if os.path.exists(mailmap): @@ -185,6 +186,7 @@ def parse_mailmap(mailmap='.mailmap'): mapping[alias] = canonical_email return mapping + def str_dict_replace(s, mapping): for s1, s2 in mapping.iteritems(): s = s.replace(s1, s2) -- cgit From 84521218b84d2eed307364c9efc9f6f2ee212aac Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Nov 2010 23:18:02 +0000 Subject: docstrings, more flags, breakout of metadata forwarding --- nova/network/linux_net.py | 10 +++---- nova/network/manager.py | 67 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index d39ed9f86..b30ddb667 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -53,15 +53,15 @@ flags.DEFINE_bool('use_nova_chains', False, DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)] - -def init_host(): - """Basic networking setup goes here""" - # NOTE(devcamcar): Cloud public DNAT entries, CloudPipe port - # forwarding entries and a default DNAT entry. +def metadata_forward(): + """Create forwarding rule for metadata""" _confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 " "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT " "--to-destination %s:%s" % (FLAGS.cc_host, FLAGS.cc_port)) + +def init_host(): + """Basic networking setup goes here""" # NOTE(devcamcar): Cloud public SNAT entries and the default # SNAT rule for outbound traffic. _confirm_rule("POSTROUTING", "-t nat -s %s " diff --git a/nova/network/manager.py b/nova/network/manager.py index 8f13a8230..f9489d2ad 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -27,6 +27,7 @@ topologies. All of the network commands are issued to a subclass of :network_driver: Driver to use for network creation :flat_network_bridge: Bridge device for simple network instances +:flat_interface: FlatDhcp will bridge into this interface if set :flat_network_dns: Dns for simple network :flat_network_dhcp_start: Dhcp start for FlatDhcp :vlan_start: First VLAN for private networks @@ -61,10 +62,12 @@ from nova import utils FLAGS = flags.FLAGS flags.DEFINE_string('flat_network_bridge', 'br100', 'Bridge for simple network instances') -flags.DEFINE_string('flat_interface', None, - 'flat_dhcp will bridge into this interface if set') flags.DEFINE_string('flat_network_dns', '8.8.4.4', 'Dns for simple network') +flags.DEFINE_bool('flat_injected', True, + 'Whether to attempt to inject network setup into guest') +flags.DEFINE_string('flat_interface', None, + 'FlatDhcp will bridge into this interface if set') flags.DEFINE_string('flat_network_dhcp_start', '10.0.0.2', 'Dhcp start for FlatDhcp') flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks') @@ -177,9 +180,11 @@ class NetworkManager(manager.Manager): if instance_ref['mac_address'] != mac: raise exception.Error("IP %s leased to bad mac %s vs %s" % (address, instance_ref['mac_address'], mac)) + now = datetime.datetime.utcnow() self.db.fixed_ip_update(context, fixed_ip_ref['address'], - {'leased': True}) + {'leased': True, + 'updated_at': now}) if not fixed_ip_ref['allocated']: logging.warn("IP %s leased that was already deallocated", address) @@ -248,7 +253,31 @@ class NetworkManager(manager.Manager): class FlatManager(NetworkManager): - """Basic network where no vlans are used.""" + """Basic network where no vlans are used. + + FlatManager does not do any bridge or vlan creation. The user is + responsible for setting up whatever bridge is specified in + flat_network_bridge (br100 by default). This bridge needs to be created + on all compute hosts. + + The idea is to create a single network for the host with a command like: + nova-manage network create 192.168.0.0/24 256 1. Creating multiple + networks for for one manager is currently not supported, but could be + added by modifying allocate_fixed_ip and get_network to get the a network + with new logic instead of network_get_by_bridge. Arbitrary lists of + addresses in a single network can be accomplished with manual db editing. + + If flat_injected is True, the compute host will attempt to inject network + config into the guest. It attempts to modify /etc/network/interfaces and + currently only works on debian based systems. To support a wider range of + OSes, some other method may need to be devised to let the guest know which + ip it should be using so that it can configure itself. Perhaps an attached + disk or serial device with configuration info. + + Metadata forwarding must be handled by the gateway, and since nova does + not do any setup in this mode, it must be done manually. Requests to + 169.254.169.254 port 80 will need to be forwarded to the api server. + """ def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): """Gets a fixed ip from the pool.""" @@ -309,13 +338,25 @@ class FlatManager(NetworkManager): def _on_set_network_host(self, context, network_id): """Called when this host becomes the host for a network.""" net = {} - net['injected'] = True + net['injected'] = FLAGS.flat_injected net['dns'] = FLAGS.flat_network_dns self.db.network_update(context, network_id, net) class FlatDHCPManager(FlatManager): - """Flat networking with dhcp.""" + """Flat networking with dhcp. + + FlatDHCPManager will start up one dhcp server to give out addresses. + It never injects network settings into the guest. Otherwise it behaves + like FlatDHCPManager. + """ + + def init_host(self): + """Do any initialization that needs to be run if this is a + standalone service. + """ + super(FlatDHCPManager, self).init_host() + self.driver.metadata_forward() def setup_compute_network(self, context, instance_id): """Sets up matching network for compute hosts.""" @@ -345,7 +386,18 @@ class FlatDHCPManager(FlatManager): class VlanManager(NetworkManager): - """Vlan network with dhcp.""" + """Vlan network with dhcp. + + VlanManager is the most complicated. It will create a host-managed + vlan for each project. Each project gets its own subnet. The networks + and associated subnets are created with nova-manage using a command like: + nova-manage network create 10.0.0.0/8 16 3. This will create 3 networks + of 16 addresses from the beginning of the 10.0.0.0 range. + + A dhcp server is run for each subnet, so each project will have its own. + For this mode to be useful, each project will need a vpn to access the + instances in its subnet. + """ @defer.inlineCallbacks def periodic_tasks(self, context=None): @@ -365,6 +417,7 @@ class VlanManager(NetworkManager): standalone service. """ super(VlanManager, self).init_host() + self.driver.metadata_forward() self.driver.init_host() def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): -- cgit From 521dd52e49feeae04108f3e21480f42456b4e4c7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Nov 2010 23:56:26 +0000 Subject: fix typos in docstring --- nova/network/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index f9489d2ad..a7298b47f 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -261,7 +261,7 @@ class FlatManager(NetworkManager): on all compute hosts. The idea is to create a single network for the host with a command like: - nova-manage network create 192.168.0.0/24 256 1. Creating multiple + nova-manage network create 192.168.0.0/24 1 256. Creating multiple networks for for one manager is currently not supported, but could be added by modifying allocate_fixed_ip and get_network to get the a network with new logic instead of network_get_by_bridge. Arbitrary lists of @@ -391,7 +391,7 @@ class VlanManager(NetworkManager): VlanManager is the most complicated. It will create a host-managed vlan for each project. Each project gets its own subnet. The networks and associated subnets are created with nova-manage using a command like: - nova-manage network create 10.0.0.0/8 16 3. This will create 3 networks + nova-manage network create 10.0.0.0/8 3 16. This will create 3 networks of 16 addresses from the beginning of the 10.0.0.0 range. A dhcp server is run for each subnet, so each project will have its own. -- cgit From d62d3f7bcf06802662f77f8013c9da99eccec0a7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 24 Nov 2010 00:16:47 +0000 Subject: pep8 --- nova/network/linux_net.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 7dbf6b733..391abfb76 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -53,6 +53,7 @@ flags.DEFINE_bool('use_nova_chains', False, DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)] + def metadata_forward(): """Create forwarding rule for metadata""" _confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 " -- cgit