diff --git a/config_samples/config/host1/filesystem b/config_samples/config/host1/filesystem new file mode 100644 index 0000000..09b33e5 --- /dev/null +++ b/config_samples/config/host1/filesystem @@ -0,0 +1,20 @@ +# type | source filesystem | reference file | owner | group | permissions + +dir|/etc/cinder|cinder|cinder|0750 +file|/etc/cinder/cinder.conf|etc/cinder/cinder.conf|cinder|cinder|0640 +file|/etc/cinder/logging.conf|etc/cinder/logging.conf|cinder|cinder|0640 +file|/etc/cinder/api-paste.ini|etc/cinder/api-paste.ini|cinder|cinder|0640 +file|/etc/cinder/policy.json|etc/cinder/policy.json|cinder|cinder|0640 +file|/etc/cinder/rootwrap.conf|etc/cinder/rootwrap.conf|cinder|cinder|0640 + +dir|/etc/glance|glance|glance|0750 +file|/etc/glance/glance-cache.conf|etc/glance/glance-cache.conf|glance|glance|0640 +file|/etc/glance/glance-api.conf|etc/glance/glance-api.conf|glance|glance|0640 +file|/etc/glance/glance-api-paste.ini|etc/glance/glance-api-paste.ini|glance|glance|0640 +file|/etc/glance/logging.conf|etc/glance/logging.conf|glance|glance|0640 +file|/etc/glance/glance-registry.conf|etc/glance/glance-registry.conf|glance|glance|0640 +file|/etc/glance/policy.json|etc/glance/policy.json|glance|glance|0640 +file|/etc/glance/schema-image.json|etc/glance/schema-image.json|glance|glance|0640 +file|/etc/glance/glance-scrubber.conf|etc/glance/glance-scrubber.conf|glance|glance|0640 +file|/etc/glance/glance-registry-paste.ini|etc/glance/glance-registry-paste.ini|glance|glance|0640 + diff --git a/config_samples/config/host1/cinder/api-paste.ini b/config_samples/config/host1/root/etc/cinder/api-paste.ini similarity index 100% rename from config_samples/config/host1/cinder/api-paste.ini rename to config_samples/config/host1/root/etc/cinder/api-paste.ini diff --git a/config_samples/config/host1/cinder/cinder.conf b/config_samples/config/host1/root/etc/cinder/cinder.conf similarity index 100% rename from config_samples/config/host1/cinder/cinder.conf rename to config_samples/config/host1/root/etc/cinder/cinder.conf diff --git a/config_samples/config/host1/cinder/logging.conf b/config_samples/config/host1/root/etc/cinder/logging.conf similarity index 100% rename from config_samples/config/host1/cinder/logging.conf rename to config_samples/config/host1/root/etc/cinder/logging.conf diff --git a/config_samples/config/host1/cinder/policy.json b/config_samples/config/host1/root/etc/cinder/policy.json similarity index 100% rename from config_samples/config/host1/cinder/policy.json rename to config_samples/config/host1/root/etc/cinder/policy.json diff --git a/config_samples/config/host1/cinder/rootwrap.conf b/config_samples/config/host1/root/etc/cinder/rootwrap.conf similarity index 100% rename from config_samples/config/host1/cinder/rootwrap.conf rename to config_samples/config/host1/root/etc/cinder/rootwrap.conf diff --git a/config_samples/config/host1/glance/glance-api-paste.ini b/config_samples/config/host1/root/etc/glance/glance-api-paste.ini similarity index 100% rename from config_samples/config/host1/glance/glance-api-paste.ini rename to config_samples/config/host1/root/etc/glance/glance-api-paste.ini diff --git a/config_samples/config/host1/glance/glance-api.conf b/config_samples/config/host1/root/etc/glance/glance-api.conf similarity index 100% rename from config_samples/config/host1/glance/glance-api.conf rename to config_samples/config/host1/root/etc/glance/glance-api.conf diff --git a/config_samples/config/host1/glance/glance-cache.conf b/config_samples/config/host1/root/etc/glance/glance-cache.conf similarity index 100% rename from config_samples/config/host1/glance/glance-cache.conf rename to config_samples/config/host1/root/etc/glance/glance-cache.conf diff --git a/config_samples/config/host1/glance/glance-registry-paste.ini b/config_samples/config/host1/root/etc/glance/glance-registry-paste.ini similarity index 100% rename from config_samples/config/host1/glance/glance-registry-paste.ini rename to config_samples/config/host1/root/etc/glance/glance-registry-paste.ini diff --git a/config_samples/config/host1/glance/glance-registry.conf b/config_samples/config/host1/root/etc/glance/glance-registry.conf similarity index 100% rename from config_samples/config/host1/glance/glance-registry.conf rename to config_samples/config/host1/root/etc/glance/glance-registry.conf diff --git a/config_samples/config/host1/glance/glance-scrubber.conf b/config_samples/config/host1/root/etc/glance/glance-scrubber.conf similarity index 100% rename from config_samples/config/host1/glance/glance-scrubber.conf rename to config_samples/config/host1/root/etc/glance/glance-scrubber.conf diff --git a/config_samples/config/host1/glance/logging.conf b/config_samples/config/host1/root/etc/glance/logging.conf similarity index 100% rename from config_samples/config/host1/glance/logging.conf rename to config_samples/config/host1/root/etc/glance/logging.conf diff --git a/config_samples/config/host1/glance/policy.json b/config_samples/config/host1/root/etc/glance/policy.json similarity index 100% rename from config_samples/config/host1/glance/policy.json rename to config_samples/config/host1/root/etc/glance/policy.json diff --git a/config_samples/config/host1/glance/schema-image.json b/config_samples/config/host1/root/etc/glance/schema-image.json similarity index 100% rename from config_samples/config/host1/glance/schema-image.json rename to config_samples/config/host1/root/etc/glance/schema-image.json diff --git a/config_samples/config/host1/services.json b/config_samples/config/host1/services.json new file mode 100644 index 0000000..14a69a6 --- /dev/null +++ b/config_samples/config/host1/services.json @@ -0,0 +1,9 @@ +{ + "cinder": { + "version": "2013.1" + }, + "glance": { + "version": "2013.1" + } +} + diff --git a/config_samples/config/host2/filesystem b/config_samples/config/host2/filesystem new file mode 100644 index 0000000..a71fd5f --- /dev/null +++ b/config_samples/config/host2/filesystem @@ -0,0 +1,16 @@ +# type | source filesystem | reference file | owner | group | permissions + +dir|/etc/keystone|keystone|keystone|0750 +file|/etc/keystone/logging.conf|etc/keystone/logging.conf|keystone|keystone|0640 +file|/etc/keystone/default_catalog.templates|etc/keystone/default_catalog.templates|keystone|keystone|0640 +file|/etc/keystone/policy.json|etc/keystone/policy.json|keystone|keystone|0640 +file|/etc/keystone/keystone.conf|etc/keystone/keystone.conf|keystone|keystone|0640 + +dir|/etc/nova|nova|nova|0750 +file|/etc/nova/logging.conf|etc/nova/logging.conf|nova|nova|0640 +file|/etc/nova/api-paste.ini|etc/nova/api-paste.ini|nova|nova|0640 +file|/etc/nova/policy.json|etc/nova/policy.json|nova|nova|0640 +file|/etc/nova/rootwrap.conf|etc/nova/rootwrap.conf|nova|nova|0640 +file|/etc/nova/release|etc/nova/release|nova|nova|0640 +file|/etc/nova/nova.conf|etc/nova/nova.conf|nova|nova|0640 + diff --git a/config_samples/config/host2/nova/version b/config_samples/config/host2/nova/version deleted file mode 100644 index f52e616..0000000 --- a/config_samples/config/host2/nova/version +++ /dev/null @@ -1 +0,0 @@ -2013.1 diff --git a/config_samples/config/host2/keystone/default_catalog.templates b/config_samples/config/host2/root/etc/keystone/default_catalog.templates similarity index 100% rename from config_samples/config/host2/keystone/default_catalog.templates rename to config_samples/config/host2/root/etc/keystone/default_catalog.templates diff --git a/config_samples/config/host2/keystone/keystone.conf b/config_samples/config/host2/root/etc/keystone/keystone.conf similarity index 100% rename from config_samples/config/host2/keystone/keystone.conf rename to config_samples/config/host2/root/etc/keystone/keystone.conf diff --git a/config_samples/config/host2/keystone/logging.conf b/config_samples/config/host2/root/etc/keystone/logging.conf similarity index 100% rename from config_samples/config/host2/keystone/logging.conf rename to config_samples/config/host2/root/etc/keystone/logging.conf diff --git a/config_samples/config/host2/keystone/policy.json b/config_samples/config/host2/root/etc/keystone/policy.json similarity index 100% rename from config_samples/config/host2/keystone/policy.json rename to config_samples/config/host2/root/etc/keystone/policy.json diff --git a/config_samples/config/host2/nova/api-paste.ini b/config_samples/config/host2/root/etc/nova/api-paste.ini similarity index 100% rename from config_samples/config/host2/nova/api-paste.ini rename to config_samples/config/host2/root/etc/nova/api-paste.ini diff --git a/config_samples/config/host2/nova/logging.conf b/config_samples/config/host2/root/etc/nova/logging.conf similarity index 100% rename from config_samples/config/host2/nova/logging.conf rename to config_samples/config/host2/root/etc/nova/logging.conf diff --git a/config_samples/config/host2/nova/nova.conf b/config_samples/config/host2/root/etc/nova/nova.conf similarity index 100% rename from config_samples/config/host2/nova/nova.conf rename to config_samples/config/host2/root/etc/nova/nova.conf diff --git a/config_samples/config/host2/nova/policy.json b/config_samples/config/host2/root/etc/nova/policy.json similarity index 100% rename from config_samples/config/host2/nova/policy.json rename to config_samples/config/host2/root/etc/nova/policy.json diff --git a/config_samples/config/host2/nova/release b/config_samples/config/host2/root/etc/nova/release similarity index 100% rename from config_samples/config/host2/nova/release rename to config_samples/config/host2/root/etc/nova/release diff --git a/config_samples/config/host2/nova/rootwrap.conf b/config_samples/config/host2/root/etc/nova/rootwrap.conf similarity index 100% rename from config_samples/config/host2/nova/rootwrap.conf rename to config_samples/config/host2/root/etc/nova/rootwrap.conf diff --git a/config_samples/config/host2/services.json b/config_samples/config/host2/services.json new file mode 100644 index 0000000..d720a3d --- /dev/null +++ b/config_samples/config/host2/services.json @@ -0,0 +1,9 @@ +{ + "keystone": { + "version": "2013.1" + }, + "nova": { + "version": "2013.1" + } +} + diff --git a/openstack-configuration.txt b/openstack-configuration.txt index fd943c7..0260344 100644 --- a/openstack-configuration.txt +++ b/openstack-configuration.txt @@ -158,3 +158,95 @@ (continue from http://docs.openstack.org/grizzly/openstack-compute/install/yum/content/compute-minimum-configuration-settings.html) + * Ensure user 'nova' exists, group 'nova' exists, user 'nova' belongs to group 'nova' + * Ensure that '/etc/nova' has 'nova:nova' owners. + * Ensure that '/etc/nova/nova.conf' has 'root:nova' owners and 0640 permissions. + + * Minimal /etc/nova/nova.conf: + + auth_strategy=keystone + network_manager=nova.network.manager.FlatDHCPManager + fixed_range=192.168.100.0/24 + public_interface=eth0 + flat_interface=eth0 + flat_network_bridge=br100 + + * Sample /etc/nova/nova.conf: + + [DEFAULT] + + # LOGS/STATE + verbose=True + logdir=/var/log/nova + state_path=/var/lib/nova + lock_path=/var/lock/nova + rootwrap_config=/etc/nova/rootwrap.conf + + # SCHEDULER + compute_scheduler_driver=nova.scheduler.filter_scheduler.FilterScheduler + + # VOLUMES + volume_api_class=nova.volume.cinder.API + volume_driver=nova.volume.driver.ISCSIDriver + volume_group=cinder-volumes + volume_name_template=volume-%s + iscsi_helper=tgtadm + + # DATABASE + sql_connection=mysql://nova:yourpassword@192.168.206.130/nova + + # COMPUTE + libvirt_type=qemu + compute_driver=libvirt.LibvirtDriver + instance_name_template=instance-%08x + api_paste_config=/etc/nova/api-paste.ini + + # COMPUTE/APIS: if you have separate configs for separate services + # this flag is required for both nova-api and nova-compute + allow_resize_to_same_host=True + + # APIS + osapi_compute_extension=nova.api.openstack.compute.contrib.standard_extensions + ec2_dmz_host=192.168.206.130 + s3_host=192.168.206.130 + enabled_apis=ec2,osapi_compute,metadata + + # QPID + qpid_hostname=192.168.206.130 + + # GLANCE + image_service=nova.image.glance.GlanceImageService + glance_api_servers=192.168.206.130:9292 + + # NETWORK + network_manager=nova.network.manager.FlatDHCPManager + force_dhcp_release=True + dhcpbridge_flagfile=/etc/nova/nova.conf + firewall_driver=nova.virt.libvirt.firewall.IptablesFirewallDriver + # Change my_ip to match each host + my_ip=192.168.206.130 + public_interface=eth100 + vlan_interface=eth0 + flat_network_bridge=br100 + flat_interface=eth0 + fixed_range=192.168.100.0/24 + + # NOVNC CONSOLE + novncproxy_base_url=http://192.168.206.130:6080/vnc_auto.html + # Change vncserver_proxyclient_address and vncserver_listen to match each compute host + vncserver_proxyclient_address=192.168.206.130 + vncserver_listen=192.168.206.130 + + # AUTHENTICATION + auth_strategy=keystone + [keystone_authtoken] + auth_host = 127.0.0.1 + auth_port = 35357 + auth_protocol = http + admin_tenant_name = service + admin_user = nova + admin_password = nova + signing_dirname = /tmp/keystone-signing-nova + + * 'nova-manage version' to find out version of nova. The output will be something like '2013.1'. + diff --git a/ostack_validator/model.py b/ostack_validator/model.py index 434df69..771e763 100644 --- a/ostack_validator/model.py +++ b/ostack_validator/model.py @@ -1,3 +1,5 @@ +import os.path + from ostack_validator.common import Mark class Openstack(object): @@ -19,10 +21,11 @@ class Host(object): component.parent = self class OpenstackComponent(object): - def __init__(self, name, version): + def __init__(self, name, version, base_path=None): super(OpenstackComponent, self).__init__() self.name = name self.version = version + self.base_path = base_path or '/etc/%s' % self.name self.configs = {} @property @@ -38,7 +41,7 @@ class OpenstackComponent(object): config_name = '%s.conf' % self.name if not config_name in self.configs: - resource = self.openstack.resource_locator.find_resource(self.host.name, self.name, config_name) + resource = self.openstack.resource_locator.find_resource('file', name=os.path.join(self.base_path, config_name), host=self.host.name) if resource: config = self.openstack.config_parser.parse(config_name, Mark(resource.name), resource.get_contents()) self.configs[config_name] = config diff --git a/ostack_validator/model_parser.py b/ostack_validator/model_parser.py index 20b4b3b..072779f 100644 --- a/ostack_validator/model_parser.py +++ b/ostack_validator/model_parser.py @@ -2,10 +2,10 @@ import logging from ostack_validator.common import Version from ostack_validator.model import * -from ostack_validator.resource import ConfigSnapshotResourceLocator +from ostack_validator.resource import Resource, ConfigSnapshotResourceLocator from ostack_validator.config_formats import IniConfigParser -OPENSTACK_COMPONENTS = ['nova', 'keystone', 'glance'] +OPENSTACK_COMPONENTS = ['nova', 'keystone', 'glance', 'cinder', 'horizon', 'quantum', 'swift'] class ModelParser(object): logger = logging.getLogger('ostack_validator.ModelParser') @@ -14,21 +14,15 @@ class ModelParser(object): resource_locator = ConfigSnapshotResourceLocator(path) hosts = [] - for host_name in resource_locator.find_hosts(): + for host in resource_locator.find_resource(Resource.HOST): components = [] - for component_name in resource_locator.find_host_components(host_name): - if not component_name in OPENSTACK_COMPONENTS: - self.logger.warn('Unknown component in config: %s', component_name) + for service in host.find_resource(Resource.SERVICE): + if not service.name in OPENSTACK_COMPONENTS: continue - component_version = Version(1000000) # very latest version - version_resource = resource_locator.find_resource(host_name, component_name, 'version') - if version_resource: - component_version = Version(version_resource.get_contents()) + components.append(OpenstackComponent(service.name, service.version)) - components.append(OpenstackComponent(component_name, component_version)) - - hosts.append(Host(host_name, {}, components)) + hosts.append(Host(host.name, {}, components)) return Openstack(hosts, resource_locator, IniConfigParser()) diff --git a/ostack_validator/resource.py b/ostack_validator/resource.py index 52414c2..3390efa 100644 --- a/ostack_validator/resource.py +++ b/ostack_validator/resource.py @@ -1,60 +1,165 @@ import glob import os.path +import json -from ostack_validator.common import Error +from ostack_validator.common import Error, Version class Resource(object): + HOST = 'host' + FILE = 'file' + DIRECTORY = 'directory' + SERVICE = 'service' + def __init__(self, name): super(Resource, self).__init__() self.name = name + def __str__(self): + return self.name + + def __repr__(self): + return '<%s name=%s>' % (str(self.__class__).split('.')[-1], self.name) + def get_contents(self): raise Error, 'Not implemented' class ResourceLocator(object): - def find_hosts(self): - return [] - - def find_host_components(self, host): - return [] - - def find_resource(self, host, component, name): + def find_resource(self, resource_type, name, host=None, **kwargs): return None +class HostResource(Resource): + def __init__(self, name, resource_locator, interfaces=[]): + super(HostResource, self).__init__(name) + self.resource_locator = resource_locator + self.interfaces = interfaces + + def find_resource(self, resource_type, name=None, **kwargs): + return self.resource_locator.find_resource(resource_type, name, host=self, **kwargs) + +class DirectoryResource(Resource): + def __init__(self, name, owner=None, group=None, permissions=None): + super(DirectoryResource, self).__init__(name) + self.owner = owner + self.group = group + self.permissions = permissions + class FileResource(Resource): - def __init__(self, name, path): + def __init__(self, name, path, owner=None, group=None, permissions=None): super(FileResource, self).__init__(name) self.path = path + self.owner = owner + self.group = group + self.permissions = permissions def get_contents(self): with open(self.path) as f: return f.read() +class ServiceResource(Resource): + def __init__(self, name, version, metadata={}): + super(ServiceResource, self).__init__(name) + self.version = Version(version) + self.metadata = metadata + + +class FilesystemSnapshot(object): + def __init__(self, path): + super(FilesystemSnapshot, self).__init__() + self.path = path + self.basedir = os.path.join(os.path.dirname(self.path), 'root') + self._parse_snapshot() + + def get_resource(self, path): + if path in self._resources: + return self._resources[path] + + return None + + def _parse_snapshot(self): + self._resources = {} + if not os.path.isfile(self.path): return + with open(self.path) as f: + for line in f.readlines(): + line = line.lstrip() + if line == '' or line.startswith('#'): continue + + resource_type = line.split('|')[0] + if resource_type == 'dir': + source_path, owner, group, permissions = line.split('|')[1:] + self._resources[source_path] = DirectoryResource(source_path, owner=owner, group=group, permissions=permissions) + elif resource_type == 'file': + source_path, local_path, owner, group, permissions = line.split('|')[1:] + self._resources[source_path] = FileResource(os.path.basename(source_path), path=os.path.join(self.basedir, local_path), owner=owner, group=group, permissions=permissions) + else: + self.logger.warn('Unknown resource "%s" in line "%s"' % (resource_type, line)) + class ConfigSnapshotResourceLocator(object): def __init__(self, basedir): super(ConfigSnapshotResourceLocator, self).__init__() self.basedir = basedir if not os.path.isdir(self.basedir): raise Error, 'Invalid argument: base directory does not exist' + self._services = None + self._filesystem_snapshots = {} - def find_hosts(self): - return [os.path.basename(host_path) for host_path in glob.glob(os.path.join(self.basedir, '*')) if os.path.isdir(host_path)] + def find_resource(self, resource_type, name=None, host=None, **kwargs): + if resource_type == Resource.HOST: + if name: + host_path = os.path.join(self.basedir, name) + if not os.path.isdir(host_path): + return None + return HostResource(name, self) + else: + return [HostResource(os.path.basename(host_path), self) for host_path in glob.glob(os.path.join(self.basedir, '*')) if os.path.isdir(host_path)] + if resource_type == Resource.FILE: + if not host: + raise Error, 'Invalid argument: "host" need to be specified' - def find_host_components(self, host): - return [os.path.basename(component_path) for component_path in glob.glob(os.path.join(self.basedir, host, '*')) if os.path.isdir(component_path)] + if isinstance(host, HostResource): + host = host.name - def find_resource(self, host, component, name): - if not host: - raise Error, 'Invalid argument: "host" need to be specified' + if name: + return self._get_filesystem_snapshot(host).get_resource(name) + else: + return [] + elif resource_type == Resource.SERVICE: + if not host: + raise Error, 'Invalid argument: "host" need to be specified' - if not component: - raise Error, 'Invalid argument: "component" need to be specified' + if isinstance(host, HostResource): + host = host.name - path = os.path.join(self.basedir, host, component, name) - if not os.path.exists(path): + self._ensure_services_loaded() + + if name: + if name in self._services: + return self._services[host][name] + else: + return None + else: + return self._services[host].values() + else: return None - fullname = '%s/%s/%s' % (host, component, name) + def _ensure_services_loaded(self): + if self._services: return - return FileResource(fullname, path) + self._services = {} + for host_path in glob.glob(os.path.join(self.basedir, '*')): + if not os.path.isdir(host_path): continue + + services_json_path = os.path.join(host_path, 'services.json') + if not os.path.isfile(services_json_path): continue + + host_name = os.path.basename(host_path) + self._services[host_name] = {} + with open(services_json_path) as f: + for service_name, metadata in json.loads(f.read()).items(): + version = metadata.pop('version') + self._services[host_name][service_name] = ServiceResource(service_name, str(version), metadata) + + def _get_filesystem_snapshot(self, host): + if not host in self._filesystem_snapshots: + self._filesystem_snapshots[host] = FilesystemSnapshot(os.path.join(self.basedir, host, 'filesystem')) + return self._filesystem_snapshots[host]