Commit 31edda67 authored by Marco Mariani's avatar Marco Mariani

Merge remote-tracking branch 'origin/master' into cliff

parents d8410ba1 d1e2cc5c
...@@ -10,6 +10,7 @@ New features: ...@@ -10,6 +10,7 @@ New features:
* Initial windows support. [Jondy Zhao] * Initial windows support. [Jondy Zhao]
* Support new/changed parameters in command line tools, defined in documentation. [Marco Mariani] * Support new/changed parameters in command line tools, defined in documentation. [Marco Mariani]
* Register: support for one-time authentication token. [Marco Mariani] * Register: support for one-time authentication token. [Marco Mariani]
* New command: "slapos configure client" [Marco Mariani]
Major Improvements: Major Improvements:
......
...@@ -173,18 +173,25 @@ node register ...@@ -173,18 +173,25 @@ node register
.. program-output:: python slapos help node register .. program-output:: python slapos help node register
If login is not provided, asks for user's SlapOS Master account then password. This will register the current node, and generate the SlapOS configuration file.
The command requires an authentication token, either provided as an argument,
or given at the interactive prompt.
Go to the SlapOS Master web page, click "My Space", then "My Account", then
"Generate a computer security token".
A token is valid for a single ``node resister`` command and will expire after one day.
The deprecated ``--login`` and ``--password`` options can be used with old SlapOS servers
that have no support for the token.
Node will register itself, if not already done, to the SlapOS Master defined in
configuration file, and will generate SlapOS configuration file.
.. ..
XXX-Cedric should be like this: If desired node name is already taken, will raise an error. XXX-Cedric should be like this: If desired node name is already taken, will raise an error.
XXX-Cedric: --master-url-web url will disappear in REST API. Currently, "register" uses XXX-Cedric: --master-url-web url will disappear in REST API. Currently, "register" uses
SlapOS master web URL to register computer, so it needs the web URL (like http://www.slapos.org) SlapOS master web URL to register computer, so it needs the web URL (like http://www.slapos.org)
If Node is already registered (:file:`slapos.cfg` and certificate already present), issues a warning, If the Node is already registered (:file:`slapos.cfg` and certificate are already present), the command
backups the original configuration and creates a new one. issues a warning, backups the original configuration and creates a new one.
.. ..
XXX-Cedric should check for IPv6 in selected interface XXX-Cedric should check for IPv6 in selected interface
...@@ -326,6 +333,23 @@ node supervisord ...@@ -326,6 +333,23 @@ node supervisord
SlapOS Miscellaneous commands SlapOS Miscellaneous commands
----------------------------- -----------------------------
configure client
~~~~~~~~~~~~~~~~
.. program-output:: python slapos help configure client
This creates a client configuration file, and downloads a certificate + key pair
from the SlapOS Master. They will be used for all the "slapos client" commands.
The command requires an authentication token, either provided as an argument,
or given at the interactive prompt.
Go to the SlapOS Master web page, click "My Space", then "My Account", then
"Generate a credential security token".
A token is valid for a single `configure client` command and will expire after one day.
cache lookup cache lookup
~~~~~~~~~~~~ ~~~~~~~~~~~~
......
...@@ -61,10 +61,12 @@ class SoftwareInstance(Item): ...@@ -61,10 +61,12 @@ class SoftwareInstance(Item):
key = element.get('id').encode("UTF-8") key = element.get('id').encode("UTF-8")
value = result_dict.get(key, None) value = result_dict.get(key, None)
if value is not None: if value is not None:
value = value + ' ' + element.text value = (value + ' ' + element.text)
else: else:
value = element.text value = element.text
result_dict[key] = value.encode("UTF-8") if value is not None:
value = value.encode("UTF-8")
result_dict[key] = value
return result_dict return result_dict
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
......
287 289
\ No newline at end of file \ No newline at end of file
...@@ -54,6 +54,7 @@ setup(name=name, ...@@ -54,6 +54,7 @@ setup(name=name,
),}, ),},
tests_require=[ tests_require=[
'pyflakes', 'pyflakes',
'mock'
], ],
zip_safe=False, # proxy depends on Flask, which has issues with zip_safe=False, # proxy depends on Flask, which has issues with
# accessing templates # accessing templates
...@@ -91,6 +92,7 @@ setup(name=name, ...@@ -91,6 +92,7 @@ setup(name=name,
'node instance = slapos.cli.slapgrid:InstanceCommand', 'node instance = slapos.cli.slapgrid:InstanceCommand',
# SlapOS client commands # SlapOS client commands
'console = slapos.cli.console:ConsoleCommand', 'console = slapos.cli.console:ConsoleCommand',
'configure client = slapos.cli.configure_client:ConfigureClientCommand',
'proxy start = slapos.cli.proxy_start:ProxyStartCommand', 'proxy start = slapos.cli.proxy_start:ProxyStartCommand',
'proxy show = slapos.cli.proxy_show:ProxyShowCommand', 'proxy show = slapos.cli.proxy_show:ProxyShowCommand',
'supply = slapos.cli.supply:SupplyCommand', 'supply = slapos.cli.supply:SupplyCommand',
......
...@@ -28,6 +28,13 @@ class ConfigCommand(Command): ...@@ -28,6 +28,13 @@ class ConfigCommand(Command):
(self.default_config_var, self.default_config_path)) (self.default_config_var, self.default_config_path))
return ap return ap
def config_path(self, args):
if args.cfg:
cfg_path = args.cfg
else:
cfg_path = os.environ.get(self.default_config_var, self.default_config_path)
return os.path.expanduser(cfg_path)
def fetch_config(self, args): def fetch_config(self, args):
""" """
Returns a configuration object if file exists/readable/valid, Returns a configuration object if file exists/readable/valid,
...@@ -36,14 +43,9 @@ class ConfigCommand(Command): ...@@ -36,14 +43,9 @@ class ConfigCommand(Command):
and will clearly show what is wrong with the file. and will clearly show what is wrong with the file.
""" """
if args.cfg: cfg_path = self.config_path(args)
cfg_path = args.cfg
else:
cfg_path = os.environ.get(self.default_config_var, self.default_config_path)
cfg_path = os.path.expanduser(cfg_path)
self.log.debug('Loading config: %s' % cfg_path) self.log.debug('Loading config: %s', cfg_path)
if not os.path.exists(cfg_path): if not os.path.exists(cfg_path):
raise ConfigError('Configuration file does not exist: %s' % cfg_path) raise ConfigError('Configuration file does not exist: %s' % cfg_path)
......
# -*- coding: utf-8 -*-
import logging
import re
import os
import sys
import requests
from slapos.cli.config import ClientConfigCommand
from slapos.util import mkdir_p, parse_certificate_key_pair
class ConfigureClientCommand(ClientConfigCommand):
"""
register a node in the SlapOS cloud
"""
log = logging.getLogger('configure-client')
def get_parser(self, prog_name):
ap = super(ConfigureClientCommand, self).get_parser(prog_name)
ap.add_argument('--master-url',
default='https://slap.vifib.com',
help='URL of SlapOS Master REST API'
' (default: %(default)s)')
ap.add_argument('--master-url-web',
default='https://www.slapos.org',
help='URL of SlapOS Master webservice to register certificates'
' (default: %(default)s)')
ap.add_argument('--token',
help="SlapOS 'credential security' authentication token "
"(use '--token ask' for interactive prompt)")
return ap
def take_action(self, args):
do_configure_client(logger=self.log,
master_url_web=args.master_url_web,
token=args.token,
config_path=self.config_path(args),
master_url=args.master_url)
def get_certificate_key_pair(logger, master_url_web, token):
url = '/'.join([master_url_web, 'myspace/my_account/request-a-certificate'])
req = requests.post('/'.join([master_url_web, 'myspace/my_account/request-a-certificate/WebSection_requestNewCertificate']),
data={},
headers={'X-Access-Token': token},
verify=False)
if req.status_code == 403:
logger.critical('Access denied to the SlapOS Master. '
'Please check the authentication token or require a new one.')
sys.exit(1)
req.raise_for_status()
return parse_certificate_key_pair(req.text)
def fetch_configuration_template():
req = requests.get('http://git.erp5.org/gitweb/slapos.core.git/blob_plain/HEAD:/slapos-client.cfg.example')
req.raise_for_status()
return req.text
def do_configure_client(logger, master_url_web, token, config_path, master_url):
while not token:
token = raw_input('Credential security token: ').strip()
# Check for existence of previous configuration, certificate or key files
# where we expect to create them. If so, ask the use to manually remove them.
if os.path.exists(config_path):
logger.critical('There is file in %s. '
'Please remove it before creating a new configuration.', config_path)
sys.exit(1)
basedir = os.path.dirname(config_path)
if not os.path.isdir(basedir):
logger.debug('Creating directory %s', basedir)
mkdir_p(basedir, mode=0o700)
cert_path = os.path.join(basedir, 'client.crt')
if os.path.exists(cert_path):
logger.critical('There is a file in %s. '
'Please remove it before creating a new certificate.', cert_path)
sys.exit(1)
key_path = os.path.join(basedir, 'client.key')
if os.path.exists(key_path):
logger.critical('There is a file in %s. '
'Please remove it before creating a new key.', key_path)
sys.exit(1)
# retrieve a template for the configuration file
cfg = fetch_configuration_template()
cfg = re.sub('master_url = .*', 'master_url = %s' % master_url, cfg)
cfg = re.sub('cert_file = .*', 'cert_file = %s' % cert_path, cfg)
cfg = re.sub('key_file = .*', 'key_file = %s' % key_path, cfg)
# retrieve and parse the certicate and key
certificate, key = get_certificate_key_pair(logger, master_url_web, token)
# write everything
with os.fdopen(os.open(config_path, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as fout:
logger.debug('Writing configuration to %s', config_path)
fout.write(cfg)
with os.fdopen(os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as fout:
logger.debug('Writing certificate to %s', cert_path)
fout.write(certificate)
with os.fdopen(os.open(key_path, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as fout:
logger.debug('Writing key to %s', key_path)
fout.write(key)
logger.info('SlapOS client configuration: DONE')
...@@ -51,9 +51,13 @@ class RegisterCommand(Command): ...@@ -51,9 +51,13 @@ class RegisterCommand(Command):
ap.add_argument('--ipv6-interface', ap.add_argument('--ipv6-interface',
help='Interface name to get ipv6') help='Interface name to get ipv6')
ap.add_argument('--login-auth',
action='store_true',
help='Force login and password authentication')
ap.add_argument('--login', ap.add_argument('--login',
help="Your SlapOS Master login. If not provided, " help='Your SlapOS Master login. '
"asks it interactively, then password.") 'Asks it interactively, then password.')
ap.add_argument('--password', ap.add_argument('--password',
help='Your SlapOS Master password. If not provided, ' help='Your SlapOS Master password. If not provided, '
...@@ -61,8 +65,7 @@ class RegisterCommand(Command): ...@@ -61,8 +65,7 @@ class RegisterCommand(Command):
'should be avoided for security reasons.') 'should be avoided for security reasons.')
ap.add_argument('--token', ap.add_argument('--token',
help="SlapOS Master authentication token " help="SlapOS 'computer security' authentication token")
"(use '--token ask' for interactive prompt)")
ap.add_argument('-t', '--create-tap', ap.add_argument('-t', '--create-tap',
action='store_true', action='store_true',
......
...@@ -42,7 +42,6 @@ from slapos.cli_legacy.slapgrid import runSoftwareRelease as software ...@@ -42,7 +42,6 @@ from slapos.cli_legacy.slapgrid import runSoftwareRelease as software
from slapos.cli_legacy.slapgrid import runUsageReport as report from slapos.cli_legacy.slapgrid import runUsageReport as report
from slapos.cli_legacy.svcbackend import supervisord from slapos.cli_legacy.svcbackend import supervisord
from slapos.cli_legacy.svcbackend import supervisorctl from slapos.cli_legacy.svcbackend import supervisorctl
from slapos.cli_legacy.register import main as register
from slapos.version import version from slapos.version import version
# Note: this whole file is a hack. We should better try dedicated library # Note: this whole file is a hack. We should better try dedicated library
...@@ -125,9 +124,7 @@ def dispatch(command, is_node_command): ...@@ -125,9 +124,7 @@ def dispatch(command, is_node_command):
sys.stderr.write('This command must be run as root.\n') sys.stderr.write('This command must be run as root.\n')
sys.exit() sys.exit()
if command == 'register': if command == 'software':
call(register)
elif command == 'software':
call(software, config_path=GLOBAL_SLAPOS_CONFIGURATION, call(software, config_path=GLOBAL_SLAPOS_CONFIGURATION,
option=['--pidfile /opt/slapos/slapgrid-sr.pid']) option=['--pidfile /opt/slapos/slapgrid-sr.pid'])
elif command == 'instance': elif command == 'instance':
...@@ -192,7 +189,6 @@ Client subcommands usage: ...@@ -192,7 +189,6 @@ Client subcommands usage:
slapos console slapos console
Node subcommands usage: Node subcommands usage:
slapos node slapos node
slapos node register <node-id>
slapos node software slapos node software
slapos node instance slapos node instance
slapos node report slapos node report
......
# -*- coding: utf-8 -*-
# vim: set et sts=2:
##############################################################################
#
# Copyright (c) 2012 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import argparse
import logging
import sys
from slapos.register.register import do_register, RegisterConfig
def main():
ap = argparse.ArgumentParser()
ap.add_argument('node_name',
help='Name of the node')
ap.add_argument('--interface-name',
help='Interface name to access internet',
default='eth0')
ap.add_argument('--master-url',
help='URL of SlapOS master',
default='https://slap.vifib.com')
ap.add_argument('--master-url-web',
help='URL of SlapOS Master webservice to register certificates',
default='https://www.slapos.org')
ap.add_argument('--partition-number',
help='Number of partition on computer',
default='10',
type=int)
ap.add_argument('--ipv4-local-network',
help='Base of ipv4 local network',
default='10.0.0.0/16')
ap.add_argument('--ipv6-interface',
help='Interface name to get ipv6',
default='')
ap.add_argument('--login',
help='User login on SlapOS Master webservice')
ap.add_argument('--password',
help='User password on SlapOs Master webservice')
ap.add_argument('-t', '--create-tap',
help='Will trigger creation of one virtual "tap" interface per '
'Partition and attach it to primary interface. Requires '
'primary interface to be a bridge. defaults to false. '
'Needed to host virtual machines.',
default=False,
action='store_true')
ap.add_argument('-n', '--dry-run',
help='Simulate the execution steps',
default=False,
action='store_true')
args = ap.parse_args()
if args.password and not args.login:
ap.error('Please enter your login with your password')
logger = logging.getLogger('Register')
handler = logging.StreamHandler()
logger.setLevel(logging.DEBUG)
handler.setLevel(logging.INFO)
handler.setFormatter(logging.Formatter('%(levelname)s - %(message)s'))
logger.addHandler(handler)
try:
conf = RegisterConfig(logger=logger)
conf.setConfig(args)
return_code = do_register(conf)
except SystemExit as exc:
return_code = exc
sys.exit(return_code)
...@@ -135,9 +135,6 @@ def merged_options(args, configp): ...@@ -135,9 +135,6 @@ def merged_options(args, configp):
if options.get('all'): if options.get('all'):
options['develop'] = True options['develop'] = True
if options.get('maximum_periodicity') is not None:
options['force_periodicity'] = True
# Supervisord configuration location # Supervisord configuration location
if not options.get('supervisord_configuration_path'): if not options.get('supervisord_configuration_path'):
options['supervisord_configuration_path'] = \ options['supervisord_configuration_path'] = \
...@@ -199,8 +196,7 @@ def create_slapgrid_object(options, logger): ...@@ -199,8 +196,7 @@ def create_slapgrid_object(options, logger):
supervisord_configuration_path=op['supervisord_configuration_path'], supervisord_configuration_path=op['supervisord_configuration_path'],
buildout=op.get('buildout'), buildout=op.get('buildout'),
logger=logger, logger=logger,
force_periodicity=op.get('force_periodicity', False), maximum_periodicity = op.get('maximum_periodicity', 86400),
maximum_periodicity=op.get('maximum_periodicity', 86400),
key_file=op.get('key_file'), key_file=op.get('key_file'),
cert_file=op.get('cert_file'), cert_file=op.get('cert_file'),
signature_private_key_file=op.get('signature_private_key_file'), signature_private_key_file=op.get('signature_private_key_file'),
...@@ -256,7 +252,6 @@ class Slapgrid(object): ...@@ -256,7 +252,6 @@ class Slapgrid(object):
supervisord_configuration_path, supervisord_configuration_path,
buildout, buildout,
logger, logger,
force_periodicity=False,
maximum_periodicity=86400, maximum_periodicity=86400,
key_file=None, key_file=None,
cert_file=None, cert_file=None,
...@@ -331,7 +326,6 @@ class Slapgrid(object): ...@@ -331,7 +326,6 @@ class Slapgrid(object):
self.computer_partition_filter_list = \ self.computer_partition_filter_list = \
computer_partition_filter_list.split(",") computer_partition_filter_list.split(",")
self.maximum_periodicity = maximum_periodicity self.maximum_periodicity = maximum_periodicity
self.force_periodicity = force_periodicity
def getWatchdogLine(self): def getWatchdogLine(self):
invocation_list = [WATCHDOG_PATH] invocation_list = [WATCHDOG_PATH]
...@@ -576,15 +570,13 @@ class Slapgrid(object): ...@@ -576,15 +570,13 @@ class Slapgrid(object):
periodicity = self.maximum_periodicity periodicity = self.maximum_periodicity
if software_path: if software_path:
# Get periodicity from periodicity file if not forced periodicity_path = os.path.join(software_path, 'periodicity')
if not self.force_periodicity: if os.path.exists(periodicity_path):
periodicity_path = os.path.join(software_path, 'periodicity') try:
if os.path.exists(periodicity_path): periodicity = int(open(periodicity_path).read())
try: except ValueError:
periodicity = int(open(periodicity_path).read()) os.remove(periodicity_path)
except ValueError: self.logger.exception('')
os.remove(periodicity_path)
self.logger.exception('')
# Check if timestamp from server is more recent than local one. # Check if timestamp from server is more recent than local one.
# If not: it's not worth processing this partition (nothing has # If not: it's not worth processing this partition (nothing has
...@@ -595,13 +587,15 @@ class Slapgrid(object): ...@@ -595,13 +587,15 @@ class Slapgrid(object):
last_runtime = int(os.path.getmtime(timestamp_path)) last_runtime = int(os.path.getmtime(timestamp_path))
if timestamp: if timestamp:
try: try:
if int(timestamp) <= int(old_timestamp): if periodicity == 0:
os.remove(timestamp_path)
elif int(timestamp) <= int(old_timestamp):
if computer_partition.getState() != COMPUTER_PARTITION_STARTED_STATE: if computer_partition.getState() != COMPUTER_PARTITION_STARTED_STATE:
return return
# Check periodicity, i.e if periodicity is one day, partition # Check periodicity, i.e if periodicity is one day, partition
# should be processed at least every day. # should be processed at least every day.
# Only do it for "started" instances # Only do it for "started" instances
if int(time.time()) <= (last_runtime + periodicity): if int(time.time()) <= (last_runtime + periodicity) or periodicity < 0:
self.logger.info('Partition already up-to-date, skipping.') self.logger.info('Partition already up-to-date, skipping.')
return return
else: else:
......
...@@ -42,6 +42,8 @@ from subprocess import CalledProcessError ...@@ -42,6 +42,8 @@ from subprocess import CalledProcessError
import requests import requests
from slapos.util import parse_certificate_key_pair
def check_credentials(url, login, password): def check_credentials(url, login, password):
"""Check if login and password are correct""" """Check if login and password are correct"""
...@@ -49,7 +51,7 @@ def check_credentials(url, login, password): ...@@ -49,7 +51,7 @@ def check_credentials(url, login, password):
return 'Logout' in req.text return 'Logout' in req.text
def get_certificates(logger, master_url_web, node_name, token=None, login=None, password=None): def get_certificate_key_pair(logger, master_url_web, node_name, token=None, login=None, password=None):
"""Download certificates from SlapOS Master""" """Download certificates from SlapOS Master"""
if token: if token:
...@@ -65,7 +67,9 @@ def get_certificates(logger, master_url_web, node_name, token=None, login=None, ...@@ -65,7 +67,9 @@ def get_certificates(logger, master_url_web, node_name, token=None, login=None,
# raise a readable exception if the computer name is already used, # raise a readable exception if the computer name is already used,
# instead of an opaque 500 Internal Error. # instead of an opaque 500 Internal Error.
# this will not work with the new API. # this will not work with the new API.
logger.error('The node name "%s" is already in use. Please change the name, or revoke the active certificate if you want to replace the node.' % node_name) logger.error('The node name "%s" is already in use. '
'Please change the name, or revoke the active '
'certificate if you want to replace the node.', node_name)
sys.exit(1) sys.exit(1)
if req.status_code == 403: if req.status_code == 403:
...@@ -73,21 +77,16 @@ def get_certificates(logger, master_url_web, node_name, token=None, login=None, ...@@ -73,21 +77,16 @@ def get_certificates(logger, master_url_web, node_name, token=None, login=None,
msg = 'Please check the authentication token or require a new one.' msg = 'Please check the authentication token or require a new one.'
else: else:
msg = 'Please check username and password.' msg = 'Please check username and password.'
logger.error('Access denied to the SlapOS Master. %s', msg) logger.critical('Access denied to the SlapOS Master. %s', msg)
sys.exit(1)
elif not req.ok and 'NotImplementedError' in req.text and not token:
logger.critical('This SlapOS server does not support login/password '
'authentication. Please use the token.')
sys.exit(1) sys.exit(1)
else: else:
req.raise_for_status() req.raise_for_status()
return req.text return parse_certificate_key_pair(req.text)
def parse_certificates(source):
"""Parse html gotten from SlapOS Master to make certificate and key files"""
c_start = source.find("Certificate:")
c_end = source.find("</textarea>", c_start)
k_start = source.find("-----BEGIN PRIVATE KEY-----")
k_end = source.find("</textarea>", k_start)
return source[c_start:c_end], source[k_start:k_end]
def get_computer_name(certificate): def get_computer_name(certificate):
...@@ -115,7 +114,8 @@ def save_former_config(conf): ...@@ -115,7 +114,8 @@ def save_former_config(conf):
saved += '.1' saved += '.1'
else: else:
break break
conf.logger.info("Former slapos configuration detected in %s moving to %s" % (former, saved)) conf.logger.info('Former slapos configuration detected '
'in %s moving to %s', former, saved)
shutil.move(former, saved) shutil.move(former, saved)
...@@ -147,13 +147,13 @@ def slapconfig(conf): ...@@ -147,13 +147,13 @@ def slapconfig(conf):
directory = os.path.dirname(directory) directory = os.path.dirname(directory)
if not os.path.exists(slap_conf_dir): if not os.path.exists(slap_conf_dir):
conf.logger.info("Creating directory: %s" % slap_conf_dir) conf.logger.info('Creating directory: %s', slap_conf_dir)
if not dry_run: if not dry_run:
os.mkdir(slap_conf_dir, 0o711) os.mkdir(slap_conf_dir, 0o711)
user_certificate_repository_path = os.path.join(slap_conf_dir, 'ssl') user_certificate_repository_path = os.path.join(slap_conf_dir, 'ssl')
if not os.path.exists(user_certificate_repository_path): if not os.path.exists(user_certificate_repository_path):
conf.logger.info("Creating directory: %s" % user_certificate_repository_path) conf.logger.info('Creating directory: %s', user_certificate_repository_path)
if not dry_run: if not dry_run:
os.mkdir(user_certificate_repository_path, 0o711) os.mkdir(user_certificate_repository_path, 0o711)
...@@ -163,7 +163,7 @@ def slapconfig(conf): ...@@ -163,7 +163,7 @@ def slapconfig(conf):
(conf.key, key_file), (conf.key, key_file),
(conf.certificate, cert_file) (conf.certificate, cert_file)
]: ]:
conf.logger.info("Copying to %r, and setting minimum privileges" % dst) conf.logger.info('Copying to %r, and setting minimum privileges', dst)
if not dry_run: if not dry_run:
with open(dst, 'w') as destination: with open(dst, 'w') as destination:
destination.write(''.join(src)) destination.write(''.join(src))
...@@ -172,13 +172,13 @@ def slapconfig(conf): ...@@ -172,13 +172,13 @@ def slapconfig(conf):
certificate_repository_path = os.path.join(slap_conf_dir, 'ssl', 'partition_pki') certificate_repository_path = os.path.join(slap_conf_dir, 'ssl', 'partition_pki')
if not os.path.exists(certificate_repository_path): if not os.path.exists(certificate_repository_path):
conf.logger.info("Creating directory: %s" % certificate_repository_path) conf.logger.info('Creating directory: %s', certificate_repository_path)
if not dry_run: if not dry_run:
os.mkdir(certificate_repository_path, 0o711) os.mkdir(certificate_repository_path, 0o711)
# Put slapos configuration file # Put slapos configuration file
slap_conf_file = os.path.join(slap_conf_dir, 'slapos.cfg') slap_conf_file = os.path.join(slap_conf_dir, 'slapos.cfg')
conf.logger.info("Creating slap configuration: %s" % slap_conf_file) conf.logger.info('Creating slap configuration: %s', slap_conf_file)
# Get example configuration file # Get example configuration file
slapos_cfg_example = get_slapos_conf_example() slapos_cfg_example = get_slapos_conf_example()
...@@ -206,7 +206,7 @@ def slapconfig(conf): ...@@ -206,7 +206,7 @@ def slapconfig(conf):
with open(slap_conf_file, 'w') as fout: with open(slap_conf_file, 'w') as fout:
new_configp.write(fout) new_configp.write(fout)
conf.logger.info("SlapOS configuration: DONE") conf.logger.info('SlapOS configuration: DONE')
class RegisterConfig(object): class RegisterConfig(object):
...@@ -232,12 +232,12 @@ class RegisterConfig(object): ...@@ -232,12 +232,12 @@ class RegisterConfig(object):
self.key = key self.key = key
def displayUserConfig(self): def displayUserConfig(self):
self.logger.debug("Computer Name: %s" % self.node_name) self.logger.debug('Computer Name: %s', self.node_name)
self.logger.debug("Master URL: %s" % self.master_url) self.logger.debug('Master URL: %s', self.master_url)
self.logger.debug("Number of partition: %s" % self.partition_number) self.logger.debug('Number of partition: %s', self.partition_number)
self.logger.info("Using Interface %s" % self.interface_name) self.logger.info('Using Interface %s', self.interface_name)
self.logger.debug("Ipv4 sub network: %s" % self.ipv4_local_network) self.logger.debug('Ipv4 sub network: %s', self.ipv4_local_network)
self.logger.debug("Ipv6 Interface: %s" % self.ipv6_interface) self.logger.debug('Ipv6 Interface: %s', self.ipv6_interface)
def gen_auth(conf): def gen_auth(conf):
...@@ -255,15 +255,7 @@ def gen_auth(conf): ...@@ -255,15 +255,7 @@ def gen_auth(conf):
def do_register(conf): def do_register(conf):
"""Register new computer on SlapOS Master and generate slapos.cfg""" """Register new computer on SlapOS Master and generate slapos.cfg"""
if conf.token == 'ask': if conf.login or conf.login_auth:
while True:
conf.token = raw_input('SlapOS Token: ').strip()
if conf.token:
break
if conf.token:
certificate_key = get_certificates(conf.logger, conf.master_url_web, conf.node_name, token=conf.token)
else:
for login, password in gen_auth(conf): for login, password in gen_auth(conf):
if check_credentials(conf.master_url_web, login, password): if check_credentials(conf.master_url_web, login, password):
break break
...@@ -271,16 +263,29 @@ def do_register(conf): ...@@ -271,16 +263,29 @@ def do_register(conf):
else: else:
return 1 return 1
certificate_key = get_certificates(conf.logger, conf.master_url_web, conf.node_name, login=login, password=password) certificate, key = get_certificate_key_pair(conf.logger,
conf.master_url_web,
conf.node_name,
login=login,
password=password)
else:
while not conf.token:
conf.token = raw_input('Computer security token: ').strip()
certificate, key = get_certificate_key_pair(conf.logger,
conf.master_url_web,
conf.node_name,
token=conf.token)
# Parse certificate and key and get computer id # get computer id
certificate, key = parse_certificates(certificate_key)
COMP = get_computer_name(certificate) COMP = get_computer_name(certificate)
# Getting configuration parameters # Getting configuration parameters
conf.COMPConfig(slapos_configuration='/etc/opt/slapos/', conf.COMPConfig(slapos_configuration='/etc/opt/slapos/',
computer_id=COMP, computer_id=COMP,
certificate=certificate, certificate=certificate,
key=key) key=key)
# Save former configuration # Save former configuration
if not conf.dry_run: if not conf.dry_run:
save_former_config(conf) save_former_config(conf)
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
# #
############################################################################## ##############################################################################
from __future__ import absolute_import
import httplib import httplib
import logging import logging
import os import os
...@@ -40,6 +41,7 @@ import unittest ...@@ -40,6 +41,7 @@ import unittest
import urlparse import urlparse
import xml_marshaller import xml_marshaller
from mock import patch
import slapos.slap.slap import slapos.slap.slap
import slapos.grid.utils import slapos.grid.utils
...@@ -49,6 +51,7 @@ from slapos.grid.utils import md5digest ...@@ -49,6 +51,7 @@ from slapos.grid.utils import md5digest
from slapos.grid.watchdog import Watchdog, getWatchdogID from slapos.grid.watchdog import Watchdog, getWatchdogID
from slapos.grid import SlapObject from slapos.grid import SlapObject
dummylogger = logging.getLogger() dummylogger = logging.getLogger()
...@@ -232,13 +235,13 @@ class MasterMixin(BasicMixin): ...@@ -232,13 +235,13 @@ class MasterMixin(BasicMixin):
def _patchHttplib(self): def _patchHttplib(self):
"""Overrides httplib""" """Overrides httplib"""
import mock.httplib import slapos.tests.mock.httplib
self.saved_httplib = {} self.saved_httplib = {}
for fake in vars(mock.httplib): for fake in vars(slapos.tests.mock.httplib):
self.saved_httplib[fake] = getattr(httplib, fake, None) self.saved_httplib[fake] = getattr(httplib, fake, None)
setattr(httplib, fake, getattr(mock.httplib, fake)) setattr(httplib, fake, getattr(slapos.tests.mock.httplib, fake))
def _unpatchHttplib(self): def _unpatchHttplib(self):
"""Restores httplib overriding""" """Restores httplib overriding"""
...@@ -1026,7 +1029,6 @@ class TestSlapgridCPPartitionProcessing(MasterMixin, unittest.TestCase): ...@@ -1026,7 +1029,6 @@ class TestSlapgridCPPartitionProcessing(MasterMixin, unittest.TestCase):
instance.timestamp = timestamp instance.timestamp = timestamp
instance.requested_state = 'started' instance.requested_state = 'started'
instance.software.setPeriodicity(1) instance.software.setPeriodicity(1)
self.grid.force_periodicity = True
self.launchSlapgrid() self.launchSlapgrid()
partition = os.path.join(self.instance_root, '0') partition = os.path.join(self.instance_root, '0')
...@@ -1042,36 +1044,7 @@ class TestSlapgridCPPartitionProcessing(MasterMixin, unittest.TestCase): ...@@ -1042,36 +1044,7 @@ class TestSlapgridCPPartitionProcessing(MasterMixin, unittest.TestCase):
self.assertItemsEqual(os.listdir(partition), self.assertItemsEqual(os.listdir(partition),
['.timestamp', 'buildout.cfg', 'software_release', 'worked']) ['.timestamp', 'buildout.cfg', 'software_release', 'worked'])
def test_partition_periodicity_is_not_overloaded_if_forced(self):
"""
If periodicity file in software directory but periodicity is forced
periodicity will be the one given by parameter
1. We set force_periodicity parameter to True
2. We put a periodicity file in the software release directory
with an unwanted periodicity
3. We process partition list and wait more than unwanted periodicity
4. We relaunch, partition should not be processed
"""
computer = ComputerForTest(self.software_root, self.instance_root)
instance = computer.instance_list[0]
timestamp = str(int(time.time()))
instance.timestamp = timestamp
instance.requested_state = 'started'
unwanted_periodicity = 2
instance.software.setPeriodicity(unwanted_periodicity)
self.grid.force_periodicity = True
self.launchSlapgrid()
time.sleep(unwanted_periodicity + 1)
self.setSlapgrid()
self.grid.force_periodicity = True
self.assertEqual(self.grid.processComputerPartitionList(), slapgrid.SLAPGRID_SUCCESS)
self.assertNotEqual(unwanted_periodicity, self.grid.maximum_periodicity)
self.assertEqual(computer.sequence,
['getFullComputerInformation', 'availableComputerPartition',
'startedComputerPartition', 'getFullComputerInformation'])
def test_one_partition_periodicity_from_file_does_not_disturb_others(self): def test_one_partition_periodicity_from_file_does_not_disturb_others(self):
""" """
...@@ -1197,6 +1170,49 @@ class TestSlapgridCPPartitionProcessing(MasterMixin, unittest.TestCase): ...@@ -1197,6 +1170,49 @@ class TestSlapgridCPPartitionProcessing(MasterMixin, unittest.TestCase):
last_runtime) last_runtime)
self.assertNotEqual(wanted_periodicity, self.grid.maximum_periodicity) self.assertNotEqual(wanted_periodicity, self.grid.maximum_periodicity)
def test_one_partition_is_never_processed_when_periodicity_is_negative(self):
"""
Checks that a partition is not processed when
its periodicity is negative
1. We setup one instance and set periodicity at -1
2. We mock the install method from slapos.grid.slapgrid.Partition
3. We launch slapgrid once so that .timestamp file is created and check that install method is
indeed called (through mocked_method.called
4. We launch slapgrid anew and check that install as not been called again
"""
timestamp = str(int(time.time()))
computer = ComputerForTest(self.software_root, self.instance_root, 1, 1)
instance = computer.instance_list[0]
instance.software.setPeriodicity(-1)
instance.timestamp = timestamp
with patch.object(slapos.grid.slapgrid.Partition, 'install', return_value=None) as mock_method:
self.launchSlapgrid()
self.assertTrue(mock_method.called)
self.launchSlapgrid()
self.assertEqual(mock_method.call_count, 1)
def test_one_partition_is_always_processed_when_periodicity_is_zero(self):
"""
Checks that a partition is always processed when
its periodicity is 0
1. We setup one instance and set periodicity at 0
2. We mock the install method from slapos.grid.slapgrid.Partition
3. We launch slapgrid once so that .timestamp file is created
4. We launch slapgrid anew and check that install has been called twice (one time because of the
new setup and one time because of periodicity = 0)
"""
timestamp = str(int(time.time()))
computer = ComputerForTest(self.software_root, self.instance_root, 1, 1)
instance = computer.instance_list[0]
instance.software.setPeriodicity(0)
instance.timestamp = timestamp
with patch.object(slapos.grid.slapgrid.Partition, 'install', return_value=None) as mock_method:
self.launchSlapgrid()
self.launchSlapgrid()
self.assertEqual(mock_method.call_count, 2)
def test_one_partition_buildout_fail_does_not_disturb_others(self): def test_one_partition_buildout_fail_does_not_disturb_others(self):
""" """
1. We set up two instance one using a corrupted buildout 1. We set up two instance one using a corrupted buildout
...@@ -1557,26 +1573,6 @@ class TestSlapgridArgumentTuple(SlapgridInitialization): ...@@ -1557,26 +1573,6 @@ class TestSlapgridArgumentTuple(SlapgridInitialization):
slapgrid_object = parser(*self.default_arg_tuple)[0] slapgrid_object = parser(*self.default_arg_tuple)[0]
self.assertFalse(slapgrid_object.develop) self.assertFalse(slapgrid_object.develop)
def test_force_periodicity_if_periodicity_not_given(self):
"""
Check if not giving --maximum-periodicity triggers "force_periodicity"
option to be false.
"""
parser = parseArgumentTupleAndReturnSlapgridObject
slapgrid_object = parser(*self.default_arg_tuple)[0]
self.assertFalse(slapgrid_object.force_periodicity)
def test_force_periodicity_if_periodicity_given(self):
"""
Check if giving --maximum-periodicity triggers "force_periodicity" option.
"""
parser = parseArgumentTupleAndReturnSlapgridObject
slapgrid_object = parser('--maximum-periodicity', '40', *self.default_arg_tuple)[0]
self.assertTrue(slapgrid_object.force_periodicity)
class TestSlapgridConfigurationFile(SlapgridInitialization):
def test_upload_binary_cache_blacklist(self): def test_upload_binary_cache_blacklist(self):
""" """
Check if giving --upload-to-binary-cache-url-blacklist triggers option. Check if giving --upload-to-binary-cache-url-blacklist triggers option.
......
...@@ -27,3 +27,19 @@ def chownDirectory(path, uid, gid): ...@@ -27,3 +27,19 @@ def chownDirectory(path, uid, gid):
for item in items: for item in items:
if not os.path.islink(os.path.join(root, item)): if not os.path.islink(os.path.join(root, item)):
os.chown(os.path.join(root, item), uid, gid) os.chown(os.path.join(root, item), uid, gid)
def parse_certificate_key_pair(html):
"""
Extract (certificate, key) pair from an HTML page received by SlapOS Master.
"""
c_start = html.find("Certificate:")
c_end = html.find("</textarea>", c_start)
certificate = html[c_start:c_end]
k_start = html.find("-----BEGIN PRIVATE KEY-----")
k_end = html.find("</textarea>", k_start)
key = html[k_start:k_end]
return certificate, key
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment