diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6431eee --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +.coverage +cover/ +.testrepository +subunit.log +.venv +*.pyc +.idea +*.sw? +*.egg? +*~ +.tox +ChangeLog +build +dist +clearstack.egg-info +clearstack/versioninfo +# Development environment files +.project +.pydevproject +.DS_Store +covhtml/ +tags diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..bbc749d --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Alberto Murillo +Julio Montes +Obed Muñoz +Victor Morales diff --git a/HACKING.rst b/HACKING.rst new file mode 100644 index 0000000..6d359ad --- /dev/null +++ b/HACKING.rst @@ -0,0 +1,55 @@ +Clearstack Style Commandments +============================== + +- Step 1: Read the OpenStack Style Commandments + http://docs.openstack.org/developer/hacking/ +- Step 2: Read on + +Docstrings +---------- + +Docstrings should ONLY use triple-double-quotes (``"""``) + +Single-line docstrings should NEVER have extraneous whitespace +between enclosing triple-double-quotes. + +Deviation! Sentence fragments do not have punctuation. Specifically in the +command classes the one line docstring is also the help string for that +command and those do not have periods. + + """A one line docstring looks like this""" + +Calling Methods +--------------- + +Deviation! When breaking up method calls due to the 79 char line length limit, +use the alternate 4 space indent. With the first argument on the succeeding +line all arguments will then be vertically aligned. Use the same convention +used with other data structure literals and terminate the method call with +the last argument line ending with a comma and the closing paren on its own +line indented to the starting line level. + + unnecessarily_long_function_name( + 'string one', + 'string two', + kwarg1=constants.ACTIVE, + kwarg2=['a', 'b', 'c'], + ) + +Python 3.x Compatibility +------------------------ + +Clearstack is only Python 3.4 compatible. Common guidelines: + +* print statements are functions: print statements should be converted + to an appropriate log or other output mechanism. +* Use six where applicable: x.iteritems is converted to six.iteritems(x) + for example + +Running Tests +------------- +The testing system is based on a combination of tox and testr. If you just +want to run the whole suite, run `tox` and all will be fine. However, if +you'd like to dig in a bit more, you might want to learn some things about +testr itself. A basic walkthrough for OpenStack can be found at +http://wiki.openstack.org/testr diff --git a/LICENSE b/LICENSE index 8f71f43..f433b1a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -174,29 +175,3 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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. - diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..0622c89 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,17 @@ +Metadata-Version: 1.1 +Name: clearstack +Version: 1.1.4 +Summary: Tool for set up an Openstack environment +Home-page: +Author: Alberto Murillo, Julio Montes, Obed Muñoz, Victor Morales +License: Apache-2.0 +Description: Tool for set up an Openstack environment +Keywords: Openstack +Platform: Unix +Classifier: Environment :: OpenStack +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: POSIX :: Linux +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..977692e --- /dev/null +++ b/README.rst @@ -0,0 +1,32 @@ + +A tool to deploy components of Openstack on multiple servers with Clear Linux* Project for Intel Architecture installed + +==== + +Contents: + +1) Description of this project +2) Compiling, prerequisites +3) Bugs and feedback? + +==== + + +==== + +2. Compiling + +$ pip3 install -r requirements-py3.txt +$ python3 setup.py install + +==== + +3. Bugs, feedback, contact + +clearstack is hosted on github. You can find releases, an issue +tracker and git sources on github.com/clearlinux/clearstack. For +mailing lists, subscribe to dev@lists.clearlinux.org (via +lists.clearlinux.org). + +This project has many contributors. Not all may be mentioned in the +AUTHORS file. diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..efceab8 --- /dev/null +++ b/babel.cfg @@ -0,0 +1 @@ +[python: **.py] diff --git a/clearstack/__init__.py b/clearstack/__init__.py new file mode 100644 index 0000000..15625b2 --- /dev/null +++ b/clearstack/__init__.py @@ -0,0 +1,22 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# +# 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 pbr.version + + +__version__ = pbr.version.VersionInfo('clearstack').version_string() diff --git a/clearstack/answer_file.py b/clearstack/answer_file.py new file mode 100644 index 0000000..351e85a --- /dev/null +++ b/clearstack/answer_file.py @@ -0,0 +1,65 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Victor Morales +# +# 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 configparser + +from clearstack.controller import Controller +from clearstack.common.util import LOG +from clearstack.common.singleton import Singleton + + +@Singleton +class AnswerFile: + def generate(self, file, variables_cmd): + with open(file, "w") as f: + f.write("[general]\n\n") + for group in Controller.get().get_all_groups(): + for argument in group.get_all_arguments(): + f.write("# {0}\n".format(argument.description)) + value = variables_cmd.get(argument.conf_name, + argument.default_value) + f.write("{0}={1}\n\n".format(argument.conf_name, value)) + + def read(self, file, variables_cmd): + conf = Controller.get().CONF + config = configparser.RawConfigParser() + config.optionxform = str + if not os.path.isfile(file): + raise IOError("file {0} not found".format(file)) + + """ Validate option in answer file""" + config.read(file) + if not config.has_section('general'): + raise KeyError("answer file {0} doesn't have general" + " section".format(file)) + conf_file = dict(config['general']) + conf_file.update(variables_cmd) # override variables + conf.update(conf_file) + + try: + Controller.get().validate_groups(conf_file) + except Exception as e: + raise e + + all_args = Controller.get().get_all_arguments() + for non_supported in (set(conf_file) - set(all_args)): + LOG.warn("clearstack: variable {0} is not" + " supported yet".format(non_supported)) diff --git a/clearstack/argument.py b/clearstack/argument.py new file mode 100644 index 0000000..7b78a04 --- /dev/null +++ b/clearstack/argument.py @@ -0,0 +1,37 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Victor Morales +# +# 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. +# + + +class Argument(object): + def __init__(self, cmd_option, description, + conf_name, default_value, validators=[], + options=None): + self.cmd_option = cmd_option + self.description = description + self.conf_name = conf_name + self.default_value = default_value + self.validators = validators + self.option_list = options + + def validate(self, option): + for validator in self.validators: + try: + validator(option) + except ValueError as e: + raise ValueError("{0}: {1}".format(self.conf_name, str(e))) diff --git a/clearstack/common/__init__.py b/clearstack/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clearstack/common/singleton.py b/clearstack/common/singleton.py new file mode 100644 index 0000000..3742bdc --- /dev/null +++ b/clearstack/common/singleton.py @@ -0,0 +1,35 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# +# 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. +# + + +class Singleton: + def __init__(self, decorated): + self._decorated = decorated + + def get(self): + try: + return self._instance + except AttributeError: + self._instance = self._decorated() + return self._instance + + def __call__(self): + raise TypeError('Singletons must be accessed through `Get()`.') + + def __instancecheck__(self, inst): + return isinstance(inst, self._decorated) diff --git a/clearstack/common/swupd.py b/clearstack/common/swupd.py new file mode 100644 index 0000000..f305a23 --- /dev/null +++ b/clearstack/common/swupd.py @@ -0,0 +1,43 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Victor Morales +# +# 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 + +from common import util +from common.util import LOG + + +class Client(): + def install(bundle): + if not os.path.isfile("/usr/share/clear/bundles/" + str(bundle)) and \ + not os.path.isfile("/usr/share/clear/bundles/" + "openstack-all-in-one"): + LOG.info("Installing {0} bundle".format(bundle)) + cmd = "clr_bundle_add -V {0}".format(bundle) + if(os.path.isfile("/bin/swupd")): + cmd = "swupd bundle-add -V {0}".format(bundle) + + try: + stdout, stderr = util.run_command(cmd) + if stderr: + LOG.error("swupd bundle-add: {0}\n{1}" + .format(stdout, stderr)) + except Exception: + LOG.error("clearstack: cannot install" + " {0} bundle".format(bundle)) diff --git a/clearstack/common/util.py b/clearstack/common/util.py new file mode 100644 index 0000000..8f81fbe --- /dev/null +++ b/clearstack/common/util.py @@ -0,0 +1,210 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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 socket +import subprocess +import configparser +import netifaces +import ipaddress +import logging +from errno import EACCES, EPERM, ENOENT + +HOST_LOG_DIR = '/var/log/clearstack' +HOST_LOG_FILE = "{0}/clearstack.log".format(HOST_LOG_DIR) +LOG_DIR = '/var/log/clearstack' +if os.geteuid() != 0: + LOG_DIR = '/tmp/log/clearstack' +LOG_FILE = "{0}/clearstack.log".format(LOG_DIR) +LOG = logging.getLogger("Clearstack") + + +def _print_error_message(self, e, file_name): + # PermissionError + if e.errno == EPERM or e.errno == EACCES: + print("PermissionError error({0}): {1} for:\n{2}".format(e.errno, + e.strerror, file_name)) + # FileNotFoundError + elif e.errno == ENOENT: + print("FileNotFoundError error({0}): {1} as:\n{2}".format(e.errno, + e.strerror, file_name)) + elif IOError: + print("I/O error({0}): {1} as:\n{2}".format(e.errno, e.strerror, + file_name)) + elif OSError: + print("OS error({0}): {1} as:\n{2}".format(e.errno, e.strerror, + file_name)) + + +def setup_debugging(debug, is_remote_host=True): + if not os.path.isdir(LOG_DIR): + os.makedirs(LOG_DIR) + try: + logging.basicConfig(filename=LOG_FILE, + format='%(message)s', + level=logging.INFO) + except (IOError, OSError) as e: + _print_error_message(e, LOG_FILE) + + ''' paramiko detects all output as error (stderr)''' + ''' if it is a remote host redirect output to log file ''' + if not is_remote_host: + sh = logging.StreamHandler() + sh.setLevel(logging.INFO) + LOG.addHandler(sh) + + if debug: + LOG.setLevel(logging.DEBUG) + + +def run_command(command, stdin=None, environ=None, debug=True): + """Returns (stdout, stderr), raises error on non-zero return code""" + if environ is None: + env = None + else: + env = os.environ.copy() + env.update(environ) + + if debug: # to avoid show passwords + LOG.debug("command: %s", command) + + proc = subprocess.Popen(['bash', '-c', command], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdin=subprocess.PIPE, env=env) + stdout, stderr = proc.communicate() + if proc.returncode and stderr: + raise Exception("Error running: {0}\n{1}" + .format(command, stderr.decode("utf-8"))) + + return stdout, stderr + + +def str2bool(value): + return value.lower() in ("yes", "true", "t", "1", "y") + + +def write_config(file, data): + directory = os.path.dirname(file) + if not os.path.isdir(directory): + os.makedirs(directory) + config = configparser.RawConfigParser(allow_no_value=True) + if os.path.isfile(file): + config.read(file) + config.read_string(data) + with open(file, 'w') as f: + config.write(f) + + +def write_properties(file, data): + directory = os.path.dirname(file) + if not os.path.isdir(directory): + os.makedirs(directory) + if os.path.isfile(file): + with open(file, 'r') as f: + for l in f.readlines(): + key, value = [x.strip() for x in l.split('=')] + data[key] = value + output = "" + for key, value in data.items(): + output += "%s = %s\n" % (key, value) + with open(file, 'w') as f: + f.write(output) + + +def get_option(file, section, option): + config = configparser.RawConfigParser(allow_no_value=True) + config.read(file) + return config[section][option] + + +def delete_option(file, section, option=None): + config = configparser.RawConfigParser(allow_no_value=True) + config.read(file) + if option: + config.remove_option(section, option) + else: + config.remove_section(section) + with open(file, 'w') as f: + config.write(f) + + +# write in file if not exist the line +def write_in_file_ine(file, data): + with open(file, 'a+') as f: + f.seek(0) + if not any(data == x.rstrip('\r\n') for x in f): + f.write(data + '\n') + return True + return False + + +def get_dns(): + with open('/etc/resolv.conf', 'r') as f: + return [l.split(' ')[1].strip() for l in f.readlines() + if l.startswith('nameserver')] + + +def get_netmask(): + interface = get_net_interface() + mask = netifaces.ifaddresses(interface)[netifaces.AF_INET][0]['netmask'] + return ipaddress.IPv4Network('0.0.0.0/%s' % mask).prefixlen + + +def get_gateway(): + return netifaces.gateways()['default'][netifaces.AF_INET][0] + + +def get_net_interface(): + return (netifaces.gateways())['default'][netifaces.AF_INET][1] + + +def get_ip(): + interface = get_net_interface() + return netifaces.ifaddresses(interface)[netifaces.AF_INET][0]['addr'] + + +def is_localhost(host): + return host == "localhost" or host == "127.0.0.1" or \ + host == socket.gethostname() or host == get_ip() + + +def has_localhost(hosts): + for host in hosts: + if is_localhost(host): + return True + return False + + +def remove_localhost(hosts): + for host in hosts.copy(): + if is_localhost(host): + hosts.remove(host) + + +def ensure_directory(dir): + if not os.path.exists(dir): + os.makedirs(dir) + + +def link_file(source, target): + dir = os.path.dirname(target) + ensure_directory(dir) + if os.path.exists(target): + os.remove(target) + os.symlink(source, target) diff --git a/clearstack/controller.py b/clearstack/controller.py new file mode 100644 index 0000000..dae2153 --- /dev/null +++ b/clearstack/controller.py @@ -0,0 +1,89 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Victor Morales +# +# 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. +# + +from clearstack.group import Group +from clearstack.sequence import Sequence +from clearstack.common.singleton import Singleton + +DEBUG = False + + +@Singleton +class Controller: + CONF = {} + _plugins = [] + _groups = [] + _sequences = [] + + def add_sequence(self, desc, function, args=None): + self._sequences.append(Sequence(desc, function, args)) + + def run_all_sequences(self): + for seq in self._sequences: + try: + seq.run() + except Exception as e: + raise e + + def add_plugin(self, plugin): + self._plugins.append(plugin) + + def get_all_plugins(self): + return self._plugins + + def add_group(self, name, args): + self._groups.append(Group(name, args)) + + def get_all_groups(self): + return self._groups + + def get_all_arguments(self): + """Get a list of the configuration argument loaded""" + arguments = [] + for group in self._groups: + arguments.extend([argument.conf_name + for argument in group.get_all_arguments()]) + return arguments + + def remove_argument(self, group_name, conf_name): + for group in self._groups: + if group.group_name == group_name: + for arg in group.arguments: + if arg.conf_name == conf_name: + group.arguments.remove(arg) + return True + return False + + def remove_validators(self, conf_names): + for group in self._groups: + for arg in group.arguments: + if arg.conf_name in conf_names: + arg.validators.clear() + + def validate_groups(self, conf_values): + """ Load validation functions, in order to check + the values in the answer file """ + arguments = {} + for group in self._groups: + for arg in group.get_all_arguments(): + try: + arg.validate(conf_values[arg.conf_name]) + except Exception as e: + raise Exception("{0}: {1}".format(arg.conf_name, str(e))) + return arguments diff --git a/clearstack/group.py b/clearstack/group.py new file mode 100644 index 0000000..322990c --- /dev/null +++ b/clearstack/group.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# +# 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. +# + + +class Group(object): + def __init__(self, group_name, arguments): + self.group_name = group_name + self.arguments = arguments + + def get_group_name(self): + return self.group_name + + def get_all_arguments(self): + return self.arguments diff --git a/clearstack/locale/clearstack.pot b/clearstack/locale/clearstack.pot new file mode 100644 index 0000000..e69de29 diff --git a/clearstack/modules/__init__.py b/clearstack/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clearstack/modules/ceilometer.py b/clearstack/modules/ceilometer.py new file mode 100644 index 0000000..ddf5fde --- /dev/null +++ b/clearstack/modules/ceilometer.py @@ -0,0 +1,58 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Victor Morales +# +# 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 socket + +from modules.keystone import Keystone +from modules.openstack import OpenStackService +from modules.conf import CONF +from common import util +from common.singleton import Singleton + + +@Singleton +class Ceilometer(OpenStackService): + _name = "ceilometer" + _bundle = "openstack-telemetry" + _services = ["ceilometer-agent-central", "ceilometer-agent-notification", + "ceilometer-api", "ceilometer-collector", + "ceilometer-alarm-evaluator", "ceilometer-alarm-notifier"] + _type = "metering" + _description = "OpenStack Telemetry Service" + _public_url = "http://{0}:8777".format(CONF['CONFIG_CONTROLLER_HOST']) + + def config_database(self, configfile): + dbpass = CONF['CONFIG_%s_DB_PW' % self._name.upper()] + dbhost = socket.gethostbyaddr(CONF['CONFIG_MONGODB_HOST'])[0] + config = ("[database]\n" + "connection=mongodb://{0}:{1}@{2}:27017/{0}" + .format(self._name, dbpass, dbhost)) + util.write_config(configfile, config) + + def config_service_credentials(self, configfile): + keystone = Keystone.get() + config = \ + "[service_credentials]\n" + \ + "os_auth_url = %s\n" % keystone._admin_url + \ + "os_username = %s\n" % self._name + \ + "os_tenant_name = service\n" + \ + "os_password = %s\n" % self._password + \ + "os_endpoint_type = internalURL\n" + \ + "os_region_name = %s\n" % self._region + util.write_config(configfile, config) diff --git a/clearstack/modules/clearlinux.py b/clearstack/modules/clearlinux.py new file mode 100644 index 0000000..0be1b30 --- /dev/null +++ b/clearstack/modules/clearlinux.py @@ -0,0 +1,26 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Victor Morales +# +# 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. +# + +from common import util + + +def support_hw_acceleration(): + stdout, stderr = util.run_command("egrep -c '(vmx|svm)' /proc/cpuinfo") + return bool(int(stdout.decode('utf-8'))) diff --git a/clearstack/modules/conf.py b/clearstack/modules/conf.py new file mode 100644 index 0000000..b18eff2 --- /dev/null +++ b/clearstack/modules/conf.py @@ -0,0 +1,40 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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 configparser + + +_conf_file = "{0}/../defaults.conf".format(os.path.dirname(__file__)) +_config = configparser.RawConfigParser() +_config.optionxform = str +_config.read(_conf_file) + +CONF = dict(_config['general']) + +ENV = {"OS_PROJECT_DOMAIN_ID": "default", + "OS_USER_DOMAIN_ID": "default", + "OS_PROJECT_NAME": "admin", + "OS_USERNAME": "admin", + "OS_PASSWORD": "{0}".format(CONF['CONFIG_KEYSTONE_ADMIN_PW']), + "OS_TENANT_NAME": "admin", + "OS_AUTH_URL": "http://{0}:35357/v3" + .format(CONF['CONFIG_CONTROLLER_HOST']), + "OS_IDENTITY_API_VERSION": "3", + "OS_IMAGE_API_VERSION": "2"} diff --git a/clearstack/modules/glance.py b/clearstack/modules/glance.py new file mode 100644 index 0000000..46ef341 --- /dev/null +++ b/clearstack/modules/glance.py @@ -0,0 +1,64 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Victor Morales +# +# 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 + +from modules.openstack import OpenStackService +from modules.conf import CONF +from common import util +from common.util import LOG +from common.singleton import Singleton + + +@Singleton +class Glance(OpenStackService): + _name = "glance" + _bundle = "openstack-image" + _services = ["glance-api", "glance-registry"] + _type = "image" + _description = "OpenStack Image" + _public_url = "http://{0}:9292".format(CONF['CONFIG_CONTROLLER_HOST']) + + def sync_database(self): + LOG.debug("syncing database") + util.run_command("su -s /bin/sh -c" + " \"glance-manage db_sync\" glance") + + def create_image(self, name, format, url, public=False): + LOG.debug("Creating image %s" % name) + try: + util.run_command("openstack image show %s" % name, + environ=self._env) + except: + command = ("openstack image create --disk-format %s %s" + % (format, name)) + if os.path.isfile(url): + command += " --file %s" % url + else: + command += " --location %s" % url + if public: + command += " --public" + util.run_command(command, environ=self._env) + + def ceilometer_enable(self, configfile): + self.config_rabbitmq(configfile) + config = ("[DEFAULT]\n" + "notification_driver = messagingv2\n") + util.write_config(configfile, config) diff --git a/clearstack/modules/heat.py b/clearstack/modules/heat.py new file mode 100644 index 0000000..e56ef74 --- /dev/null +++ b/clearstack/modules/heat.py @@ -0,0 +1,73 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Victor Morales +# +# 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 configparser + +from modules.openstack import OpenStackService +from modules.conf import CONF +from common import util +from common.util import LOG +from common.singleton import Singleton + + +@Singleton +class Heat(OpenStackService): + _name = "heat" + _bundle = "openstack-orchestration" + _services = ["heat-api", "heat-api-cfn", "heat-engine"] + _type = "orchestration" + _description = "OpenStack Orchestration" + _public_url = ("http://{0}:8004/v1/%\(tenant_id\)s" + .format(CONF['CONFIG_CONTROLLER_HOST'])) + + domain_admin = CONF['CONFIG_HEAT_DOMAIN_ADMIN'] + domain_admin_pw = CONF['CONFIG_HEAT_DOMAIN_PASSWORD'] + domain_name = CONF['CONFIG_HEAT_DOMAIN'] + + def sync_database(self): + LOG.debug("syncing database") + util.run_command("su -s /bin/sh -c" + " \"heat-manage db_sync\" heat") + + def config_auth(self, configfile): + OpenStackService.config_auth(self, configfile) + config = configparser.RawConfigParser(allow_no_value=True) + config.read(configfile) + auth_uri = config['keystone_authtoken']['auth_uri'] + config['trustee'] = config['keystone_authtoken'] + if not config.has_section('clients_keystone'): + config.add_section('clients_keystone') + config['clients_keystone']['auth_uri'] = auth_uri + if not config.has_section('ec2authtoken'): + config.add_section('ec2authtoken') + config['ec2authtoken']['auth_uri'] = auth_uri + with open(configfile, 'w') as f: + config.write(f) + + def config_domain(self, configfile): + config = ("[DEFAULT]\n" + "heat_metadata_server_url = http://{0}:8000\n" + "heat_waitcondition_server_url = " + "http://{0}:8000/v1/waitcondition\n" + "stack_domain_admin = {1}\n" + "stack_domain_admin_password = {2}\n" + "stack_user_domain_name = {3}\n" + .format(self._controller, self.domain_admin, + self.domain_admin_pw, self.domain_name)) + util.write_config(configfile, config) diff --git a/clearstack/modules/horizon.py b/clearstack/modules/horizon.py new file mode 100644 index 0000000..7e07243 --- /dev/null +++ b/clearstack/modules/horizon.py @@ -0,0 +1,39 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Obed Muñoz +# Author: Victor Morales +# +# 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. +# + +from modules.openstack import OpenStackService +from modules.conf import CONF +from common.singleton import Singleton + + +@Singleton +class Horizon(OpenStackService): + _name = "horizon" + _bundle = "openstack-dashboard" + if CONF['CONFIG_HTTP_SERVICE'] == 'nginx': + _services = ["nginx", "uwsgi@horizon.socket"] + elif CONF['CONFIG_HTTP_SERVICE'] == 'apache2': + _services = ["httpd"] + # These are needed by OpenStackService but not used by horizon + _type = "" + _public_url = "" + _description = "" + _password = True diff --git a/clearstack/modules/keystone.py b/clearstack/modules/keystone.py new file mode 100644 index 0000000..2671669 --- /dev/null +++ b/clearstack/modules/keystone.py @@ -0,0 +1,72 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Obed Muñoz +# Author: Victor Morales +# +# 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. +# + +from modules.openstack import OpenStackService +from modules.conf import CONF +from common import util +from common.util import LOG +from common.singleton import Singleton + + +@Singleton +class Keystone(OpenStackService): + _name = "keystone" + _bundle = "openstack-identity" + if CONF['CONFIG_HTTP_SERVICE'] == 'nginx': + _services = ["memcached", "nginx", "uwsgi@keystone-admin.socket", + "uwsgi@keystone-public.socket"] + elif CONF['CONFIG_HTTP_SERVICE'] == 'apache2': + _services = ["memcached", "httpd"] + else: + _services = ["keystone"] + _type = "identity" + _description = "OpenStack Identity" + _user = "admin" + _password = CONF['CONFIG_KEYSTONE_ADMIN_PW'] + _public_url = "http://%s:5000/v3" % CONF['CONFIG_CONTROLLER_HOST'] + _internal_url = _public_url + _admin_url = "http://%s:35357/v3" % CONF['CONFIG_CONTROLLER_HOST'] + _env = {"OS_TOKEN": CONF['CONFIG_KEYSTONE_ADMIN_TOKEN'], + "OS_URL": "http://{0}:35357/v3" + .format(CONF['CONFIG_CONTROLLER_HOST']), + "OS_IDENTITY_API_VERSION": "3"} + + def sync_database(self): + LOG.debug("syncing database") + util.run_command("su -s /bin/sh -c" + " \"keystone-manage db_sync\" keystone") + + def create_project(self, project, description): + LOG.debug("setting up %s project" % project) + try: + """ test if project already exists """ + util.run_command("openstack project show %s" % project, + environ=self._env) + except: + util.run_command("openstack project create --domain default" + " --description \"%s\" %s" + % (description, project), environ=self._env) + + def config_admin_token(self, configfile): + config = \ + "[DEFAULT]\n" + \ + "admin_token=%s\n" % CONF['CONFIG_KEYSTONE_ADMIN_TOKEN'] + util.write_config(configfile, config) diff --git a/clearstack/modules/mariadb.py b/clearstack/modules/mariadb.py new file mode 100644 index 0000000..a47f0d8 --- /dev/null +++ b/clearstack/modules/mariadb.py @@ -0,0 +1,111 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Obed Muñoz +# Author: Victor Morales +# +# 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 sys +import socket + +from common import util +from common.singleton import Singleton +from common.swupd import Client as swupd_client +from common.util import LOG +from modules.conf import CONF + + +@Singleton +class MariaDB: + def install(self): + swupd_client.install("database-mariadb") + + def start_server(self): + LOG.debug("starting services") + util.run_command("systemctl enable mariadb") + util.run_command("systemctl restart mariadb") + + def configure(self): + config_file = "/etc/mariadb/openstack.cnf" + config = """ + [mysqld] + default-storage-engine = innodb + innodb_file_per_table + collation-server = utf8_general_ci + init-connect = 'SET NAMES utf8' + character-set-server = utf8 + bind-address = 0.0.0.0 + """ + util.write_config(config_file, config) + + def secure_installation(self, user, password): + LOG.debug("secure installation mariadb") + try: + """ test if mysql has a password """ + util.run_command("mysql -u{0} -e \"\"".format(user), debug=False) + """ Secure the database service """ + util.run_command('mysqladmin -u root password "{0}"' + .format(password, debug=False)) + util.run_command('mysql -u root -p"{0}" -e "UPDATE mysql.user ' + 'SET Password=PASSWORD(\'{0}\') ' + 'WHERE User=\'root\'"' + .format(password, debug=False)) + util.run_command('mysql -u root -p"{0}" -e "DELETE FROM ' + 'mysql.user WHERE User=\'root\' AND Host ' + ' NOT IN (\'localhost\', \'127.0.0.1\', ' + '\'::1\')"' + .format(password, debug=False)) + util.run_command('mysql -u root -p"{0}" -e "DELETE FROM ' + 'mysql.user WHERE User=\'\'"' + .format(password, debug=False)) + util.run_command('mysql -u root -p"{0}" -e "DELETE FROM mysql.db ' + 'WHERE Db=\'test\' OR Db=\'test\_%\'"' + .format(password, debug=False)) + util.run_command('mysql -u root -p"{0}" -e ' + '"FLUSH PRIVILEGES"' + .format(password, debug=False)) + except: + pass + + try: + """ verify the password """ + util.run_command("mysql -u{0} -p{1} -e \"\"" + .format(user, password), debug=False) + except: + LOG.error("clearstack: cannot connect to mysql database," + " the password is incorrect") + return sys.exit(1) + + def setup_database(self, database, user, password, host): + LOG.debug("setting up database") + mariadb_host = socket.gethostbyaddr(CONF["CONFIG_MARIADB_HOST"])[0] + mariadb_user = CONF["CONFIG_MARIADB_USER"] + mariadb_pw = CONF["CONFIG_MARIADB_PW"] + util.run_command("mysql -u{0} -p{1} -h{2} -e" + " \"CREATE DATABASE if not exists {3};\"" + .format(mariadb_user, mariadb_pw, mariadb_host, + database), debug=False) + util.run_command("mysql -u{0} -p{1} -h{2} -e" + " \"GRANT ALL PRIVILEGES ON {3}.* TO" + " '{4}'@'localhost' IDENTIFIED BY '{5}';\"" + .format(mariadb_user, mariadb_pw, mariadb_host, + database, user, password), debug=False) + util.run_command("mysql -u{0} -p{1} -h{2} -e" + " \"GRANT ALL PRIVILEGES ON {3}.* TO '{4}'@'{5}'" + " IDENTIFIED BY '{6}';\"" + .format(mariadb_user, mariadb_pw, mariadb_host, + database, user, host, password), debug=False) diff --git a/clearstack/modules/mongodb.py b/clearstack/modules/mongodb.py new file mode 100644 index 0000000..3a769c1 --- /dev/null +++ b/clearstack/modules/mongodb.py @@ -0,0 +1,56 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Victor Morales +# +# 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. +# + +from common import util +from common.singleton import Singleton +from common.swupd import Client as swupd_client +from common.util import LOG + + +@Singleton +class MongoDB: + def install(self): + swupd_client.install("database-mongodb") + + def start_server(self): + LOG.debug("starting services") + util.run_command("systemctl enable mongodb") + util.run_command("systemctl restart mongodb") + count = 0 + while count < 10: + try: + util.run_command("mongo --host 127.0.0.1") + return + except: + util.run_command("sleep 1") + count += 1 + raise Exception("Failed to start mongodb service") + + def configure(self): + config_file = "/etc/mongodb/mongod.conf" + config = {'bind_ip': '0.0.0.0'} + util.write_properties(config_file, config) + + def setup_database(self, database, user, password): + cmd = ("mongo --host 127.0.0.1 --eval '" + "db = db.getSiblingDB(\"%s\");" + "db.createUser({user: \"%s\",pwd: \"%s\"," + "roles: [ \"readWrite\", \"dbAdmin\" ]})'" + % (database, user, password)) + util.run_command(cmd) diff --git a/clearstack/modules/neutron.py b/clearstack/modules/neutron.py new file mode 100644 index 0000000..7c95bf1 --- /dev/null +++ b/clearstack/modules/neutron.py @@ -0,0 +1,254 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Obed Muñoz +# Author: Victor Morales +# +# 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. +# + +from ipaddress import IPv4Network + +from modules.keystone import Keystone +from modules.nova import Nova +from modules.openstack import OpenStackService +from modules.conf import CONF +from common import util +from common.util import LOG +from common.singleton import Singleton + + +metadata_password = CONF['CONFIG_NEUTRON_METADATA_PW'] +tenant_network_types = CONF['CONFIG_NEUTRON_ML2_TENANT_NETWORK_TYPES'] +mechanism_drivers = CONF['CONFIG_NEUTRON_ML2_MECHANISM_DRIVERS'] +vni_ranges = CONF['CONFIG_NEUTRON_ML2_TUNNEL_ID_RANGES'] +type_drivers = CONF['CONFIG_NEUTRON_ML2_TYPE_DRIVERS'] +bridge_mappings = CONF['CONFIG_NEUTRON_LINUXBRIDGE_IFACES'] +flat_networks = CONF['CONFIG_NEUTRON_ML2_FLAT_NETWORKS'] + + +@Singleton +class Neutron(OpenStackService): + _name = "neutron" + _bundle = "openstack-network" + _services = ["neutron-server", "neutron-linuxbridge-agent", + "neutron-dhcp-agent", "neutron-metadata-agent", + "neutron-l3-agent"] + _type = "network" + _description = "OpenStack Networking" + _public_url = ("http://{0}:9696" + .format(CONF['CONFIG_CONTROLLER_HOST'])) + + def sync_database(self): + LOG.debug("syncing database") + util.run_command('su -s /bin/sh -c "neutron-db-manage ' + '--config-file /etc/neutron/neutron.conf ' + '--config-file /etc/neutron/plugins/ml2/ml2_conf.ini ' + 'upgrade head" neutron') + if util.str2bool(CONF['CONFIG_LBAAS_INSTALL']): + util.run_command('su -s /bin/sh -c "neutron-db-manage ' + '--service lbaas upgrade head"') + + def config_ml2_plugin(self): + config = \ + "[DEFAULT]\n" + \ + "core_plugin = ml2\n" \ + "service_plugins = router\n" + \ + "allow_overlapping_ips = True\n" + util.write_config("/etc/neutron/neutron.conf", config) + config = \ + "[ml2]\n" + \ + "type_drivers = %s\n" % type_drivers + \ + "tenant_network_types = %s\n" % tenant_network_types + \ + "mechanism_drivers = %s\n" % mechanism_drivers + \ + "extension_drivers = port_security\n" + \ + "[ml2_type_flat]\n" + \ + "flat_networks = public\n" + \ + "[ml2_type_vxlan]\n" + \ + "vni_ranges = %s\n" % vni_ranges + \ + "[securitygroup]\n" + \ + "enable_ipset = True\n" + util.write_config("/etc/neutron/plugins/ml2/ml2_conf.ini", config) + util.link_file('/etc/neutron/plugins/ml2/ml2_conf.ini', + '/etc/neutron/plugin.ini') + + def config_linux_bridge_agent(self): + config = \ + "[linux_bridge]\n" + \ + "physical_interface_mappings = %s\n" % bridge_mappings + \ + "[vxlan]\n" + \ + "enable_vxlan = True\n" + \ + "local_ip = %s\n" % util.get_ip() + \ + "l2_population = True\n" + \ + "[agent]\n" + \ + "prevent_arp_spoofing = True\n" + \ + "[securitygroup]\n" + \ + "enable_security_group = True\n" + \ + "firewall_driver = neutron.agent.linux.iptables_firewall." + \ + "IptablesFirewallDriver\n" + util.write_config("/etc/neutron/plugins/ml2/linuxbridge_agent.ini", + config) + + def config_l3_agent(self, configfile): + config = ("[DEFAULT]\n" + "interface_driver = " + "neutron.agent.linux.interface.BridgeInterfaceDriver\n" + "external_network_bridge = \n") + util.write_config(configfile, config) + + def config_dhcp_agent(self, configfile): + config = ("[DEFAULT]\n" + "interface_driver = " + "neutron.agent.linux.interface.BridgeInterfaceDriver\n" + "dhcp_driver = neutron.agent.linux.dhcp.Dnsmasq\n" + "enable_isolated_metadata = True\n") + util.write_config(configfile, config) + + def config_metadata_agent(self, configfile): + config = \ + "[DEFAULT]\n" + \ + "auth_uri = http://%s:5000\n" % self._controller + \ + "auth_url = http://%s:35357\n" % self._controller + \ + "auth_region = %s\n" % self._region + \ + "auth_plugin = password\n" + \ + "project_domain_id = default\n" + \ + "user_domain_id = default\n" + \ + "project_name = service\n" + \ + "username = %s\n" % self._name + \ + "password = %s\n" % self._password + \ + "nova_metadata_ip = %s\n" % self._controller + \ + "metadata_proxy_shared_secret = %s\n" % metadata_password + util.write_config(configfile, config) + + def config_nova(self, configfile): + nova = Nova.get() + config = ("[DEFAULT]\n" + "notify_nova_on_port_status_changes = True\n" + "notify_nova_on_port_data_changes = True\n" + "nova_url = http://{0}:8774/v2\n" + "[nova]\n" + "auth_url = http://{0}:35357\n" + "auth_plugin = password\n" + "project_domain_id = default\n" + "user_domain_id = default\n" + "region_name = {1}\n" + "project_name = service\n" + "username = nova\n" + "password = {2}\n" + .format(self._controller, self._region, nova._password)) + util.write_config(configfile, config) + + def config_neutron_on_nova(self, configfile): + keystone = Keystone.get() + config = \ + "[DEFAULT]\n" + \ + "network_api_class = nova.network.neutronv2.api.API\n" + \ + "security_group_api = neutron\n" + \ + "linuxnet_interface_driver = " + \ + "nova.network.linux_net.NeutronLinuxBridgeInterfaceDriver\n" + \ + "firewall_driver = nova.virt.firewall.NoopFirewallDriver\n" + \ + "[neutron]\n" + \ + "url = %s\n" % self._public_url + \ + "auth_url = %s\n" % keystone._admin_url + \ + "auth_plugin = password\n" + \ + "project_domain_id = default\n" + \ + "user_domain_id = default\n" + \ + "region_name = %s\n" % self._region + \ + "project_name = service\n" + \ + "username = neutron\n" + \ + "password = %s\n" % self._password + \ + "service_metadata_proxy = True\n" + \ + "metadata_proxy_shared_secret = %s\n" % metadata_password + util.write_config(configfile, config) + if CONF['CONFIG_HTTP_SERVICE'] == 'nginx': + nova_api_services = ['uwsgi@nova-api.socket', + 'uwsgi@nova-metadata.socket'] + elif CONF['CONFIG_HTTP_SERVICE'] == 'apache2': + nova_api_services = ['httpd'] + else: + nova_api_services = ['nova-api'] + self.start_server(nova_api_services) + + def add_service_plugin(self, plugin): + plugins = util.get_option('/etc/neutron/neutron.conf', + 'DEFAULT', 'service_plugins').split(',') + if plugin not in plugins: + plugins.append(plugin) + config = ('[DEFAULT]\n' + "service_plugins = %s\n" % ','.join(plugins)) + util.write_config("/etc/neutron/neutron.conf", config) + + def config_lbaas(self): + self.add_service_plugin('lbaas') + config = ("[DEFAULT]\n" + "interface_driver = " + "neutron.agent.linux.interface.BridgeInterfaceDriver\n") + util.write_config("/etc/neutron/lbaas_agent.ini", config) + self._services += ['neutron-lbaas-agent'] + + def config_vpnaas(self): + self.add_service_plugin('vpnaas') + driver = ("neutron_vpnaas.services.vpn.device_drivers.libreswan_ipsec" + ".LibreSwanDriver") + config = "[vpnagent]\n" + \ + "vpn_device_driver = %s\n" % driver + util.write_config("/etc/neutron/vpnaas_agent.ini", config) + self._services += ['ipsec', 'neutron-vpn-agent'] + + def create_network(self, name, public=None): + if public: + cidr = IPv4Network(CONF['CONFIG_NOVA_NETWORK_FLOATRANGE']) + else: + cidr = IPv4Network(CONF['CONFIG_NOVA_NETWORK_FIXEDRANGE']) + cidr_e = cidr.exploded + cidr_gw = (cidr.network_address + 1).exploded + cidr_start = (cidr.network_address + 2).exploded + cidr_end = (cidr.broadcast_address - 1).exploded + try: + util.run_command("neutron net-show %s" % name, + environ=self._env) + except: + cmd = "neutron net-create %s" % name + if public: + cmd += (" --provider:physical_network public" + " --shared --provider:network_type flat") + util.run_command(cmd, environ=self._env) + cmd = ("neutron subnet-create %s %s --name %s" + " --gateway %s" % (name, cidr_e, name, cidr_gw)) + if public: + cmd += (" --allocation-pool start=%s,end=%s" + % (cidr_start, cidr_end)) + util.run_command(cmd, environ=self._env) + if public: + util.run_command("neutron net-update %s --router:external" + % name, environ=self._env) + + def create_router(self, router, gw, interfaces): + try: + util.run_command("neutron router-show %s" % router, + environ=self._env) + except: + util.run_command("neutron router-create %s" % router, + environ=self._env) + util.run_command("neutron router-gateway-set %s %s" + % (router, gw), environ=self._env) + for i in interfaces: + util.run_command("neutron router-interface-add %s %s" + % (router, i), environ=self._env) + + def ceilometer_enable(self, configfile): + self.config_rabbitmq(configfile) + config = ("[DEFAULT]\n" + "notification_driver = messagingv2\n") + util.write_config(configfile, config) diff --git a/clearstack/modules/nova.py b/clearstack/modules/nova.py new file mode 100644 index 0000000..a551457 --- /dev/null +++ b/clearstack/modules/nova.py @@ -0,0 +1,95 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Obed Muñoz +# Author: Victor Morales +# +# 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. +# + +from modules.openstack import OpenStackService +from modules.ceilometer import Ceilometer +from modules.conf import CONF +from common import util +from common.util import LOG +from common.singleton import Singleton + + +@Singleton +class Nova(OpenStackService): + _name = "nova" + _bundle = "openstack-compute-controller" + _services = ["nova-cert", "nova-consoleauth", "nova-scheduler", + "nova-conductor", "nova-novncproxy"] + if CONF['CONFIG_HTTP_SERVICE'] == 'nginx': + _services.extend(["nginx", "uwsgi@nova-api.socket", + "uwsgi@nova-metadata.socket"]) + elif CONF['CONFIG_HTTP_SERVICE'] == 'apache2': + _services.append("httpd") + else: + _services.append("nova-api") + _type = "compute" + _description = "OpenStack Compute" + _password = CONF['CONFIG_NOVA_KS_PW'] + _public_url = ("http://{0}:8774/v2/%\(tenant_id\)s" + .format(CONF['CONFIG_CONTROLLER_HOST'])) + + def install_compute(self): + self.install("openstack-compute") + + def sync_database(self): + LOG.debug("syncing database") + util.run_command("su -s /bin/sh -c \"nova-manage db sync\" nova") + + def create_network(self): + LOG.debug("Creating network") + command = ("nova-manage network create --bridge=br100" + "--label=demo-net --fixed_range_v4=%s" + % CONF['CONFIG_NOVA_NETWORK_FIXEDRANGE']) + try: + util.run_command("nova network-show demo-net", environ=self._env) + except: + command = ("nova-manage network create --bridge=br100 " + "--label=demo-net --fixed_range_v4=%s" + % CONF['CONFIG_NOVA_NETWORK_FIXEDRANGE']) + if util.str2bool(CONF['CONFIG_NOVA_NETWORK_MULTIHOST']): + command += " --multi_host=T" + util.run_command(command, environ=self._env) + + def create_floating_ips(self): + LOG.debug("Creating floating ips") + try: + util.run_command("nova floating-ip-pool-list | grep nova") + except: + cmd = ("nova-manage floating create --pool nova " + "--ip_range=%s" % CONF['CONFIG_NOVA_NETWORK_FLOATRANGE']) + util.run_command(cmd, environ=self._env) + + def ceilometer_enable(self, configfile): + ceilometer = Ceilometer.get() + ceilometer_cfg = "/etc/ceilometer/ceilometer.conf" + + ceilometer.install() + ceilometer.config_rabbitmq(ceilometer_cfg) + ceilometer.config_auth(ceilometer_cfg) + ceilometer.config_service_credentials(ceilometer_cfg) + + config = ("[DEFAULT]\n" + "instance_usage_audit = True\n" + "instance_usage_audit_period = hour\n" + "notify_on_state_change = vm_and_task_state\n" + "notification_driver = messagingv2\n") + util.write_config(configfile, config) + self.start_server(['ceilometer-agent-compute.service']) diff --git a/clearstack/modules/openstack.py b/clearstack/modules/openstack.py new file mode 100644 index 0000000..c3ea6bb --- /dev/null +++ b/clearstack/modules/openstack.py @@ -0,0 +1,195 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Obed Muñoz +# Author: Victor Morales +# +# 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 socket + +from common import util +from common.swupd import Client as swupd_client +from common.util import LOG +from modules.conf import CONF +from modules.conf import ENV + + +class OpenStackService: + _env = "" + _internal_url = "" + _admin_url = "" + _region = "" + _user = "" + _controller = socket.gethostbyaddr(CONF["CONFIG_CONTROLLER_HOST"])[0] + _password = "" + + def __init__(self): + if not self._env: + self._env = ENV + if not self._user: + self._user = self._name + if not self._password: + self._password = CONF['CONFIG_%s_KS_PW' % self._name.upper()] + if not self._internal_url: + self._internal_url = self._public_url + if not self._admin_url: + self._admin_url = self._public_url + if not self._region: + self._region = CONF['CONFIG_KEYSTONE_REGION'] + + def install(self, bundle=None): + bundle = bundle or self._bundle + swupd_client.install(bundle) + + def start_server(self, services=None): + services = services or self._services + LOG.debug("Starting services") + util.run_command("systemctl enable %s" % " ".join(services)) + for service in services: + if service.endswith(".socket"): + util.run_command("systemctl stop %s" % + service.replace(".socket", ".service")) + util.run_command("systemctl restart %s" % service) + + def create_service(self, name=None, description=None, type=None): + name = name or self._name + description = description or self._description + type = type or self._type + LOG.debug("setting up %s service" % name) + try: + """ test if service already exists """ + util.run_command("openstack service show %s" % name, + environ=self._env) + except: + util.run_command("openstack service create %s" + " --name %s --description \"%s\"" + % (type, name, description), + environ=self._env) + + def create_endpoint(self, publicurl=None, + internalurl=None, + adminurl=None, + region=None, + type=None): + publicurl = publicurl or self._public_url + internalurl = internalurl or self._internal_url + adminurl = adminurl or self._admin_url + region = region or self._region + type = type or self._type + + LOG.debug("creating endpoint") + """ test if endpoint already exists """ + out, err = util.run_command("openstack endpoint list | grep %s" + % self._name, environ=self._env) + if not out: + cmd = ("openstack endpoint create --region {0} {1}" + .format(region, type)) + util.run_command(cmd + " public {0}".format(publicurl), + environ=self._env) + util.run_command(cmd + " internal {0}".format(internalurl), + environ=self._env) + util.run_command(cmd + " admin {0}".format(adminurl), + environ=self._env) + + def create_role(self, role): + LOG.debug("setting up %s role" % role) + try: + util.run_command("openstack role show %s" % role, + environ=self._env) + except: + util.run_command("openstack role create %s" % role, + environ=self._env) + + def add_role(self, user, role, project="service", domain="default"): + # openstack role add: error: argument --project: not allowed with + # argument --domain + cmd = "openstack role add --user {0} {1}".format(user, role) + if domain != "default": + cmd += " --domain %s" % domain + else: + cmd += " --project %s" % project + util.run_command(cmd, environ=self._env) + + def create_user(self, user=None, password=None, domain="default", + project="service", role="admin"): + user = user or self._user + password = password or self._password + LOG.debug("setting up %s user" % user) + try: + """ test if user already exists """ + util.run_command("openstack user show %s" % user, + environ=self._env) + except: + util.run_command("openstack user create --domain {0}" + " --password={1}" + " --email {2}@example.com {2}" + .format(domain, password, user), + environ=self._env, debug=False) + self.add_role(user, role, project, domain) + + def create_domain(self, domain, description): + LOG.debug("setting up %s domain" % domain) + try: + """ test if domain already exists """ + util.run_command("openstack domain show %s" % domain, + environ=self._env) + except: + util.run_command("openstack domain create --description \"%s\" %s" + % (description, domain), + environ=self._env, debug=False) + + def config_debug(self, configfile): + if util.str2bool(CONF['CONFIG_DEBUG_MODE']): + config = "[DEFAULT]\n" + \ + "debug=True\n" + \ + "verbose=True\n" + util.write_config(configfile, config) + + def config_database(self, configfile): + dbpass = CONF['CONFIG_%s_DB_PW' % self._name.upper()] + dbhost = socket.gethostbyaddr(CONF['CONFIG_MARIADB_HOST'])[0] + config = ("[database]\n" + "connection=mysql://{0}:{1}@{2}/{0}" + .format(self._name, dbpass, dbhost)) + util.write_config(configfile, config) + + def config_rabbitmq(self, configfile): + rabbit_host = socket.gethostbyaddr(CONF['CONFIG_AMQP_HOST'])[0] + rabbit_password = CONF['CONFIG_AMQP_AUTH_PASSWORD'] + rabbit_user = CONF['CONFIG_AMQP_AUTH_USER'] + config = \ + "[DEFAULT]\n" + \ + "rpc_backend=rabbit\n" + \ + "[oslo_messaging_rabbit]\n" + \ + "rabbit_host=%s\n" % rabbit_host + \ + "rabbit_userid=%s\n" % rabbit_user + \ + "rabbit_password=%s\n" % rabbit_password + \ + "send_single_reply=True" + util.write_config(configfile, config) + + def config_auth(self, configfile, section='keystone_authtoken'): + config = ("[{0}]\n" + "auth_uri=http://{1}:5000\n" + "auth_url=http://{1}:35357\n" + "auth_plugin=password\n" + "project_domain_id=default\n" + "user_domain_id=default\n" + "project_name=service\n" + "username={2}\n" + "password={3}" + .format(section, self._controller, self._name, + self._password)) + util.write_config(configfile, config) diff --git a/clearstack/modules/rabbitmq.py b/clearstack/modules/rabbitmq.py new file mode 100644 index 0000000..04c2ef9 --- /dev/null +++ b/clearstack/modules/rabbitmq.py @@ -0,0 +1,71 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Victor Morales +# +# 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. +# + +from common import util +from common.singleton import Singleton +from common.swupd import Client as swupd_client +from common.util import LOG + + +@Singleton +class Rabbitmq: + def __init__(self): + hostname, stderr = util.run_command("hostname") + util.write_in_file_ine("/etc/hosts", "127.0.0.1 {0}" + .format(str(hostname.decode("utf-8")[:-1]))) + + def user_exists(self, user): + stdout = None + stderr = None + try: + stdout, stderr = util.run_command("rabbitmqctl list_users" + " | grep {0}".format(user)) + except: + pass + + if not stdout: + return False + + return True + + def install(self): + swupd_client.install("message-broker-rabbitmq") + + def start_server(self): + LOG.debug("starting services") + util.run_command("systemctl enable rabbitmq-server") + util.run_command("systemctl restart rabbitmq-server") + + def add_user(self, auth_user, auth_pw): + """ todo: what we do with guest """ + LOG.debug("adding user") + if not self.user_exists(auth_user): + util.run_command("rabbitmqctl add_user {0} {1}" + .format(auth_user, auth_pw), debug=False) + + def delete_user(self, user): + LOG.debug("deleting user") + util.run_command("rabbitmqctl delete_user {1}" + .format(user)) + + def set_permissions(self, auth_user, permissions): + LOG.debug("setting permissions") + util.run_command("rabbitmqctl set_permissions {0} {1}" + .format(auth_user, permissions)) diff --git a/clearstack/modules/swift.py b/clearstack/modules/swift.py new file mode 100644 index 0000000..59cb6f6 --- /dev/null +++ b/clearstack/modules/swift.py @@ -0,0 +1,202 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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 shutil + +from common import util +from modules.openstack import OpenStackService +from modules.conf import CONF +from common.singleton import Singleton + + +@Singleton +class Swift(OpenStackService): + _name = "swift" + _bundle = "openstack-object-storage" + _services = ["swift-proxy", "memcached"] + _type = "object-store" + _description = "OpenStack Object Storage" + _public_url = ("http://{0}:8080/v1/AUTH_%\(tenant_id\)s" + .format(CONF['CONFIG_CONTROLLER_HOST'])) + _admin_url = "http://{0}:8080/v1".format(CONF['CONFIG_CONTROLLER_HOST']) + + _devices = [] + + def ceilometer_enable(self, configfile): + pass + + def config_auth(self, configfile): + confdir = os.path.dirname(configfile) + if not os.path.isdir(confdir): + os.makedirs(confdir) + shutil.copy('/usr/share/defaults/swift/proxy-server.conf', confdir) + OpenStackService.config_auth(self, configfile, + section='filter:authtoken') + config = ("[pipeline:main]\n" + "pipeline = catch_errors gatekeeper healthcheck " + "proxy-logging cache container_sync bulk ratelimit " + "authtoken keystoneauth container-quotas account-quotas " + "slo dlo versioned_writes proxy-logging proxy-server\n" + "[filter:authtoken]\n" + "paste.filter_factory = " + "keystonemiddleware.auth_token:filter_factory\n" + "delay_auth_decision = True\n" + "[app:proxy-server]\n" + "use = egg:swift#proxy\n" + "account_autocreate = True\n" + "[filter:keystoneauth]\n" + "use = egg:swift#keystoneauth\n" + "operator_roles = admin,user\n") + util.write_config(configfile, config) + + def config_memcache(self, configfile): + config = ("[filter:cache]\n" + "use = egg:swift#memcache\n" + "memcache_servers = 127.0.0.1:11211") + util.write_config(configfile, config) + + def config_hash(self, configfile): + suffix = CONF['CONFIG_SWIFT_HASH'] + config = ("[swift-hash]\n" + "swift_hash_path_suffix = {0}").format(suffix) + util.write_config(configfile, config) + + def parse_devices(self): + + def create_loopback(name, size): + # Remove the file if exists + if os.path.isfile(name): + os.remove(name) + # Create an empty file + with open(name, 'wb') as f: + f.seek(1024 * 1024 * 1024 * size - 1) + f.write(b"\0") + util.run_command("mkfs.xfs %s" % name) + + devs = CONF['CONFIG_SWIFT_STORAGES'] + if devs: + devs = devs.split(',') + devs = [x.strip() for x in devs] + device_number = 0 + num_zones = int(CONF["CONFIG_SWIFT_STORAGE_ZONES"]) + for dev in devs: + device_number += 1 + zone = (device_number % num_zones) + 1 + self._devices.append({'device': dev, 'zone': zone, + 'name': 'device%s' % device_number}) + else: + # Setup loopdevice + filename = '/srv/loopback-device' + filesize = int(CONF['CONFIG_SWIFT_STORAGE_SIZE']) + create_loopback(filename, filesize) + self._devices.append({'device': filename, 'zone': 1, + 'name': 'loopback'}) + + def prepare_devices(self): + # Avoid adding duplicate entries in fstab + mounted_devices = [] + if os.path.isfile('/etc/fstab'): + with open('/etc/fstab', 'r') as f: + for l in f: + mounted_devices.append(l.split()[0].strip()) + + # Add each device to fstab if not already added + fstab = "" + for device in self._devices: + # Format the device with xfs filesystem + util.ensure_directory('/srv/node/%s' % device['name']) + + # Ensure the device is mounted + if device['device'] not in mounted_devices: + fstab += ("{0} /srv/node/{1} xfs noatime,nodiratime,nobarrier," + "logbufs=8 0 2\n").format(device['device'], + device['name']) + with open('/etc/fstab', 'a') as f: + f.write(fstab) + + util.run_command('mount -a') + # Change ownership of /srv to swift + util.run_command("chown -R swift:swift /srv") + + def create_rings(self): + replicas = CONF['CONFIG_SWIFT_STORAGE_REPLICAS'] + ip = util.get_ip() + for ringtype, port in [('object', '6000'), + ('container', '6001'), + ('account', 6002)]: + cmd = ("swift-ring-builder {0}.builder create 10 {1} 1" + .format(ringtype, replicas)) + util.run_command(cmd) + for device in self._devices: + cmd = ("swift-ring-builder %s.builder add --region 1 " + "--zone %s --ip %s --port %s --device %s --weight 100" + % (ringtype, device['zone'], ip, port, device['name'])) + util.run_command(cmd) + cmd = "swift-ring-builder {0}.builder rebalance".format(ringtype) + util.run_command(cmd) + if os.path.isfile("/etc/swift/%s.ring.gz" % ringtype): + os.remove("/etc/swift/%s.ring.gz" % ringtype) + shutil.move("%s.ring.gz" % ringtype, "/etc/swift") + + def config_storage_services(self): + confdir = '/etc/swift/' + shutil.copy('/usr/share/defaults/swift/account-server.conf', confdir) + shutil.copy('/usr/share/defaults/swift/container-server.conf', confdir) + shutil.copy('/usr/share/defaults/swift/object-server.conf', confdir) + shutil.copy('/usr/share/defaults/swift/container-reconciler.conf', + confdir) + shutil.copy('/usr/share/defaults/swift/object-expirer.conf', confdir) + for type in ['account', 'container', 'object']: + conf = ("[DEFAULT]\n" + "bind_ip = 0.0.0.0\n" + "devices = /srv/node\n" + "[pipeline:main]\n" + "pipeline = healthcheck recon {0}-server\n" + "[filter:recon]\n" + "recon_cache_path = /var/cache/swift\n").format(type) + util.write_config('/etc/swift/%s-server.conf' % type, conf) + + def config_rsync(self): + config = """ +uid = swift +gid = swift +log file = /var/log/rsyncd.log +pid file = /var/run/rsyncd.pid +address = {0} + +[account] +max connections = 2 +path = /srv/node/ +read only = false +lock file = /var/lock/account.lock + +[container] +max connections = 2 +path = /srv/node/ +read only = false +lock file = /var/lock/container.lock + +[object] +max connections = 2 +path = /srv/node/ +read only = false +lock file = /var/lock/object.lock +""".format(util.get_ip()) + with open('/etc/rsyncd.conf', 'w') as f: + f.write(config) diff --git a/clearstack/plugins/__init__.py b/clearstack/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clearstack/plugins/amqp_100.py b/clearstack/plugins/amqp_100.py new file mode 100644 index 0000000..4de429d --- /dev/null +++ b/clearstack/plugins/amqp_100.py @@ -0,0 +1,59 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# +# 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. +# + +from clearstack import utils +from clearstack import validators +from clearstack.argument import Argument +from clearstack.controller import Controller +from clearstack.common import util + + +def init_config(): + conf = { + "AMQP": [ + Argument("amqp-auth-user", + "User for amqp authentication", + "CONFIG_AMQP_AUTH_USER", + "amqp_user", + validators=[validators.not_empty]), + Argument("amqp-auth-pw", + "Password for amqp user authentication", + "CONFIG_AMQP_AUTH_PASSWORD", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("amqp-host", + "The IP address or hostname of the server on which" + " to install the AMQP service", + "CONFIG_AMQP_HOST", + util.get_ip(), + validators=[validators.ip_or_hostname]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + Controller.get().add_sequence("Setting up rabbit", setup_rabbit) + + +def setup_rabbit(): + conf = Controller.get().CONF + recipe = utils.get_template("rabbitmq") + return utils.run_recipe("rabbitmq.py", recipe, [conf["CONFIG_AMQP_HOST"]]) diff --git a/clearstack/plugins/ceilometer_800.py b/clearstack/plugins/ceilometer_800.py new file mode 100644 index 0000000..3d303be --- /dev/null +++ b/clearstack/plugins/ceilometer_800.py @@ -0,0 +1,58 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from clearstack import utils +from clearstack import validators +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack.common import util + + +def init_config(): + conf = { + "CEILOMETER": [ + Argument("ceilometer-db-pw", + "Password for ceilometer to access DB", + "CONFIG_CEILOMETER_DB_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("ceilometer-ks-pw", + "Password to use for Ceilometer to" + " authenticate with Keystone", + "CONFIG_CEILOMETER_KS_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + conf = controller.CONF + if util.str2bool(conf['CONFIG_CEILOMETER_INSTALL']): + controller.add_sequence("Setting up ceilometer", setup_ceilometer) + + +def setup_ceilometer(): + conf = Controller.get().CONF + recipe = utils.get_template("ceilometer") + return utils.run_recipe("ceilometer.py", recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) diff --git a/clearstack/plugins/glance_300.py b/clearstack/plugins/glance_300.py new file mode 100644 index 0000000..d20fa56 --- /dev/null +++ b/clearstack/plugins/glance_300.py @@ -0,0 +1,59 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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. +# + +from clearstack import utils +from clearstack import validators +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack.common import util + + +def init_config(): + conf = { + "GLANCE": [ + Argument("glance-db-pw", + "Password for glance to access DB", + "CONFIG_GLANCE_DB_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("glance-ks-pw", + "Password to use for Glance to" + " authenticate with Keystone", + "CONFIG_GLANCE_KS_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + conf = controller.CONF + if util.str2bool(conf['CONFIG_GLANCE_INSTALL']): + controller.add_sequence("Setting up glance", setup_glance) + + +def setup_glance(): + conf = Controller.get().CONF + recipe = utils.get_template("glance") + return utils.run_recipe("glance.py", recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) diff --git a/clearstack/plugins/heat_650.py b/clearstack/plugins/heat_650.py new file mode 100644 index 0000000..562282b --- /dev/null +++ b/clearstack/plugins/heat_650.py @@ -0,0 +1,75 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from clearstack import utils +from clearstack import validators +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack.common import util + + +def init_config(): + conf = { + "HEAT": [ + Argument("heat-db-pw", + "Password for heat to access DB", + "CONFIG_HEAT_DB_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("heat-ks-pw", + "Password to use for Heat to" + " authenticate with Keystone", + "CONFIG_HEAT_KS_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("heat-domain", + "Name of the Identity domain for Orchestration.", + "CONFIG_HEAT_DOMAIN", + "heat", + validators=[validators.not_empty]), + Argument("heat-domain-admin", + "Name of the Identity domain administrative user for " + "Orchestration.", + "CONFIG_HEAT_DOMAIN_ADMIN", + "heat_domain_admin", + validators=[validators.not_empty]), + Argument("heat-domain-password", + "Password for the Identity domain administrative user " + "for Orchestration", + "CONFIG_HEAT_DOMAIN_PASSWORD", + utils.generate_random_pw(), + validators=[validators.not_empty]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + conf = controller.CONF + if util.str2bool(conf['CONFIG_HEAT_INSTALL']): + controller.add_sequence("Setting up heat", setup_heat) + + +def setup_heat(): + conf = Controller.get().CONF + recipe = utils.get_template("heat") + return utils.run_recipe("heat.py", recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) diff --git a/clearstack/plugins/horizon_500.py b/clearstack/plugins/horizon_500.py new file mode 100644 index 0000000..7772e7a --- /dev/null +++ b/clearstack/plugins/horizon_500.py @@ -0,0 +1,43 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from clearstack import utils +from clearstack.controller import Controller +from clearstack.common import util + + +def init_config(): + conf = {} + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + conf = controller.CONF + if util.str2bool(conf['CONFIG_HORIZON_INSTALL']): + controller.add_sequence("Setting up horizon", setup_horizon) + + +def setup_horizon(): + template = "horizon" + conf = Controller.get().CONF + recipe = utils.get_template(template) + return utils.run_recipe("{0}.py".format(template), recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) diff --git a/clearstack/plugins/keystone_200.py b/clearstack/plugins/keystone_200.py new file mode 100644 index 0000000..1e64d83 --- /dev/null +++ b/clearstack/plugins/keystone_200.py @@ -0,0 +1,71 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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 uuid + +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack import utils +from clearstack import validators + + +def init_config(): + conf = { + "KEYSTONE": [ + Argument("keystone-db-pw", + "Password for keystone to access DB", + "CONFIG_KEYSTONE_DB_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("keystone-admin-token", + "The token to use for the Keystone service api", + "CONFIG_KEYSTONE_ADMIN_TOKEN", + uuid.uuid4().hex, + validators=[validators.not_empty]), + Argument("keystone-admin-pw", + "The password to use for the Keystone admin user", + "CONFIG_KEYSTONE_ADMIN_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("keystone-demo-pw", + "The password to use for the Keystone demo user", + "CONFIG_KEYSTONE_DEMO_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("keystone-region", + "Region name", + "CONFIG_KEYSTONE_REGION", + "RegionOne", + validators=[validators.not_empty]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + Controller.get().add_sequence("Setting up keystone", setup_keystone) + + +def setup_keystone(): + conf = Controller.get().CONF + recipe = utils.get_template('keystone') + return utils.run_recipe("keystone.py", recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) diff --git a/clearstack/plugins/mariadb_150.py b/clearstack/plugins/mariadb_150.py new file mode 100644 index 0000000..72c69b4 --- /dev/null +++ b/clearstack/plugins/mariadb_150.py @@ -0,0 +1,62 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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. +# + +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack import utils +from clearstack import validators +from clearstack.common import util + + +def init_config(): + conf = { + "MARIADB": [ + Argument("mariadb-host", + "The IP address or hostname of the MariaDB server", + "CONFIG_MARIADB_HOST", + util.get_ip(), + validators=[validators.ip_or_hostname]), + Argument("mariadb-user", + "User for mariadb authentication", + "CONFIG_MARIADB_USER", + "root", + validators=[validators.not_empty]), + Argument("mariadb-pw", + "Password for mariadb user", + "CONFIG_MARIADB_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + controller.add_sequence("Setting up mariadb", + setup_mariadb, args=['mariadb']) + + +def setup_mariadb(template): + conf = Controller.get().CONF + recipe = utils.get_template(template) + return utils.run_recipe("{0}.py".format(template), recipe, + [conf["CONFIG_MARIADB_HOST"]]) diff --git a/clearstack/plugins/mongodb_170.py b/clearstack/plugins/mongodb_170.py new file mode 100644 index 0000000..fec062b --- /dev/null +++ b/clearstack/plugins/mongodb_170.py @@ -0,0 +1,52 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack import utils +from clearstack import validators +from clearstack.common import util + + +def init_config(): + conf = { + "MONGODB": [ + Argument("mongodb-host", + "The IP address or hostname of the MongoDB server", + "CONFIG_MONGODB_HOST", + util.get_ip(), + validators=[validators.ip_or_hostname]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + conf = controller.CONF + if util.str2bool(conf['CONFIG_CEILOMETER_INSTALL']): + controller.add_sequence("Setting up MongoDB", setup_mongodb) + + +def setup_mongodb(): + conf = Controller.get().CONF + recipe = utils.get_template('mongodb') + return utils.run_recipe("mongodb.py", recipe, + [conf["CONFIG_MONGODB_HOST"]]) diff --git a/clearstack/plugins/neutron_450.py b/clearstack/plugins/neutron_450.py new file mode 100644 index 0000000..6b5e146 --- /dev/null +++ b/clearstack/plugins/neutron_450.py @@ -0,0 +1,159 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from clearstack import utils +from clearstack import validators +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack.common import util + + +def init_config(): + conf = { + "NEUTRON": [ + Argument("neutron-ks-pw", + "Password to use for OpenStack Networking (neutron) to" + " authenticate with the Identity service.", + "CONFIG_NEUTRON_KS_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("neutron-db-pw", + "The password to use for OpenStack Networking to access " + "the database.", + "CONFIG_NEUTRON_DB_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + # Argument('neutron-l3-ext-bridge', + # 'The name of the Open vSwitch bridge (or empty for ' + # 'linuxbridge) for the OpenStack Networking L3 agent to ' + # 'use for external traffic', + # 'CONFIG_NEUTRON_L3_EXT_BRIDGE', + # 'br-ex', + # validators=[validators.not_empty]), + Argument('neutron-metadata-pw', + 'Password for the OpenStack Networking metadata agent', + 'CONFIG_NEUTRON_METADATA_PW', + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument('neutron-lbaas-install', + "Specify 'y' to install OpenStack Networking's " + "Load-Balancing-as-a-Service (LBaaS)", + "CONFIG_LBAAS_INSTALL", + 'n', + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument('neutron-vpnass-install', + "Specify 'y' to install OpenStack Networking's " + "VPN-as-a-Service (VPNaaS)", + "CONFIG_VPNAAS_INSTALL", + 'n', + options=['y', 'n'], + validators=[validators.y_or_n]) + ], + # "NEUTRON_OVS_AGENT": [ + # Argument("neutron-ovs-bridge-mappings", + # "Comma-separated list of bridge mappings for the " + # "OpenStack Networking Open vSwitch plugin. Each tuple " + # "in the list must be in he format " + # " :", + # "CONFIG_NEUTRON_OVS_BRIDGE_MAPPINGS", + # "external:br-ex"), + # Argument("neutron-ovs-bridge-interfaces", + # "Comma-separated list of colon-separated Open vSwitch" + # " : pairs.", + # "CONFIG_NEUTRON_OVS_BRIDGE_IFACES", + # "br-ex:%s" % util.get_net_interface()) + # ], + "NEUTRON_LINUXBRIDGE_AGENT": [ + Argument("neutron-linuxbridge-physical-interface-mappings", + "Colon separated linuxbridge : pairs", + "CONFIG_NEUTRON_LINUXBRIDGE_IFACES", + "public:%s" % util.get_net_interface()) + ], + "NEUTRON_ML2_PLUGIN": [ + Argument("neutron-ml2-type-drivers", + "Comma-separated list of network-type driver entry " + "points to be loaded from the neutron.ml2.type_drivers " + "namespace", + "CONFIG_NEUTRON_ML2_TYPE_DRIVERS", + "flat,vlan,vxlan", + options=['local', 'flat', 'vlan', 'gre', 'vxlan'], + validators=[validators.not_empty]), + Argument("neutron-ml2-tenant-network-types", + "Comma-separated, ordered list of network types to " + "allocate as tenant networks", + "CONFIG_NEUTRON_ML2_TENANT_NETWORK_TYPES", + "vxlan", + options=['local', 'vlan', 'gre', 'vxlan'], + validators=[validators.not_empty]), + Argument("neutron-ml2-mechanism-drivers", + "Comma-separated, ordered list of network types to " + "allocate as tenant networks", + "CONFIG_NEUTRON_ML2_MECHANISM_DRIVERS", + "linuxbridge,l2population", + options=['openvswitch', 'linuxbridge'], + validators=[validators.not_empty]), + Argument("neutron-ml2-flat-networks", + "Comma-separated list of physical_network names with " + "wich flat networks can be created.", + "CONFIG_NEUTRON_ML2_FLAT_NETWORKS", + "public", + validators=[validators.not_empty]), + Argument("neutron-ml2-tunnel-id-ranges", + "Comma-separated list of : tuples " + "enumerating ranges of GRE tunnel IDs that are available " + "for tenant-network allocation.", + "CONFIG_NEUTRON_ML2_TUNNEL_ID_RANGES", + "1:1000", + validators=[validators.not_empty]), + Argument("neutron-l2-agent", + "Name of the L2 agent to be used with OpenStack " + "Networking", + "CONFIG_NEUTRON_L2_AGENT", + "linuxbridge", + options=['openvswitch', 'linuxbridge'], + validators=[validators.not_empty]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + conf = controller.CONF + if util.str2bool(conf['CONFIG_NEUTRON_INSTALL']): + controller.add_sequence("Setting up neutron controller", + setup_controller) + controller.add_sequence("Setting up neutron on computes nodes", + setup_compute_nodes) + + +def setup_controller(): + conf = Controller.get().CONF + recipe = utils.get_template('neutron') + return utils.run_recipe("neutron.py", recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) + + +def setup_compute_nodes(): + conf = Controller.get().CONF + recipe = utils.get_template('neutron_compute') + return utils.run_recipe("neutron_compute.py", recipe, + conf["CONFIG_COMPUTE_HOSTS"].split(',')) diff --git a/clearstack/plugins/nova_400.py b/clearstack/plugins/nova_400.py new file mode 100644 index 0000000..be812b7 --- /dev/null +++ b/clearstack/plugins/nova_400.py @@ -0,0 +1,114 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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. +# + +from clearstack import utils +from clearstack import validators +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack.common import util + + +def init_config(): + conf = { + "NOVA": [ + Argument("nova-db-pw", + "Password for nova to access DB", + "CONFIG_NOVA_DB_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("nova-ks-pw", + "Password to use for the Nova to" + " authenticate with Keystone", + "CONFIG_NOVA_KS_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]) + ], + "NOVA_NETWORK": [ + Argument("nova-compute-privif", + "Private interface for Flat DHCP on" + " the Nova compute servers", + "CONFIG_NOVA_COMPUTE_PRIVIF", + util.get_net_interface(), + validators=[validators.not_empty]), + Argument("nova-network-manager", + "Nova network manager", + "CONFIG_NOVA_NETWORK_MANAGER", + "nova.network.manager.FlatDHCPManager", + validators=[validators.not_empty]), + Argument("nova-network-pubif", + "Public interface on the Nova network server", + "CONFIG_NOVA_NETWORK_PUBIF", + util.get_net_interface(), + validators=[validators.not_empty]), + Argument("nova-network-fixed-range", + "IP Range for flat DHCP.", + "CONFIG_NOVA_NETWORK_FIXEDRANGE", + "192.168.32.0/22", + validators=[validators.cidr]), + Argument("nova-network-floating-range", + "IP Range for floating IP addresses.", + "CONFIG_NOVA_NETWORK_FLOATRANGE", + "10.3.4.0/22", + validators=[validators.cidr]), + Argument("nova-network-auto-assign-floating-ip", + "Specify 'y' to automatically assign a floating IP to " + "new instances ['y', 'n']", + "CONFIG_NOVA_NETWORK_AUTOASSIGNFLOATINGIP", + "n", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("nova-network-size", + "Number of addresses in each private subnet", + "CONFIG_NOVA_NETWORK_SIZE", + "255", + validators=[validators.digit]), + Argument("nova-network-multihost", + "Nova network multihost", + "CONFIG_NOVA_NETWORK_MULTIHOST", + "True", + validators=[validators.true_or_false]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + conf = controller.CONF + if util.str2bool(conf['CONFIG_NOVA_INSTALL']): + controller.add_sequence("Setting up nova controller", + setup_controller) + controller.add_sequence("Setting up nova computes", + setup_computes) + + +def setup_controller(): + conf = Controller.get().CONF + recipe = utils.get_template('nova') + return utils.run_recipe("nova.py", recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) + + +def setup_computes(): + conf = Controller.get().CONF + recipe = utils.get_template('nova_compute') + return utils.run_recipe("nova_compute.py", recipe, + conf["CONFIG_COMPUTE_HOSTS"].split(',')) diff --git a/clearstack/plugins/pre_000.py b/clearstack/plugins/pre_000.py new file mode 100644 index 0000000..3b6ca74 --- /dev/null +++ b/clearstack/plugins/pre_000.py @@ -0,0 +1,191 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Obed Muñoz +# +# 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 + +from clearstack.argument import Argument +from clearstack.controller import Controller +from clearstack import validators +from clearstack import utils +from clearstack.common import util + + +def validate_ssh_key(ssh_key): + hosts = utils.get_all_hosts() + util.remove_localhost(hosts) + if len(hosts) != 0: + validators.file(ssh_key) + + +def init_config(): + home = os.getenv('HOME') + if home is None: + home = "/root" + conf = { + "GENERAL": [ + Argument("debug-mode", + "Set to 'y' if you want to run" + " OpenStack services in debug mode", + "CONFIG_DEBUG_MODE", + "n", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("ssh-private-key", + "Path to the private SSH key file", + "CONFIG_PRIVATE_SSH_KEY", + "", + validators=[validate_ssh_key]), + Argument("controller-host", + "IP address or hostname of the server on which " + " to install OpenStack services specific to" + " controller role such as API servers", + "CONFIG_CONTROLLER_HOST", + util.get_ip(), + validators=[validators.ip_or_hostname]), + Argument("clean-controller-host", + "Set 'y' if you would like Clearstack to " + "clean up controller host", + "CONFIG_CLEAN_CONTROLLER_HOST", + "y", + validators=[validators.y_or_n]), + Argument("compute-hosts", + "List of IP addresses or hostnames of the server on which" + " to install the Nova compute service", + "CONFIG_COMPUTE_HOSTS", + util.get_ip(), + validators=[validators.ip_or_hostname]), + Argument("clean-compute-hosts", + "Set 'y' if you would like Clearstack to " + "clean up compute hosts", + "CONFIG_CLEAN_COMPUTE_HOSTS", + "y", + validators=[validators.y_or_n]), + Argument("mariadb-install", + "Set 'y' if you would like Clearstack to install MariaDB", + "CONFIG_MARIADB_INSTALL", + "y", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("mongodb-install", + "Set 'y' if you would like Clearstack to install MongoDB", + "CONFIG_MONGODB_INSTALL", + "y", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("http-service", + "Set 'apache2' or 'nginx' as HTTP Service for Horizon, " + "Keystone and Nova API web services", + "CONFIG_HTTP_SERVICE", + "nginx", + options=['nginx', 'apache2'], + validators=[validators.not_empty]), + Argument("glance-install ", + "Set 'y' if you would like Clearstack to install" + " Openstack image (glance)", + "CONFIG_GLANCE_INSTALL", + "y", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("nova-install ", + "Set 'y' if you would like Clearstack to install" + " Openstack compute (nova)", + "CONFIG_NOVA_INSTALL", + "y", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("horizon-install", + "Set 'y' if you would like Clearstack to install" + " OpenStack dashboard (horizon)", + "CONFIG_HORIZON_INSTALL", + "y", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("neutron-install", + "Set 'y' if you would like Clearstack to install" + " OpenStack Networking (neutron)", + "CONFIG_NEUTRON_INSTALL", + "y", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("ceilometer-install", + "Set 'y' if you would like Clearstack to install" + " OpenStack Telemetry (ceilometer)", + "CONFIG_CEILOMETER_INSTALL", + "y", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("heat-install", + "Set 'y' if you would like Clearstack to install" + " OpenStack Orchestration (heat)", + "CONFIG_HEAT_INSTALL", + "n", + options=['y', 'n'], + validators=[validators.y_or_n]), + Argument("swift-install", + "Set 'y' if you would like Clearstack to install" + " OpenStack Object Storage (swift)", + "CONFIG_SWIFT_INSTALL", + "n", + options=['y', 'n'], + validators=[validators.y_or_n]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + Controller.get().add_sequence("Setting up controller host", + setup_controller) + Controller.get().add_sequence("Setting up compute hosts", + setup_computes) + + +def setup_controller(): + conf = Controller.get().CONF + if util.str2bool(conf['CONFIG_CLEAN_CONTROLLER_HOST']): + try: + utils.run_recipe("{0}".format("controller_cleanup.sh"), + utils.get_template("controller_cleanup.sh"), + [conf["CONFIG_CONTROLLER_HOST"]]) + except: + pass + + template = "pre_controller.sh" + recipe = utils.get_template(template) + return utils.run_recipe("{0}".format(template), recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) + + +def setup_computes(): + conf = Controller.get().CONF + if util.str2bool(conf['CONFIG_CLEAN_COMPUTE_HOSTS']): + try: + utils.run_recipe("{0}".format("compute_cleanup.sh"), + utils.get_template("compute_cleanup.sh"), + conf["CONFIG_COMPUTE_HOSTS"].split(',')) + except: + pass + + template = "pre_compute.sh" + recipe = utils.get_template(template) + return utils.run_recipe("{0}".format(template), recipe, + conf["CONFIG_COMPUTE_HOSTS"].split(',')) diff --git a/clearstack/plugins/provision_700.py b/clearstack/plugins/provision_700.py new file mode 100644 index 0000000..461b73e --- /dev/null +++ b/clearstack/plugins/provision_700.py @@ -0,0 +1,86 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Victor Morales +# +# 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. +# + +from clearstack import utils +from clearstack import validators +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack.common import util + + +def init_config(): + conf = { + "PROVISION_INIT": [ + Argument("provision-demo", + "Specify 'y' to provision for demo usage and testing." + " ['y', 'n']", + "CONFIG_PROVISION_DEMO", + "n", + options=['y', 'n'], + validators=[validators.y_or_n]) + ], + "PROVISION_DEMO": [ + Argument("provision-demo-floatrange", + "CIDR network address for the floating IP subnet.", + "CONFIG_PROVISION_DEMO_FLOATRANGE", + "172.24.4.224/28", + validators=[validators.cidr]), + Argument("privision-image-format", + 'Format for the demo image (default "qcow2").', + "CONFIG_PROVISION_IMAGE_FORMAT", + "qcow2", + validators=[validators.not_empty]), + Argument("provision-image-name", + 'The name to be assigned to the demo image in Glance ' + '(default "cirros")', + "CONFIG_PROVISION_IMAGE_NAME", + "cirros", + validators=[validators.not_empty]), + Argument("provision-image-url", + 'A URL or local file location for an image to download' + 'and provision in Glance (defaults to a URL for a ' + 'recent "cirros" image).', + "CONFIG_PROVISION_IMAGE_URL", + 'http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-' + 'x86_64-disk.img', + validators=[validators.not_empty]) + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + conf = controller.CONF + if util.str2bool(conf['CONFIG_PROVISION_DEMO']): + controller.add_sequence("Running provisioning", + setup_controller) + + +def setup_controller(): + conf = Controller.get().CONF + templates = ['provision'] + recipe = "" + for template in templates: + recipe += utils.get_template(template) + + return utils.run_recipe("provision.py", recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) diff --git a/clearstack/plugins/swift_600.py b/clearstack/plugins/swift_600.py new file mode 100644 index 0000000..d32c501 --- /dev/null +++ b/clearstack/plugins/swift_600.py @@ -0,0 +1,101 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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 stat + +from clearstack import utils +from clearstack import validators +from clearstack.controller import Controller +from clearstack.argument import Argument +from clearstack.common import util + + +def init_config(): + conf = { + "SWIFT": [ + Argument("swift-ks-pw", + "Password to use for the Object Storage service to " + " authenticate with with the Identity service", + "CONFIG_SWIFT_KS_PW", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("swift-storages", + "Comma-separated list of devices to use as storage device" + " for Object Storage. e.g. /path/to/dev1,/path/to/dev2." + " Clearstack DOES NOT creare the file system, you must " + "format it with xfs. Leave blank to use a loop device", + "CONFIG_SWIFT_STORAGES", + '', + validators=[validate_storage]), + Argument("swift-storage-zones", + "Number of Object Storage storage zones; this number " + "MUST be no larger than the number of configured " + "storage devices.", + "CONFIG_SWIFT_STORAGE_ZONES", + '1', + validators=[validators.digit]), + Argument("swift-storage-replicas", + "Number of Object Storage storage replicas; this number " + "MUST be no larger than the number of configured " + "storage zones.", + "CONFIG_SWIFT_STORAGE_REPLICAS", + '1', + validators=[validators.digit]), + Argument("swift-hash", + "Custom seed number to use for swift_hash_path_suffix in" + " /etc/swift/swift.conf. If you do not provide a value, " + "a seed number is automatically generated.", + "CONFIG_SWIFT_HASH", + utils.generate_random_pw(), + validators=[validators.not_empty]), + Argument("swift-storage-size", + "Size of the Object Storage loopback file storage " + "device in GB", + "CONFIG_SWIFT_STORAGE_SIZE", + "2", + validators=[validators.digit]), + ] + } + + for group in conf: + Controller.get().add_group(group, conf[group]) + + +def init_sequences(): + controller = Controller.get() + conf = controller.CONF + if util.str2bool(conf['CONFIG_SWIFT_INSTALL']): + controller.add_sequence("Setting up swift", setup_swift) + + +def setup_swift(): + conf = Controller.get().CONF + recipe = utils.get_template("swift") + return utils.run_recipe("swift.py", recipe, + [conf["CONFIG_CONTROLLER_HOST"]]) + + +def validate_storage(value): + if not value: + return + devices = value.split(',') + for device in devices: + mode = os.stat(device).st_mode + if not stat.S_ISBLK(mode): + raise ValueError("%s is not a block device" % device) diff --git a/clearstack/run_setup.py b/clearstack/run_setup.py new file mode 100644 index 0000000..03dec07 --- /dev/null +++ b/clearstack/run_setup.py @@ -0,0 +1,110 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Victor Morales +# +# 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 re +import os + +from importlib import import_module + +from clearstack import utils +from clearstack.controller import Controller +from clearstack.common.util import LOG + + +def load_plugins(): + """ return if plugins already are loaded """ + if Controller.get().get_all_plugins(): + return + + path = "plugins" + base_module = "clearstack.{0}".format(path) + directory = "{0}/{1}".format(os.path.dirname( + os.path.realpath(__file__)), path) + rx_val = r'^[a-zA-Z]+_[0-9]{3}\.py$' + files = [fd for fd in os.listdir(directory) if re.match(rx_val, fd)] + for fd in sorted(files, key=_get_weight): + plugin = import_module("{0}.{1}".format(base_module, fd.split(".")[0])) + Controller.get().add_plugin(plugin) + try: + getattr(plugin, "init_config")() + except AttributeError: + LOG.debug("missing attribute: init_config in %s", + plugin.__file__) + + +def add_arguments(parser): + load_plugins() + for group in Controller.get().get_all_groups(): + for argument in group.get_all_arguments(): + parser.add_argument("--{0}".format(argument.cmd_option), + action="store", + dest=argument.conf_name, + help=argument.description, + choices=argument.option_list) + + +def load_sequences(): + load_plugins() + for plugin in Controller.get().get_all_plugins(): + try: + getattr(plugin, "init_sequences")() + except AttributeError: + LOG.debug("missing attribute: init_sequences in %s", + plugin.__file__) + + +def run_all_sequences(): + load_sequences() + + try: + utils.copy_resources() + except Exception as e: + raise e + + try: + Controller.get().run_all_sequences() + except Exception as e: + raise e + finally: + utils.get_logs() + + +def generate_admin_openrc(): + conf = Controller.get().CONF + home = os.getenv('HOME') + with open("{0}/admin-openrc.sh".format(home), "w") as f: + f.write('export OS_PROJECT_DOMAIN_ID=default\n') + f.write('export OS_USER_DOMAIN_ID=default\n') + f.write('export OS_PROJECT_NAME=admin\n') + f.write('export OS_USERNAME="admin"\n') + f.write('export OS_TENANT_NAME="admin"\n') + f.write('export OS_AUTH_URL=http://{0}:35357/v3\n' + .format(conf['CONFIG_CONTROLLER_HOST'])) + f.write('export OS_REGION_NAME="{0}"\n' + .format(conf['CONFIG_KEYSTONE_REGION'])) + f.write('export OS_PASSWORD={0}\n' + .format(conf['CONFIG_KEYSTONE_ADMIN_PW'])) + f.write('export OS_IDENTITY_API_VERSION=3\n') + + +def _get_weight(item): + tmp = item.split('_')[-1] + tmp = tmp.split('.')[0] + return tmp diff --git a/clearstack/sequence.py b/clearstack/sequence.py new file mode 100644 index 0000000..d86cfe5 --- /dev/null +++ b/clearstack/sequence.py @@ -0,0 +1,39 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Victor Morales +# +# 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. +# + +from clearstack.common.util import LOG + + +class Sequence: + def __init__(self, desc, function, args=None): + self.description = desc + self.function = function + self.function_args = args + + def run(self): + LOG.info(self.description) + if self.function_args: + if not self.function(*self.function_args): + raise Exception("error running {0}({1})" + .format(self.function.__name__, + self.function_args)) + else: + if not self.function(): + raise Exception("error running {0}" + .format(self.function.__name__)) diff --git a/clearstack/shell.py b/clearstack/shell.py new file mode 100644 index 0000000..02364f4 --- /dev/null +++ b/clearstack/shell.py @@ -0,0 +1,143 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Victor Morales +# +# 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. +# + +"""Command-line interface to configure Clearstack.""" + +from __future__ import print_function + +import argparse +import sys + +import clearstack +from clearstack.answer_file import AnswerFile +from clearstack import run_setup +from clearstack import utils +from clearstack.common.util import LOG + + +class ClearstackConfiguratorShell(object): + + def __init__(self, parser_class=argparse.ArgumentParser): + self.parser_class = parser_class + + def get_base_parser(self): + parser = self.parser_class( + prog='clearstack', + description=__doc__.strip(), + epilog='See "clearstack help COMMAND" ' + 'for help on a specific command.', + add_help=False, + formatter_class=argparse.HelpFormatter, + ) + + # Global arguments + parser.add_argument('-h', + '--help', + action='store_true', + help=argparse.SUPPRESS) + + parser.add_argument('--version', + action='version', + version=clearstack.__version__, + help="Shows the client version and exits.") + + parser.add_argument('-d', + '--debug', + action='store_true', + help="Enable debug mode in clearstack") + + parser.add_argument('--gen-answer-file', + action='store', + dest='gen_answer_file', + help='Generate an answer file') + + parser.add_argument('--answer-file', + action='store', + dest='answer_file', + help='Read answer file') + + parser.add_argument('--gen-keys', + action='store', + dest='gen_keys', + help='Generate ssh keys') + + return parser + + def main(self, argv): + self.parser = self.get_base_parser() + run_setup.add_arguments(self.parser) + (options, args) = self.parser.parse_known_args(argv) + utils.setup_debugging(options.debug) + + LOG.debug('Starting clearstack') + + if not argv or options.help: + self.do_help(options) + return 0 + + if options.gen_keys: + LOG.debug('generating ssh keys') + utils.generate_ssh_keys(options.gen_keys) + + # todo: fix + # if options.allinone: + # LOG.debug('testing root access') + # if os.geteuid() != 0: + # LOG.error("clearstack: error: you need to have root access") + # sys.exit(1) + + """ Save user's variables, used to read/write answerfile """ + variables_cmd = {k: v for (k, v) in options.__dict__.items() + if v is not None and k.startswith("CONFIG_")} + + ansfile = AnswerFile.get() + if options.gen_answer_file: + LOG.debug('generating answer file') + ansfile.generate(options.gen_answer_file, variables_cmd) + + if options.answer_file: + try: + LOG.debug('Reading answer file') + ansfile.read(options.answer_file, variables_cmd) + LOG.debug('Running all sequences') + run_setup.run_all_sequences() + except Exception as e: + LOG.error("clearstack: {0}".format(str(e))) + sys.exit(1) + LOG.debug('Generating admin-openrc') + run_setup.generate_admin_openrc() + + @utils.arg('command', metavar='', nargs='?', + help='Display help for .') + def do_help(self, args): + self.parser.print_help() + + +def main(): + try: + ClearstackConfiguratorShell().main(sys.argv[1:]) + except KeyboardInterrupt: + print("... terminating clearstack", file=sys.stderr) + sys.exit(130) + except Exception as e: + print(str(e), file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/clearstack/ssh.py b/clearstack/ssh.py new file mode 100644 index 0000000..cda6f5e --- /dev/null +++ b/clearstack/ssh.py @@ -0,0 +1,166 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Obed Muñoz +# Author: Victor Morales +# +# 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 paramiko + +from clearstack.controller import Controller +from clearstack.common import util +from clearstack.common.util import LOG +from clearstack.common.singleton import Singleton + + +@Singleton +class SshHandler(object): + def __init__(self): + conf = Controller.get().CONF + path = os.path.expanduser( + conf['CONFIG_PRIVATE_SSH_KEY']) + path = os.path.realpath(path) + self.ssh_private_key = path + self.ssh_user = "root" + + def connect(self, user, key_file, host): + ''' Creates a connection to the host using SSH key ''' + private_key = paramiko.RSAKey.from_private_key_file(key_file) + connection = paramiko.SSHClient() + connection.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + connection.connect(hostname=host, username=user, pkey=private_key, + timeout=1000) + return connection + + def run_command(self, connection, command): + try: + stdin, stdout, stderr = connection.exec_command(command) + while not stdout.channel.exit_status_ready(): + pass + except Exception as e: + raise e + + ''' if stderr is not empty then something fail ''' + error = stderr.read() + if error: + raise Exception(error.decode('utf-8')) + + return stdin, stdout, stderr + + def transfer_file(self, file, dest_path, ip): + try: + connection = self.connect(self.ssh_user, self.ssh_private_key, ip) + sftp = connection.open_sftp() + except paramiko.ssh_exception.SSHException: + raise Exception("cannot send {0} to {1}, please check" + " your ssh connection".format(file, ip)) + + if os.path.isdir(file): + parent_dir = "/".join(file.split('/')[:-1]) + try: + sftp.mkdir(dest_path) + except IOError: + pass + for dirpath, dirnames, filenames in os.walk(file): + remote_path = dest_path + dirpath.split(parent_dir)[1] + try: + sftp.mkdir(remote_path) + except: + LOG.info("clearstack: Directory {0} is already created" + "in remote host".format(remote_path)) + for filename in filenames: + local_path = os.path.join(dirpath, filename) + remote_filepath = os.path.join(remote_path, filename) + sftp.put(local_path, remote_filepath) + else: + filename = file.split('/')[-1] + sftp.put(file, "{0}/{1}".format(dest_path, filename)) + + connection.close() + + def test_python_in_host(self, host, connection): + try: + stdin, stdout, stderr = self.run_command(connection, + 'python3 --version') + except: + raise Exception("cannot run python3 in {0}," + " please install python3".format(host)) + + def test_hosts(self, _hosts): + hosts = set(_hosts) + connection = None + conf = Controller.get().CONF + + util.remove_localhost(hosts) + + if not conf['CONFIG_PRIVATE_SSH_KEY'] and hosts: + raise Exception("CONFIG_PRIVATE_SSH_KEY: missing private key") + + for host in hosts: + try: + connection = self.connect(self.ssh_user, self.ssh_private_key, + host) + self.test_python_in_host(host, connection) + except Exception as e: + raise Exception("host {0}: {1}".format(host, str(e))) + finally: + if connection: + connection.close() + + def run_recipe(self, recipe_file, host): + connection = None + interpreter = "python3" + if recipe_file.endswith('.sh'): + interpreter = "bash -f" + + try: + connection = self.connect(self.ssh_user, self.ssh_private_key, + host) + cmd = "source /root/.bashrc 2> /dev/null;" \ + "source /usr/share/defaults/etc/profile 2> /dev/null;" \ + "{0} {1}".format(interpreter, recipe_file) + stdin, stdout, stderr = self.run_command(connection, cmd) + except Exception as e: + LOG.error("clearstack: an error has occurred in {0}," + " please check logs for more information" + .format(host)) + raise e + finally: + if connection: + connection.close() + + def get_logs(self, _hosts): + hosts = set(_hosts) + basename = os.path.splitext(util.LOG_FILE)[0] + connection = None + + util.remove_localhost(hosts) + + for host in hosts: + new_name = "{0}-{1}.log".format(basename, host) + try: + connection = self.connect(self.ssh_user, self.ssh_private_key, + host) + sftp = connection.open_sftp() + sftp.get(util.HOST_LOG_FILE, new_name) + except: + LOG.warning("clearstack: cannot get log file from {0}".format( + host)) + finally: + if connection: + connection.close() diff --git a/clearstack/templates/__init__.py b/clearstack/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clearstack/templates/ceilometer.py b/clearstack/templates/ceilometer.py new file mode 100644 index 0000000..13fd348 --- /dev/null +++ b/clearstack/templates/ceilometer.py @@ -0,0 +1,38 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from modules.ceilometer import Ceilometer +from common import util + + +ceilometer = Ceilometer.get() +config_file = "/etc/ceilometer/ceilometer.conf" + +ceilometer.install() +ceilometer.create_user() +ceilometer.create_service() +ceilometer.create_endpoint() + +ceilometer.config_debug(config_file) +ceilometer.config_database(config_file) +ceilometer.config_rabbitmq(config_file) +ceilometer.config_auth(config_file) +ceilometer.config_service_credentials(config_file) + +util.run_command("systemctl restart update-triggers.target") +ceilometer.start_server() diff --git a/clearstack/templates/compute_cleanup.sh b/clearstack/templates/compute_cleanup.sh new file mode 100644 index 0000000..fb5cb31 --- /dev/null +++ b/clearstack/templates/compute_cleanup.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# reset neutron services +neutron_services="neutron-server neutron-linuxbridge-agent \ + neutron-dhcp-agent neutron-metadata-agent neutron-l3-agent" +systemctl stop $neutron_services +systemctl disable $neutron_services + +# Reset compute services +systemctl disable libvirtd.socket libvirtd.service nova-compute.service \ + nova-network.service nova-metadata.{socket,service} +systemctl stop libvirtd.socket libvirtd.service nova-compute.service \ + nova-network.service nova-metadata.{socket,service} + +# Delete nova config file +rm -rf /etc/nova + +# Update /etc/nova permissions +systemctl restart update-triggers.target + +# Clean Bridges +ip link set br100 down +brctl delbr br100 + +# Restart network service +systemctl restart systemd-networkd + +# Clean Nova data +rm -rf /var/lib/nova/CA/* +rm -rf /var/lib/nova/instances/* +rm -rf /var/lib/nova/keys/* +rm -rf /var/lib/nova/networks/* + +# Clean Libvirt data +rm -rf /var/lib/libvirt/qemu/*.monitor +rm -rf /etc/libvirt/nwfilter/* +rm -rf /etc/libvirt/qemu/* + +# Kill reamining qemu VMs +ps aux | grep qemu | grep -v grep | awk '{print $2}' | xargs kill -9 + +# Kill all about dnsmasq +ps aux | grep dnsmasq | grep -v grep | awk '{print $2}' | xargs kill -9 + +# Clean Logs +rm -rf /var/log/nova/* +rm -rf /var/log/libvirt/qemu/* +rm -rf /var/log/swupd/* diff --git a/clearstack/templates/controller_cleanup.sh b/clearstack/templates/controller_cleanup.sh new file mode 100644 index 0000000..79c12cc --- /dev/null +++ b/clearstack/templates/controller_cleanup.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# reset neutron services +neutron_services="neutron-server neutron-linuxbridge-agent \ + neutron-dhcp-agent neutron-metadata-agent neutron-l3-agent" +systemctl stop $neutron_services +systemctl disable $neutron_services + +# reset nova services +systemctl stop memcached uwsgi@nova-{api,metadata}.{service,socket} nova-cert \ + nova-consoleauth nova-scheduler \ + nova-conductor nova-novncproxy +systemctl disable memcached uwsgi@nova-{api,metadata}.{service,socket} nova-cert \ + nova-consoleauth nova-scheduler \ + nova-conductor nova-novncproxy +rm -rf /etc/nova + +# reset glance services +systemctl stop glance-api glance-registry +systemctl disable glance-api glance-registry +rm -rf /etc/glance +rm -rf /var/lib/glance/images/* + +# reset keystone services +systemctl stop httpd nginx uwsgi@keystone{admin,public}.{socket,service} +systemctl disable httpd nginx +rm -rf /etc/keystone +rm /etc/httpd/conf.d/wsgi-keystone.conf + +# reset horizon +systemctl stop httpd nginx uwsgi@horizon.{socket,service} + +# reset owners and permissions +systemctl restart update-triggers.target + +# reset rabbitmq +systemctl stop rabbitmq-server +systemctl disable rabbitmq-server +rm -rf /etc/rabbitmq/rabbitmq.config +rm -rf /var/lib/rabbitmq/* +rm -rf /etc/hosts + +# reset mariadb +systemctl stop mariadb +systemctl disable mariadb +rm -rf /etc/mariadb +rm -rf /var/lib/mysql + +# Clean Logs +rm -rf /var/log/nova/* +rm -rf /var/log/keystone/* +rm -rf /var/log/glance/* +rm -rf /var/log/rabbitmq/* +rm -rf /var/log/httpd/* +rm -rf /var/log/swupd/* diff --git a/clearstack/templates/glance.py b/clearstack/templates/glance.py new file mode 100644 index 0000000..ef55764 --- /dev/null +++ b/clearstack/templates/glance.py @@ -0,0 +1,43 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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. +# + +from common import util +from modules.conf import CONF +from modules.glance import Glance + + +glance = Glance.get() +config_files = ["/etc/glance/glance-api.conf"] +config_files.append("/etc/glance/glance-registry.conf") + +glance.install() +glance.create_user() +glance.create_service() +glance.create_endpoint() + +for config_file in config_files: + glance.config_debug(config_file) + glance.config_database(config_file) + glance.config_auth(config_file) + if util.str2bool(CONF['CONFIG_CEILOMETER_INSTALL']): + glance.ceilometer_enable(config_file) + +util.run_command("systemctl restart update-triggers.target") +glance.sync_database() +glance.start_server() diff --git a/clearstack/templates/heat.py b/clearstack/templates/heat.py new file mode 100644 index 0000000..b5b52b0 --- /dev/null +++ b/clearstack/templates/heat.py @@ -0,0 +1,49 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from common import util +from modules.heat import Heat + + +heat = Heat.get() +config_file = "/etc/heat/heat.conf" + +heat.install() +heat.create_user() +heat.create_service(name='heat', description='OpenStack Orchestration', + type='orchestration') +heat.create_service(name='heat-cfn', description='OpenStack Orchestration', + type='cloudformation') +heat.create_endpoint() +publicurl = "http://%s:8000/v1" % heat._controller +heat.create_endpoint(publicurl=publicurl, internalurl=publicurl, + adminurl=publicurl, type='cloudformation') +heat.create_domain(heat.domain_name, 'Stack projects and users') +heat.create_user(user=heat.domain_admin, password=heat.domain_admin_pw, + domain=heat.domain_name) +heat.create_role('heat_stack_owner') +heat.create_role('heat_stack_user') + +heat.config_database(config_file) +heat.config_rabbitmq(config_file) +heat.config_auth(config_file) +heat.config_domain(config_file) + +util.run_command("systemctl restart update-triggers.target") +heat.sync_database() +heat.start_server() diff --git a/clearstack/templates/horizon.py b/clearstack/templates/horizon.py new file mode 100644 index 0000000..0864c11 --- /dev/null +++ b/clearstack/templates/horizon.py @@ -0,0 +1,24 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from modules.horizon import Horizon + + +horizon = Horizon.get() +horizon.install() +horizon.start_server() diff --git a/clearstack/templates/keystone.py b/clearstack/templates/keystone.py new file mode 100644 index 0000000..479fa49 --- /dev/null +++ b/clearstack/templates/keystone.py @@ -0,0 +1,47 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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. +# + +from modules.conf import CONF +from modules.keystone import Keystone +from common import util + +keystone = Keystone.get() +config_file = "/etc/keystone/keystone.conf" + +keystone.install() +keystone.config_debug(config_file) +keystone.config_database(config_file) +keystone.config_admin_token(config_file) + +util.run_command("systemctl restart update-triggers.target") +keystone.sync_database() +keystone.start_server() + +keystone.create_service() +keystone.create_endpoint() +keystone.create_project("admin", "Admin Project") +keystone.create_role("admin") +keystone.create_user(user="admin", project="admin", role="admin") +keystone.create_project("service", "Service Project") +keystone.create_project("demo", "Demo Project") +keystone.create_role("user") +keystone.create_user(user="demo", project="demo", role="user", + password=CONF['CONFIG_KEYSTONE_DEMO_PW']) +util.delete_option(config_file, "DEFAULT", "admin_token") +keystone.start_server() diff --git a/clearstack/templates/mariadb.py b/clearstack/templates/mariadb.py new file mode 100644 index 0000000..edf3c58 --- /dev/null +++ b/clearstack/templates/mariadb.py @@ -0,0 +1,46 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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 socket + +from common.util import LOG +from common import util +from modules.conf import CONF +from modules.mariadb import MariaDB + +conf = CONF +mariadb = MariaDB.get() +mariadb_user = CONF["CONFIG_MARIADB_USER"] +mariadb_pw = CONF["CONFIG_MARIADB_PW"] +controller = socket.gethostbyaddr(CONF["CONFIG_CONTROLLER_HOST"])[0] + +if util.str2bool(conf['CONFIG_MARIADB_INSTALL']): + mariadb.install() + mariadb.configure() + mariadb.start_server() + mariadb.secure_installation(mariadb_user, mariadb_pw) + +databases = ['keystone', 'glance', 'nova', 'neutron', 'heat'] + +for database in databases: + if database == 'keystone' or util.str2bool(conf['CONFIG_%s_INSTALL' + % database.upper()]): + LOG.info("Setting up mariadb for %s" % database) + password = CONF['CONFIG_%s_DB_PW' % database.upper()] + mariadb.setup_database(database, database, password, controller) diff --git a/clearstack/templates/mongodb.py b/clearstack/templates/mongodb.py new file mode 100644 index 0000000..20d2c8d --- /dev/null +++ b/clearstack/templates/mongodb.py @@ -0,0 +1,31 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from common import util +from modules.conf import CONF +from modules.mongodb import MongoDB + +mongo = MongoDB.get() + +if util.str2bool(CONF['CONFIG_MONGODB_INSTALL']): + mongo.install() + mongo.configure() + mongo.start_server() + +mongo.setup_database('ceilometer', 'ceilometer', + CONF['CONFIG_CEILOMETER_DB_PW']) diff --git a/clearstack/templates/neutron.py b/clearstack/templates/neutron.py new file mode 100644 index 0000000..ad46a94 --- /dev/null +++ b/clearstack/templates/neutron.py @@ -0,0 +1,60 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from modules.conf import CONF +from modules.neutron import Neutron +from common import util + + +neutron = Neutron.get() +config_file = "/etc/neutron/neutron.conf" + +# Install neutron controller +neutron.install() +neutron.create_user() +neutron.create_service() +neutron.create_endpoint() + +# Configure neutron controller +neutron.config_debug(config_file) +neutron.config_database(config_file) +neutron.config_rabbitmq(config_file) +neutron.config_auth(config_file) + +neutron.config_nova(config_file) +neutron.config_ml2_plugin() +neutron.config_linux_bridge_agent() +neutron.config_l3_agent("/etc/neutron/l3_agent.ini") +neutron.config_dhcp_agent("/etc/neutron/dhcp_agent.ini") +neutron.config_metadata_agent("/etc/neutron/metadata_agent.ini") +neutron.config_neutron_on_nova("/etc/nova/nova.conf") + +if util.str2bool(CONF['CONFIG_CEILOMETER_INSTALL']): + neutron.ceilometer_enable(config_file) + +if util.str2bool(CONF['CONFIG_LBAAS_INSTALL']): + neutron.install('openstack-lbaas') + neutron.config_lbaas() + +if util.str2bool(CONF['CONFIG_VPNAAS_INSTALL']): + neutron.install('openstack-vpnaas') + neutron.config_vpnaas() + +util.run_command("systemctl restart update-triggers.target") +neutron.sync_database() +neutron.start_server() diff --git a/clearstack/templates/neutron_compute.py b/clearstack/templates/neutron_compute.py new file mode 100644 index 0000000..a304514 --- /dev/null +++ b/clearstack/templates/neutron_compute.py @@ -0,0 +1,40 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from modules.conf import CONF +from modules.neutron import Neutron +from common import util + + +neutron = Neutron.get() +config_file = "/etc/neutron/neutron.conf" +services = ['nova-compute', 'neutron-linuxbridge-agent'] + +neutron.install() +neutron.config_debug(config_file) +neutron.config_rabbitmq(config_file) +neutron.config_auth(config_file) +neutron.config_linux_bridge_agent() +neutron.config_neutron_on_nova('/etc/nova/nova.conf') + +if util.str2bool(CONF['CONFIG_CEILOMETER_INSTALL']): + neutron.ceilometer_enable(config_file) + +util.run_command("systemctl restart update-triggers.target") + +neutron.start_server(services) diff --git a/clearstack/templates/nova.py b/clearstack/templates/nova.py new file mode 100644 index 0000000..14dd54e --- /dev/null +++ b/clearstack/templates/nova.py @@ -0,0 +1,72 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from modules.nova import Nova +from modules.conf import CONF +from common import util + + +nova = Nova.get() +config_file = "/etc/nova/nova.conf" +my_ip = util.get_ip() + +# Install nova controller +nova.install() +nova.create_user() +nova.create_service() +nova.create_endpoint() + +# Configure nova controller +nova.config_debug(config_file) +nova.config_database(config_file) +nova.config_rabbitmq(config_file) +nova.config_auth(config_file) + +# Setup vncproxy +config = \ + "[DEFAULT]\n" + \ + "my_ip=%s\n" % my_ip + \ + "[vnc]\n" + \ + "vncserver_listen=0.0.0.0\n" + \ + "vncserver_proxyclient_address=%s\n" % my_ip +util.write_config(config_file, config) + +# Setup glance host +config = \ + "[glance]\n" + \ + "host=%s\n" % CONF['CONFIG_CONTROLLER_HOST'] +util.write_config(config_file, config) + +# Setup nova-network if neutron is not installed +if not util.str2bool(CONF['CONFIG_NEUTRON_INSTALL']): + config = \ + "[DEFAULT]\n" + \ + "network_api_class = nova.network.api.API\n" + \ + "security_group_api = nova\n" + util.write_config(config_file, config) + +if CONF['CONFIG_HTTP_SERVICE'] == 'nginx': + util.link_file('/usr/share/nginx/conf.d/nova-api.template', + '/etc/nginx/nova-api.conf') +else: + util.link_file('/usr/share/defaults/httpd/conf.d/nova-api.template', + '/etc/httpd/conf.d/nova-api.conf') + +util.run_command("systemctl restart update-triggers.target") +nova.sync_database() +nova.start_server() diff --git a/clearstack/templates/nova_compute.py b/clearstack/templates/nova_compute.py new file mode 100644 index 0000000..a88158d --- /dev/null +++ b/clearstack/templates/nova_compute.py @@ -0,0 +1,97 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Obed Muñoz +# Author: Victor Morales +# +# 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. +# + +from modules import clearlinux +from modules.nova import Nova +from modules.conf import CONF +from common import util + + +nova = Nova.get() +config_file = "/etc/nova/nova.conf" +services = ['libvirtd.socket', 'nova-compute'] +my_ip = util.get_ip() + +if CONF['CONFIG_HTTP_SERVICE'] == 'nginx': + services.extend(['nginx', 'uwsgi@nova-metadata.socket']) +elif CONF['CONFIG_HTTP_SERVICE'] == 'apache2': + services.append('httpd') +else: + services.append('nova-api-metadata') + +nova.install_compute() +nova.config_debug(config_file) +nova.config_rabbitmq(config_file) +nova.config_auth(config_file) + +# Setup vncproxy server +config = ("[DEFAULT]\n" + "my_ip={0}\n" + "[vnc]\n" + "vnc_enabled=True\n" + "vncserver_listen=0.0.0.0\n" + "vncserver_proxyclient_address={0}\n" + "novncproxy_base_url=http://{1}:6080/vnc_auto.html\n" + .format(my_ip, CONF['CONFIG_CONTROLLER_HOST'])) + +# Setup glance host +config += ("[glance]\n" + "host=%s\n") % CONF['CONFIG_CONTROLLER_HOST'] + +# Disable HW acceleration on unsupported HW +if not clearlinux.support_hw_acceleration(): + config += ("[libvirt]\n" + "virt_type=qemu\n") + +util.write_config(config_file, config) + +# Setup compute Networking +if not util.str2bool(CONF['CONFIG_NEUTRON_INSTALL']): + services.append("nova-network") + network_manager = CONF['CONFIG_NOVA_NETWORK_MANAGER'] + network_size = CONF['CONFIG_NOVA_NETWORK_SIZE'] + multihost = CONF['CONFIG_NOVA_NETWORK_MULTIHOST'] + network_pubif = CONF['CONFIG_NOVA_NETWORK_PUBIF'] + network_privif = CONF['CONFIG_NOVA_COMPUTE_PRIVIF'] + config = \ + "[DEFAULT]\n" + \ + "network_api_class=nova.network.api.API\n" + \ + "security_group_api=nova\n" + \ + "firewall_driver=" + \ + "nova.virt.libvirt.firewall.IptablesFirewallDriver\n" + \ + "network_size=%s\n" % network_size + \ + "allow_same_net_traffic=False\n" + \ + "multi_host=%s\n" % multihost + \ + "send_arp_for_ha=True\n" + \ + "share_dhcp_address=True\n" + \ + "force_dhcp_release=True\n" + \ + "flat_network_bridge=br100\n" + \ + "public_interface=%s\n" % network_pubif + \ + "flat_interface=%s\n" % network_privif + if util.str2bool(CONF['CONFIG_NOVA_NETWORK_AUTOASSIGNFLOATINGIP']): + config += "auto_assign_floating_ip = True\n" + util.write_config(config_file, config) + +if util.str2bool(CONF['CONFIG_CEILOMETER_INSTALL']): + nova.ceilometer_enable(config_file) + +util.run_command("systemctl restart update-triggers.target") +nova.start_server(services) diff --git a/clearstack/templates/pre_compute.sh b/clearstack/templates/pre_compute.sh new file mode 100644 index 0000000..969c58f --- /dev/null +++ b/clearstack/templates/pre_compute.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Copyright (c) 2015 Intel Corporation +# +# 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. +# + +if [ -f /bin/swupd ];then + if [ ! -f /usr/share/clear/bundles/openstack-configure ];then + swupd bundle-add openstack-configure + fi +elif [ -f /bin/clr_bundle_add];then + if [ ! -f /usr/share/clear/bundles/openstack-configure ];then + clr_bundle_add openstack-configure + fi +fi diff --git a/clearstack/templates/pre_controller.sh b/clearstack/templates/pre_controller.sh new file mode 100644 index 0000000..af910b9 --- /dev/null +++ b/clearstack/templates/pre_controller.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Copyright (c) 2015 Intel Corporation +# +# 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. +# + +if [ -f /bin/swupd ];then + if [ ! -f /usr/share/clear/bundles/openstack-configure ];then + swupd bundle-add openstack-configure + fi + + if [ ! -f /usr/share/clear/bundles/openstack-python-clients -a ! -f /usr/share/clear/bundles/openstack-all-in-one ];then + swupd bundle-add openstack-python-clients + fi +elif [ -f /bin/clr_bundle_add];then + if [ ! -f /usr/share/clear/bundles/openstack-configure ];then + clr_bundle_add openstack-configure + fi + + if [ ! -f /usr/share/clear/bundles/openstack-python-clients -a ! -f /usr/share/clear/bundles/openstack-all-in-one ];then + clr_bundle_add openstack-python-clients + fi +fi diff --git a/clearstack/templates/provision.py b/clearstack/templates/provision.py new file mode 100644 index 0000000..27c04a5 --- /dev/null +++ b/clearstack/templates/provision.py @@ -0,0 +1,44 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from modules.glance import Glance +from modules.neutron import Neutron +from modules.nova import Nova +from modules.conf import CONF +from common import util + +glance = Glance.get() +nova = Nova.get() +neutron = Neutron.get() + +if util.str2bool(CONF['CONFIG_PROVISION_DEMO']): + name = CONF['CONFIG_PROVISION_IMAGE_NAME'] + format = CONF['CONFIG_PROVISION_IMAGE_FORMAT'] + url = CONF['CONFIG_PROVISION_IMAGE_URL'] + glance.create_image(name, format, url, public=True) + + fr = CONF['CONFIG_PROVISION_DEMO_FLOATRANGE'] + CONF['CONFIG_NOVA_NETWORK_FLOATRANGE'] = fr + +if not util.str2bool(CONF['CONFIG_NEUTRON_INSTALL']): + nova.create_network() + nova.create_floating_ips() +else: + neutron.create_network('private', public=False) + neutron.create_network('public', public=True) + neutron.create_router('router', gw='public', interfaces=['private']) diff --git a/clearstack/templates/rabbitmq.py b/clearstack/templates/rabbitmq.py new file mode 100644 index 0000000..3e71a7c --- /dev/null +++ b/clearstack/templates/rabbitmq.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# +# 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. +# + +from modules.rabbitmq import Rabbitmq +from modules.conf import CONF + +rabbitmq_auth_user = CONF["CONFIG_AMQP_AUTH_USER"] +rabbitmq_auth_pw = CONF["CONFIG_AMQP_AUTH_PASSWORD"] + +rabbit = Rabbitmq.get() +rabbit.install() +rabbit.start_server() +rabbit.add_user(rabbitmq_auth_user, rabbitmq_auth_pw) +rabbit.set_permissions(rabbitmq_auth_user, '".*" ".*" ".*"') diff --git a/clearstack/templates/swift.py b/clearstack/templates/swift.py new file mode 100644 index 0000000..781dc49 --- /dev/null +++ b/clearstack/templates/swift.py @@ -0,0 +1,58 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# +# 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. +# + +from common import util +from modules.conf import CONF +from modules.swift import Swift + + +swift = Swift.get() +config_file = "/etc/swift/proxy-server.conf" + +swift.install() +swift.create_user() +swift.create_service() +swift.create_endpoint() + +swift.config_auth(config_file) +swift.config_memcache(config_file) +swift.config_hash('/etc/swift/swift.conf') + +# Storage configuration +swift.parse_devices() +swift.prepare_devices() +swift.create_rings() +swift.config_rsync() +swift.config_storage_services() + +if util.str2bool(CONF['CONFIG_CEILOMETER_INSTALL']): + swift.ceilometer_enable(config_file) + +util.run_command("systemctl restart update-triggers.target") + +# Start controller services +swift.start_server() + +# Start storage node services +services = ['rsyncd', 'swift-account', 'swift-account-auditor', + 'swift-account-reaper', 'swift-account-replicator', + 'swift-container', 'swift-container-auditor', + 'swift-container-replicator', 'swift-container-updater', + 'swift-object', 'swift-object-auditor', + 'swift-object-replicator', 'swift-object-updater'] +swift.start_server(services) diff --git a/clearstack/tests/__init__.py b/clearstack/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clearstack/tests/test_answer_file.py b/clearstack/tests/test_answer_file.py new file mode 100644 index 0000000..11dd3fe --- /dev/null +++ b/clearstack/tests/test_answer_file.py @@ -0,0 +1,51 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Victor Morales +# +# 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 mock +import testtools + +from clearstack.answer_file import AnswerFile +from clearstack.argument import Argument +from clearstack.controller import Controller + +conf = {'COMPONENT': [Argument('argument', + 'description', + 'CONFIG_ARGUMET', + 'secure')]} + +for group in conf: + Controller.get().add_group(group, conf[group]) + + +class AnswerFileTest(testtools.TestCase): + def setUp(self): + super(AnswerFileTest, self).setUp() + + def test_generate_file(self): + m = mock.mock_open() + filename = '/tmp/clearstack.answerfile' + with mock.patch('clearstack.answer_file.open', m, create=True): + AnswerFile.get().generate(filename, {}) + m.assert_called_once_with(filename, 'w') + + @mock.patch('os.path.isfile') + def test_read_non_existing_file(self, mock_isfile): + mock_isfile.side_effect = [False] + filename = '/tmp/clearstack.answerfile' + self.assertRaises(IOError, AnswerFile.get().read, filename, {}) diff --git a/clearstack/tests/test_argument.py b/clearstack/tests/test_argument.py new file mode 100644 index 0000000..ea48a91 --- /dev/null +++ b/clearstack/tests/test_argument.py @@ -0,0 +1,37 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Victor Morales +# +# 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 testtools + + +class ArgumentTest(testtools.TestCase): + def setUp(self): + super(ArgumentTest, self).setUp() + + # def test_argument_validate(self): + # argument = Argument('cmd_option', 'description', 'conf_name', + # 'default_value') + # self.assertTrue(argument.validate('test')) + + # def validator(value): + # return 'ERROR' + + # argument = Argument('cmd_option', 'description', 'conf_name', + # 'default_value', [validator]) + # self.assertFalse(argument.validate('test')) diff --git a/clearstack/tests/test_modules_util.py b/clearstack/tests/test_modules_util.py new file mode 100644 index 0000000..780c5a5 --- /dev/null +++ b/clearstack/tests/test_modules_util.py @@ -0,0 +1,51 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# +# 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 testtools + +from clearstack.common import util + + +class ModulesUtilTest(testtools.TestCase): + def setUp(self): + super(ModulesUtilTest, self).setUp() + + # # Test if run_command is hidden debug logs + # def test_0000_run_command(self): + # util.setup_debugging(True, False) + # util.run_command("echo", debug=False) + # logging.StreamHandler().close() + # self.assertEqual("echo" in open(self.log_file).read(), False) + + # # Test if run_command is showing debug logs + # def test_0001_run_command(self): + # util.setup_debugging(True, False) + # util.run_command("echo") + # logging.StreamHandler().close() + # self.assertEqual("echo" in open(self.log_file).read(), True) + + # Test command not found + def test_0002_run_command(self): + self.assertRaises(Exception, util.run_command, "inexistente") + + # Test simple command + def test_0003_run_command(self): + try: + util.run_command("echo") + except Exception: + self.fail() diff --git a/clearstack/tests/test_sequence.py b/clearstack/tests/test_sequence.py new file mode 100644 index 0000000..35addd9 --- /dev/null +++ b/clearstack/tests/test_sequence.py @@ -0,0 +1,39 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Victor Morales +# +# 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 mock + +import testtools + +from clearstack.sequence import Sequence + + +class SequenceTest(testtools.TestCase): + def setUp(self): + super(SequenceTest, self).setUp() + + def test_run(self): + mock_function = mock.Mock(return_value=False) + mock_function.__name__ = 'Bar' + + seq = Sequence('test', mock_function) + self.assertRaises(Exception, seq.run) + + seq = Sequence('test', mock_function, ["subsequence"]) + self.assertRaises(Exception, seq.run) diff --git a/clearstack/tests/test_shell.py b/clearstack/tests/test_shell.py new file mode 100644 index 0000000..ea95856 --- /dev/null +++ b/clearstack/tests/test_shell.py @@ -0,0 +1,35 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# Author: Victor Morales +# +# 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 testtools +from testtools import matchers + +from clearstack.tests import utils + + +class ShellTest(testtools.TestCase): + + def setUp(self): + super(ShellTest, self).setUp() + + def test_help(self): + required = 'usage:' + help_text = utils.shell('--help') + self.assertThat(help_text, + matchers.MatchesRegex(required)) diff --git a/clearstack/tests/utils.py b/clearstack/tests/utils.py new file mode 100644 index 0000000..7221c92 --- /dev/null +++ b/clearstack/tests/utils.py @@ -0,0 +1,42 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# +# 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 sys +import io + +from clearstack import shell as clearstack_shell + + +def shell(argstr): + orig = sys.stdout + clean_env = {} + _old_env, os.environ = os.environ, clean_env.copy() + try: + sys.stdout = io.StringIO() + _shell = clearstack_shell.ClearstackConfiguratorShell() + _shell.main(argstr.split()) + except SystemExit: + exc_type, exc_value, exc_traceback = sys.exc_info() + assert exc_value, 0 + finally: + out = sys.stdout.getvalue() + sys.stdout.close() + sys.stdout = orig + os.environ = _old_env + return out diff --git a/clearstack/utils.py b/clearstack/utils.py new file mode 100644 index 0000000..aea16c4 --- /dev/null +++ b/clearstack/utils.py @@ -0,0 +1,200 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# Author: Victor Morales +# +# 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 calendar +import configparser +import datetime +import os +import binascii +import time +import shutil + +from clearstack import controller +from clearstack.ssh import SshHandler +from clearstack.controller import Controller +from clearstack.common import util + + +__now = datetime.datetime.now() +__recipes_directory = "/tmp/clearstack-{0}-{1}-{2}-{3}/".format( + __now.year, __now.month, __now.day, calendar.timegm(time.gmtime())) + + +def arg(*args, **kwargs): + def _decorator(func): + func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs)) + return func + return _decorator + + +def run_recipe(recipe_file, recipe_src, hosts): + recipes_dir = __recipes_directory + if not os.path.isdir(recipes_dir): + os.makedirs(recipes_dir) + + if controller.DEBUG and recipe_file.endswith('.py'): + recipe_src = "from common import util\n" + \ + "util.setup_debugging(True)\n\n" + \ + recipe_src + + recipe_file = os.path.join(recipes_dir, recipe_file) + with open(recipe_file, "w") as f: + f.write(recipe_src) + + _run_recipe_in_hosts(recipe_file, recipes_dir, hosts) + return True + + +def _run_recipe_local(recipe_file): + if recipe_file.endswith('.py'): + util.run_command("python3 {0}".format(recipe_file)) + else: + util.run_command("bash -f {0}".format(recipe_file)) + + +def _run_recipe_in_hosts(recipe_file, recipes_dir, _hosts): + hosts = set(_hosts) + ssh = SshHandler.get() + + if util.has_localhost(hosts): + _run_recipe_local(recipe_file) + util.remove_localhost(hosts) + + for host in hosts: + try: + ssh.transfer_file(recipe_file, recipes_dir, host) + ssh.run_recipe(recipe_file, host) + except Exception as e: + raise e + + +def get_all_hosts(): + conf = Controller.get().CONF + hosts = set() + hosts.update(set(conf["CONFIG_COMPUTE_HOSTS"].split(","))) + hosts.add(conf["CONFIG_CONTROLLER_HOST"]) + hosts.add(conf["CONFIG_AMQP_HOST"]) + hosts.add(conf["CONFIG_MARIADB_HOST"]) + hosts.add(conf["CONFIG_MONGODB_HOST"]) + + return hosts + + +def generate_conf_file(conf_file): + """ create defaults.conf file """ + conf = Controller.get().CONF + config_file = configparser.RawConfigParser() + config_file.optionxform = str + dir_name = os.path.dirname(conf_file) + + if not os.path.isdir(dir_name): + os.makedirs(dir_name) + + config_file.add_section("general") + for key in conf.keys(): + config_file.set("general", key, '{0}'.format(conf[key])) + + with open(conf_file, 'w') as f: + config_file.write(f) + + +def copy_resources(): + _copy_resources_to_hosts(get_all_hosts()) + return True + + +def _copy_resources_local(): + resources_dir = os.path.dirname(os.path.realpath(__file__)) + modules_src = "{0}/modules".format(resources_dir) + common_src = "{0}/common".format(resources_dir) + + modules_dst = "{0}/modules".format(__recipes_directory) + common_dst = "{0}/common".format(__recipes_directory) + conf_file = "{0}/defaults.conf".format(__recipes_directory) + + generate_conf_file(conf_file) + shutil.copytree(modules_src, modules_dst) + shutil.copytree(common_src, common_dst) + + +def _copy_resources_to_hosts(_hosts): + try: + SshHandler.get().test_hosts(_hosts) + except Exception as e: + raise e + + hosts = set(_hosts) + ssh = SshHandler.get() + resources_dir = os.path.dirname(os.path.realpath(__file__)) + modules_dir = "{0}/modules".format(resources_dir) + common_dir = "{0}/common".format(resources_dir) + conf_file = "{0}/defaults.conf".format(__recipes_directory) + + if util.has_localhost(hosts): + _copy_resources_local() + + util.remove_localhost(hosts) + + if not os.path.isfile(conf_file): + generate_conf_file(conf_file) + + """ Copy modules and conf to hosts """ + for host in hosts: + try: + ssh.transfer_file(modules_dir, __recipes_directory, host) + ssh.transfer_file(common_dir, __recipes_directory, host) + ssh.transfer_file(conf_file, __recipes_directory, host) + except Exception as e: + raise e + + return True + + +def get_logs(): + SshHandler.get().get_logs(get_all_hosts()) + + +def get_template(template): + directory = "{0}/{1}".format(os.path.dirname( + os.path.realpath(__file__)), "templates") + if not template.endswith(".sh"): + template = template + ".py" + template = os.path.join(directory, template) + source = "\n{0} {1} {0}\n".format("#" * 5, template) + with open(template, 'r') as f: + source += f.read() + source += "\n{0}\n".format("#" * 60) + return source + + raise Exception("cannot load {0} template".format(template)) + + +def generate_random_pw(): + return binascii.b2a_hex(os.urandom(10)).decode("utf-8") + + +def generate_ssh_keys(output): + util.run_command("ssh-keygen -b 4096 -f {0} -a 500 -N ''" + .format(output)) + + +def setup_debugging(debug): + controller.DEBUG = debug + util.setup_debugging(debug, is_remote_host=False) diff --git a/clearstack/validators.py b/clearstack/validators.py new file mode 100644 index 0000000..dbf37dc --- /dev/null +++ b/clearstack/validators.py @@ -0,0 +1,62 @@ +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Alberto Murillo +# Author: Julio Montes +# +# 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 ipaddress +import os +import socket + + +def y_or_n(value): + if not value.lower() in ("n", "y"): + raise ValueError("please set 'y' or 'n'") + + +def true_or_false(value): + if not value.lower() in ("true", "false"): + raise ValueError("please set 'True' or 'False'") + + +def ip_or_hostname(value): + for v in value.split(","): + try: + ipaddress.ip_address(v) + except ValueError: + socket.gethostbyname(v) + + +def cidr(value): + ip = ipaddress.ip_network(value) + if not ip: + raise ValueError("Invalid cidr") + + +def file(value): + value = os.path.expanduser(value) + if not os.path.exists(value): + raise ValueError("file {0} not found".format(value)) + + +def not_empty(value): + if not value.strip(): + raise ValueError("empty value") + + +def digit(value): + if not value.isdigit(): + raise ValueError("{0} is not a digit".format(value)) diff --git a/requirements-py3.txt b/requirements-py3.txt new file mode 100644 index 0000000..dfd374b --- /dev/null +++ b/requirements-py3.txt @@ -0,0 +1,5 @@ +pbr # Apache-2.0 +ipaddress # Python-2.0 +netifaces # MIT +paramiko # LGPL-2.1 +sphinx # BSD-2-Clause diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f7bcb24 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,55 @@ +[metadata] +name = clearstack +summary = Tool for set up an OpenStack environment +description-file = + README.rst +author = Alberto Murillo, Julio Montes, Obed Muñoz, Victor Morales +home-page = +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.3 + +[files] +packages = + clearstack + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[entry_points] +console_scripts = + clearstack = clearstack.shell:main + +[build_sphinx] +source-dir = doc/source +build-dir = doc/build +all_files = 1 + +[pbr] +warnerrors = True + +[upload_sphinx] +upload-dir = doc/build/html + +[compile_catalog] +directory = clearstack/locale +domain = clearstack + +[update_catalog] +domain = clearstack +output_dir = clearstack/locale +input_file = clearstack/locale/clearstack.pot + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = clearstack/locale/clearstack.pot + +[wheel] +universal = 1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ea9d08f --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# +# Copyright (c) 2015 Intel Corporation +# +# Author: Julio Montes +# +# 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. +# + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr'], + pbr=True, + packages=['clearstack'], + package_dir={'clearstack': 'clearstack'}, + scripts=['clearstack/templates/pre_compute.sh', + 'clearstack/templates/pre_controller.sh'] +) diff --git a/test-requirements-py3.txt b/test-requirements-py3.txt new file mode 100644 index 0000000..0598492 --- /dev/null +++ b/test-requirements-py3.txt @@ -0,0 +1,4 @@ +coverage>=3.6 +flake8>=2.2.4 +testrepository>=0.0.18 +mock diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..24bd914 --- /dev/null +++ b/tox.ini @@ -0,0 +1,41 @@ +[tox] +minversion = 1.6 +skipsdist = True +envlist = py34,pep8 + +[testenv] +usedevelop = True +install_command = pip install -U {opts} {packages} +setenv = VIRTUAL_ENV={envdir} + OS_STDOUT_NOCAPTURE=False + OS_STDERR_NOCAPTURE=False + +deps = -r{toxinidir}/requirements-py3.txt + -r{toxinidir}/test-requirements-py3.txt +commands = python setup.py testr --testr-args='{posargs}' + +[testenv:pep8] +commands = + flake8 + +[testenv:venv] +commands = {posargs} + +[testenv:cover] +commands = python setup.py testr --coverage --testr-args='{posargs}' + +[tox:jenkins] +downloadcache = ~/cache/pip + +[testenv:debug] +commands = oslo_debug_helper -t keystoneclient/tests {posargs} + +[flake8] +# H405: multi line docstring summary not separated with an empty line +ignore = H405 +show-source = True +exclude = .venv,.tox,dist,doc,*egg,build + +[testenv:docs] +commands= + python setup.py build_sphinx