Commit f23b4032 authored by Marco Mariani's avatar Marco Mariani

Merge remote-tracking branch 'origin/cliff'

parents 40d958a6 23e24e19
......@@ -6,7 +6,7 @@ SlapOS command line usage
Notes:
------
* Default SlapOS Master is http://www.slapos.org. It can be changed by altering configuration files or with the ``--master-url``
* Default SlapOS Master is https://slap.vifib.com. It can be changed by altering configuration files or with the ``--master-url``
argument for commands that support it.
* Most commands take a configuration file parameter, provided as ``--cfg /path/to/file.cfg``.
......@@ -20,7 +20,8 @@ Notes:
XXX TODO document 'alias' for software_url, software_group?, computer_group?
..
XXX TODO document 'alias' for software_url, software_group?, computer_group?
......@@ -59,7 +60,8 @@ Examples
$ slapos request mykvm kvm --node id=COMP-12345 --configuration \
nbd-host=debian.nbd.vifib.net nbd-port=1024
XXX Change in slaplib: allow to fetch instance params without changing anything. i.e we should do "slapos request myalreadyrequestedinstance" to fetch connection parameters without erasing previously defined instance parameters.
..
XXX Change in slaplib: allow to fetch instance params without changing anything. i.e we should do "slapos request myalreadyrequestedinstance" to fetch connection parameters without erasing previously defined instance parameters.
..
......@@ -93,7 +95,8 @@ remove
Ask Removal of a software from a specific node or group of nodes. Existing instances won't work anymore.
XXX "slapos autounsupply a.k.a slapos cleanup"
..
XXX "slapos autounsupply a.k.a slapos cleanup"
Examples
......@@ -160,24 +163,14 @@ If login is not provided, asks for user's SlapOS Master account then password.
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: --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)
..
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 SlapOS master web URL to register computer, so it needs the web URL (like http://www.slapos.org)
If Node is already registered (slapos.cfg and certificate already present), issues a warning, backups original configuration and creates new one.
XXX-Cedric should check for IPv6 in selected interface
Parameters:
***********
--login LOGIN Your SlapOS Master login. If not provided, asks it interactively.
--password PASSWORD Your SlapOS Master password. If not provided, asks it interactively. NOTE: giving password as parameter should be avoided for security reasons.
--interface-name INTERFACE Use interface as primary interface. IP of Partitions will be added to it. Defaults to "eth0".
--master-url URL URL of SlapOS Master REST API. defaults to "https://slap.vifib.com".
--master-url-web URL URL of SlapOS Master web access. defaults to "https://www.vifib.com".
--partition-number NUMBER Number of partitions that will have your SlapOS Node. defaults to "10".
--ipv4-local-network NETWORK Subnetwork used to assign local IPv4 addresses. It should be a not used network in order to avoid conflicts. defaults to 10.0.0.0/16.
-t, --create-tap 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.
..
XXX-Cedric should check for IPv6 in selected interface
Notes:
......@@ -217,7 +210,7 @@ node software
Return values:
**************
(Among other standard Python return values)
(among other standard Python return values)
* 0 Everything went fine
* 1 At least one software was not correctly installed.
......@@ -232,7 +225,7 @@ node instance
Return values:
**************
(Among other standard Python return values)
(among other standard Python return values)
* 0 Everything went fine
* 1 At least one instance was not correctly processed.
......@@ -250,7 +243,7 @@ Run instance reports and garbage collection.
Return values:
**************
(Among other standard Python return values)
(among other standard Python return values)
* 0 Everything went fine
* 1 At least one instance hasn't correctly been processed.
......@@ -266,9 +259,10 @@ node start|stop|restart|tail|status
Start/Stop/Restart/Show stdout/stderr of instance and/or process.
optional arguments:
-h, --help show this help message and exit
--cfg CFG SlapOS configuration file - defaults to
$SLAPOS_CONFIGURATION or /etc/opt/slapos/slapos.cfg
-h, --help show this help message and exit
--cfg CFG SlapOS configuration file (default: $SLAPOS_CONFIGURATION
or /etc/opt/slapos/slapos.cfg)
Examples
......
......@@ -2,6 +2,7 @@
import logging
from slapos.cli.command import must_be_root
from slapos.cli.config import ConfigCommand
from slapos.bang import do_bang
......@@ -19,6 +20,7 @@ class BangCommand(ConfigCommand):
help='Message for bang')
return ap
@must_be_root
def take_action(self, args):
configp = self.fetch_config(args)
do_bang(configp, args.message)
# -*- coding: utf-8 -*-
import argparse
import functools
import os
import sys
import cliff
class Command(cliff.command.Command):
def get_parser(self, prog_name):
parser = argparse.ArgumentParser(
description=self.get_description(),
......@@ -14,3 +18,13 @@ class Command(cliff.command.Command):
)
return parser
def must_be_root(func):
@functools.wraps(func)
def inner(self, *args, **kw):
if os.getuid() != 0:
self.app.log.error('This slapos command must be run as root.')
sys.exit(5)
return func(self, *args, **kw)
return inner
......@@ -22,39 +22,20 @@ class ConfigCommand(Command):
def get_parser(self, prog_name):
ap = super(ConfigCommand, self).get_parser(prog_name)
ap.add_argument('--cfg', help='SlapOS configuration file - ' +
'defaults to $%s ' % self.default_config_var +
'or %s' % self.default_config_path)
ap.add_argument('--cfg',
help='SlapOS configuration file'
' (default: $%s or %s)' %
(self.default_config_var, self.default_config_path))
return ap
def _get_config(self, cfg_path, required=False):
def fetch_config(self, args):
"""
Returns a configuration object if file exists/readable/valid,
None otherwise.
Will raise an error instead of returning None if required is True.
Even if required is False, may still raise an exception from the
configparser if the configuration content is very broken.
We don't catch that exception as it will clearly show what is
wrong with the file.
will raise an error otherwise. The exception may come from the
configparser itself if the configuration content is very broken,
and will clearly show what is wrong with the file.
"""
if not os.path.exists(cfg_path):
if required:
raise ConfigError('Configuration file does not exist: %s' % cfg_path)
else:
return None
configp = ConfigParser.SafeConfigParser()
if configp.read(cfg_path) != [cfg_path]:
# bad permission, etc.
if required:
raise ConfigError('Cannot parse configuration file: %s' % cfg_path)
else:
return None
return configp
def fetch_config(self, args):
if args.cfg:
cfg_path = args.cfg
else:
......@@ -64,7 +45,15 @@ class ConfigCommand(Command):
self.log.debug('Loading config: %s' % cfg_path)
return self._get_config(cfg_path, required=True)
if not os.path.exists(cfg_path):
raise ConfigError('Configuration file does not exist: %s' % cfg_path)
configp = ConfigParser.SafeConfigParser()
if configp.read(cfg_path) != [cfg_path]:
# bad permission, etc.
raise ConfigError('Cannot parse configuration file: %s' % cfg_path)
return configp
class ClientConfigCommand(ConfigCommand):
......
......@@ -72,7 +72,7 @@ class SlapOSApp(cliff.app.App):
'--log-file', '--logfile', '--log_file',
action='store',
default=None,
help='Specify a file to log output. Only console by default.',
help='Specify a file to log output (default: console only)',
)
# always show tracebacks on errors
......
......@@ -3,6 +3,7 @@
import logging
import sys
from slapos.cli.command import must_be_root
from slapos.cli.config import ConfigCommand
from slapos.format import do_format, FormatConfig, tracing_monkeypatch, UsageError
......@@ -18,12 +19,10 @@ class FormatCommand(ConfigCommand):
ap = super(FormatCommand, self).get_parser(prog_name)
ap.add_argument('-x', '--computer_xml',
help="Path to file with computer's XML. If does not exists, will be created",
default=None)
help="Path to file with computer's XML. If does not exists, will be created")
ap.add_argument('--computer_json',
help="Path to a JSON version of the computer's XML (for development only).",
default=None)
help="Path to a JSON version of the computer's XML (for development only)")
ap.add_argument('-i', '--input_definition_file',
help="Path to file to read definition of computer instead of "
......@@ -35,26 +34,33 @@ class FormatCommand(ConfigCommand):
help="Path to file to write definition of computer from "
"declaration.")
ap.add_argument('-n', '--dry_run',
help="Don't actually do anything.",
default=False,
action="store_true")
ap.add_argument('--alter_user',
choices=['True', 'False'],
help="Shall slapformat alter user database [default: True]")
default='True',
help='Shall slapformat alter user database'
' (default: %(default)s)')
ap.add_argument('--alter_network',
choices=['True', 'False'],
help="Shall slapformat alter network configuration [default: True]")
default='True',
help='Shall slapformat alter network configuration'
' (default: %(default)s)')
ap.add_argument('--now',
help="Launch slapformat without delay",
default=False,
action="store_true")
action="store_true",
help='Launch slapformat without delay'
' (default: %(default)s)')
ap.add_argument('-n', '--dry_run',
default=False,
action="store_true",
help="Don't actually do anything"
" (default: %(default)s)")
return ap
@must_be_root
def take_action(self, args):
configp = self.fetch_config(args)
......
......@@ -2,6 +2,7 @@
import logging
from slapos.cli.command import must_be_root
from slapos.cli.config import ConfigCommand
from slapos.proxy import do_proxy, ProxyConfig
......@@ -21,6 +22,7 @@ class ProxyStartCommand(ConfigCommand):
return ap
@must_be_root
def take_action(self, args):
configp = self.fetch_config(args)
......
......@@ -3,7 +3,7 @@
import logging
import sys
from slapos.cli.command import Command
from slapos.cli.command import Command, must_be_root
from slapos.register.register import do_register, RegisterConfig
......@@ -21,51 +21,63 @@ class RegisterCommand(Command):
help='Name of the node')
ap.add_argument('--interface-name',
help='Interface name to access internet',
default='eth0')
default='eth0',
help='Primary network interface. IP of Partitions '
'will be added to it'
' (default: %(default)s)')
ap.add_argument('--master-url',
help='URL of SlapOS master',
default='https://slap.vifib.com')
default='https://slap.vifib.com',
help='URL of SlapOS Master REST API'
' (default: %(default)s)')
ap.add_argument('--master-url-web',
help='URL of SlapOS Master webservice to register certificates',
default='https://www.slapos.org')
default='https://www.slapos.org',
help='URL of SlapOS Master webservice to register certificates'
' (default: %(default)s)')
ap.add_argument('--partition-number',
help='Number of partition on computer',
default='10',
type=int)
default=10,
type=int,
help='Number of partitions to create in the SlapOS Node'
' (default: %(default)s)')
ap.add_argument('--ipv4-local-network',
help='Base of ipv4 local network',
default='10.0.0.0/16')
default='10.0.0.0/16',
help='Subnetwork used to assign local IPv4 addresses. '
'It should be a not used network in order to avoid conflicts'
' (default: %(default)s)')
ap.add_argument('--ipv6-interface',
help='Interface name to get ipv6',
default='')
help='Interface name to get ipv6')
ap.add_argument('--login',
help='User login on SlapOS Master webservice')
help="Your SlapOS Master login. If not provided, "
"asks it interactively, then password.")
ap.add_argument('--password',
help='User password on SlapOs Master webservice')
help='Your SlapOS Master password. If not provided, '
'asks it interactively. NOTE: giving password as parameter '
'should be avoided for security reasons.')
ap.add_argument('-t', '--create-tap',
default=False,
action='store_true',
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')
'primary interface to be a bridge. '
'Needed to host virtual machines'
' (default: %(default)s)')
ap.add_argument('-n', '--dry-run',
help='Simulate the execution steps',
default=False,
action='store_true')
action='store_true',
help='Simulate the execution steps'
' (default: %(default)s)')
return ap
@must_be_root
def take_action(self, args):
try:
conf = RegisterConfig(logger=self.log)
......
......@@ -2,6 +2,7 @@
import logging
from slapos.cli.command import must_be_root
from slapos.cli.config import ConfigCommand
from slapos.grid.utils import setRunning, setFinished
......@@ -33,7 +34,7 @@ class SlapgridCommand(ConfigCommand):
help='The socket supervisor will use.')
ap.add_argument('--supervisord-configuration-path',
help='The location where supervisord configuration will be stored.')
ap.add_argument('--buildout', default=None,
ap.add_argument('--buildout',
help='Location of buildout binary.')
ap.add_argument('--pidfile',
help='The location where pidfile will be created. '
......@@ -49,14 +50,20 @@ class SlapgridCommand(ConfigCommand):
help='Root certificate of SlapOS master key.')
ap.add_argument('--certificate_repository_path',
help='Path to directory where downloaded certificates would be stored.')
ap.add_argument('--maximum-periodicity', type=int, default=None,
ap.add_argument('--maximum-periodicity',
type=int,
help='Periodicity at which buildout should be run in instance.')
ap.add_argument('--promise-timeout', type=int, default=3,
help='Promise timeout in seconds.')
ap.add_argument('--now', action='store_true',
ap.add_argument('--promise-timeout',
default=3,
type=int,
help='Promise timeout in seconds'
' (default: %(default)s)')
ap.add_argument('--now',
action='store_true',
help='Launch slapgrid without delay. Default behavior.')
return ap
@must_be_root
def take_action(self, args):
configp = self.fetch_config(args)
options = merged_options(args, configp)
......
......@@ -4,6 +4,7 @@ import argparse
import logging
import os
from slapos.cli.command import must_be_root
from slapos.cli.config import ConfigCommand
from slapos.grid.svcbackend import launchSupervisord
......@@ -22,6 +23,7 @@ class SupervisorctlCommand(ConfigCommand):
help='parameters passed to supervisorctl')
return ap
@must_be_root
def take_action(self, args):
configp = self.fetch_config(args)
instance_root = configp.get('slapos', 'instance_root')
......
......@@ -3,6 +3,7 @@
import logging
import os
from slapos.cli.command import must_be_root
from slapos.cli.config import ConfigCommand
from slapos.grid.svcbackend import launchSupervisord
......@@ -12,6 +13,7 @@ class SupervisordCommand(ConfigCommand):
log = logging.getLogger('supervisord')
@must_be_root
def take_action(self, args):
configp = self.fetch_config(args)
instance_root = configp.get('slapos', 'instance_root')
......
......@@ -20,7 +20,7 @@ class SupplyCommand(ClientConfigCommand):
help='Your software url')
ap.add_argument('node',
help="Target node")
help='Target node')
return ap
......
......@@ -294,7 +294,7 @@ class Computer(object):
except:
# might be a corrupted zip file. let's move it out of the way and retry.
shutil.move(path_to_archive,
path_to_archive+time.strftime('_broken_%Y%m%d-%H:%M'))
path_to_archive + time.strftime('_broken_%Y%m%d-%H:%M'))
try:
self.backup_xml(path_to_archive, path_to_xml)
except:
......@@ -376,9 +376,9 @@ class Computer(object):
for path in self.instance_root, self.software_root:
if not os.path.exists(path):
os.makedirs(path, 0755)
os.makedirs(path, 0o755)
else:
os.chmod(path, 0755)
os.chmod(path, 0o755)
# own self.software_root by software user
slapsoft = User(self.software_user)
......@@ -387,7 +387,7 @@ class Computer(object):
slapsoft.create()
slapsoft_pw = pwd.getpwnam(slapsoft.name)
os.chown(self.software_root, slapsoft_pw.pw_uid, slapsoft_pw.pw_gid)
os.chmod(self.software_root, 0755)
os.chmod(self.software_root, 0o755)
# Speed hack:
# Blindly add all IPs from existing configuration, just to speed up actual
......@@ -442,7 +442,7 @@ class Computer(object):
# There should be two addresses on each Computer Partition:
# * global IPv6
# * local IPv4, took from slapformat:ipv4_local_network
if len(partition.address_list) == 0:
if not partition.address_list:
# regenerate
partition.address_list.append(self.interface.addIPv4LocalAddress())
partition.address_list.append(self.interface.addAddr())
......@@ -454,11 +454,11 @@ class Computer(object):
raise ValueError(
'There should be exactly 2 stored addresses. Got: %r' %
(old_partition_address_list,))
if not any([netaddr.valid_ipv6(q['addr'])
for q in old_partition_address_list]):
if not any(netaddr.valid_ipv6(q['addr'])
for q in old_partition_address_list):
raise ValueError('Not valid ipv6 addresses loaded')
if not any([netaddr.valid_ipv4(q['addr'])
for q in old_partition_address_list]):
if not any(netaddr.valid_ipv4(q['addr'])
for q in old_partition_address_list):
raise ValueError('Not valid ipv6 addresses loaded')
for address in old_partition_address_list:
......@@ -510,11 +510,11 @@ class Partition(object):
self.path = os.path.abspath(self.path)
owner = self.user if self.user else User('root')
if not os.path.exists(self.path):
os.mkdir(self.path, 0750)
os.mkdir(self.path, 0o750)
if alter_user:
owner_pw = pwd.getpwnam(owner.name)
os.chown(self.path, owner_pw.pw_uid, owner_pw.pw_gid)
os.chmod(self.path, 0750)
os.chmod(self.path, 0o750)
class User(object):
......@@ -705,10 +705,15 @@ class Interface(object):
"""
if not socket.AF_INET in netifaces.ifaddresses(self.name):
return []
return [dict(addr=q['addr'], netmask=q['netmask']) for q in
netifaces.ifaddresses(self.name)[socket.AF_INET] if netaddr.IPAddress(
q['addr'], 4) in netaddr.glob_to_iprange(
netaddr.cidr_to_glob(self.ipv4_local_network))]
return [
{
'addr': q['addr'],
'netmask': q['netmask']
}
for q in netifaces.ifaddresses(self.name)[socket.AF_INET]
if netaddr.IPAddress(q['addr'], 4) in netaddr.glob_to_iprange(
netaddr.cidr_to_glob(self.ipv4_local_network))
]
def getGlobalScopeAddressList(self):
"""Returns currently configured global scope IPv6 addresses"""
......@@ -717,9 +722,11 @@ class Interface(object):
else:
interface_name = self.name
try:
address_list = [q
for q in netifaces.ifaddresses(interface_name)[socket.AF_INET6]
if isGlobalScopeAddress(q['addr'].split('%')[0])]
address_list = [
q
for q in netifaces.ifaddresses(interface_name)[socket.AF_INET6]
if isGlobalScopeAddress(q['addr'].split('%')[0])
]
except KeyError:
raise ValueError("%s must have at least one IPv6 address assigned" % \
interface_name)
......@@ -856,7 +863,7 @@ class Interface(object):
# confirmed to be configured
return dict(addr=addr, netmask=netmask)
def addAddr(self, addr = None, netmask = None):
def addAddr(self, addr=None, netmask=None):
"""
Adds IP address to interface.
......@@ -940,8 +947,10 @@ def parse_computer_definition(conf, definition_path):
address, netmask = computer_definition.get('computer', 'address').split('/')
if conf.alter_network and conf.interface_name is not None \
and conf.ipv4_local_network is not None:
interface = Interface(conf.logger, conf.interface_name, conf.ipv4_local_network,
conf.ipv6_interface)
interface = Interface(logger=conf.logger,
name=conf.interface_name,
ipv4_local_network=conf.ipv4_local_network,
ipv6_interface=conf.ipv6_interface)
computer = Computer(
reference=conf.computer_id,
interface=interface,
......@@ -971,21 +980,24 @@ def parse_computer_definition(conf, definition_path):
def parse_computer_xml(conf, xml_path):
interface = Interface(logger=conf.logger,
name=conf.interface_name,
ipv4_local_network=conf.ipv4_local_network,
ipv6_interface=conf.ipv6_interface)
if os.path.exists(xml_path):
conf.logger.info('Loading previous computer data from %r' % xml_path)
computer = Computer.load(xml_path,
reference=conf.computer_id,
ipv6_interface=conf.ipv6_interface)
# Connect to the interface defined by the configuration
computer.interface = Interface(conf.logger, conf.interface_name, conf.ipv4_local_network,
conf.ipv6_interface)
computer.interface = interface
else:
# If no pre-existent configuration found, create a new computer object
conf.logger.warning('Creating new data computer with id %r' % conf.computer_id)
computer = Computer(
reference=conf.computer_id,
interface=Interface(conf.logger, conf.interface_name, conf.ipv4_local_network,
conf.ipv6_interface),
interface=interface,
addr=None,
netmask=None,
ipv6_interface=conf.ipv6_interface,
......@@ -994,29 +1006,25 @@ def parse_computer_xml(conf, xml_path):
partition_amount = int(conf.partition_amount)
existing_partition_amount = len(computer.partition_list)
if existing_partition_amount > partition_amount:
if partition_amount < existing_partition_amount:
raise ValueError('Requested amount of computer partitions (%s) is lower '
'then already configured (%s), cannot continue' % (partition_amount,
len(computer.partition_list)))
existing_partition_amount))
conf.logger.info('Adding %s new partitions' %
(partition_amount - existing_partition_amount))
for nb_iter in range(existing_partition_amount, partition_amount):
# add new ones
user = User("%s%s" % (conf.user_base_name, nb_iter))
tap = Tap("%s%s" % (conf.tap_base_name, nb_iter))
path = os.path.join(conf.instance_root, "%s%s" % (
conf.partition_base_name, nb_iter))
computer.partition_list.append(
Partition(
reference="%s%s" % (conf.partition_base_name, nb_iter),
path=path,
user=user,
address_list=None,
tap=tap,
))
for i in range(existing_partition_amount, partition_amount):
# add new partitions
partition = Partition(
reference='%s%s' % (conf.partition_base_name, i),
path=os.path.join(conf.instance_root, '%s%s' % (
conf.partition_base_name, i)),
user=User('%s%s' % (conf.user_base_name, i)),
address_list=None,
tap=Tap('%s%s' % (conf.tap_base_name, i))
)
computer.partition_list.append(partition)
return computer
......@@ -1047,8 +1055,8 @@ def random_delay(conf):
# --maximal-delay=3600
if not conf.now:
duration = float(60 * 60) * random.random()
print("Sleeping for %s seconds. To disable this feature, " \
"use with --now parameter in manual." % duration)
conf.logger.info('Sleeping for %s seconds. To disable this feature, '
'use with --now parameter in manual.' % duration)
time.sleep(duration)
......@@ -1170,17 +1178,17 @@ class FormatConfig(object):
self.create_tap = True
# Convert strings to booleans
for o in ['alter_network', 'alter_user', 'create_tap']:
attr = getattr(self, o)
for option in ['alter_network', 'alter_user', 'create_tap']:
attr = getattr(self, option)
if isinstance(attr, str):
if attr.lower() == 'true':
root_needed = True
setattr(self, o, True)
setattr(self, option, True)
elif attr.lower() == 'false':
setattr(self, o, False)
setattr(self, option, False)
else:
message = 'Option %r needs to be "True" or "False", wrong value: ' \
'%r' % (o, getattr(self, o))
'%r' % (option, getattr(self, option))
self.logger.error(message)
raise UsageError(message)
......@@ -1203,6 +1211,7 @@ class FormatConfig(object):
root_needed = False
# check root
# XXX in the new CLI, this is checked by the @must_be_root decorator.
if root_needed and os.getuid() != 0:
message = "Root rights are needed"
self.logger.error(message)
......
......@@ -294,19 +294,33 @@ class Partition(object):
self._updateCertificate()
def _updateCertificate(self):
if not os.path.exists(self.key_file) or not os.path.exists(self.cert_file):
self.logger.info('Certificate and key not found, downloading to %r and '
'%r' % (self.cert_file, self.key_file))
try:
partition_certificate = self.computer_partition.getCertificate()
except NotFoundError:
raise NotFoundError('Partition %s is not known from SlapOS Master.' %
self.partition_id)
open(self.key_file, 'w').write(partition_certificate['key'])
open(self.cert_file, 'w').write(partition_certificate['certificate'])
for f in [self.key_file, self.cert_file]:
os.chmod(f, 0o400)
os.chown(f, *self.getUserGroupId())
try:
partition_certificate = self.computer_partition.getCertificate()
except NotFoundError:
raise NotFoundError('Partition %s is not known by SlapOS Master.' %
self.partition_id)
uid, gid = self.getUserGroupId()
for name, path in [
('certificate', self.cert_file),
('key', self.key_file),
]:
new_content = partition_certificate[name]
old_content = None
if os.path.exists(path):
old_content = open(path).read()
if old_content != new_content:
if old_content is None:
self.logger.info('Missing %s file. Creating %r' % (name, path))
else:
self.logger.info('Changed %s content. Updating %r' % (name, path))
with os.fdopen(os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o400), 'wb') as fout:
fout.write(new_content)
os.chown(path, uid, gid)
def getUserGroupId(self):
"""Returns tuple of (uid, gid) of partition"""
......@@ -338,7 +352,7 @@ class Partition(object):
def updateSymlink(self, sr_symlink, software_path):
if os.path.lexists(sr_symlink):
if not os.path.islink(sr_symlink):
self.logger.debug('Not a symlink: %s, has been ignored' % (sr_symlink))
self.logger.debug('Not a symlink: %s, has been ignored' % sr_symlink)
return
os.unlink(sr_symlink)
os.symlink(software_path, sr_symlink)
......
......@@ -228,6 +228,13 @@ def create_slapgrid_object(options, logger):
computer_partition_filter_list=op.get('only-cp', op.get('only_cp')))
def check_required_only_partitions(existing, required):
missing = set(required) - set(existing)
if missing:
plural = ['s', ''][len(missing)==1]
raise ValueError('Unknown partition%s: %s' % (plural, ', '.join(sorted(missing))))
class Slapgrid(object):
""" Main class for SlapGrid. Fetches and processes informations from master
server and pushes usage information to master server.
......@@ -737,6 +744,9 @@ class Slapgrid(object):
# Boolean to know if every promises correctly passed
clean_run_promise = True
check_required_only_partitions([cp.getId() for cp in self.getComputerPartitionList()],
self.computer_partition_filter_list)
# Filter all dummy / empty partitions
computer_partition_list = self.FilterComputerPartitionList(
self.getComputerPartitionList())
......
......@@ -258,7 +258,10 @@ def supplySupply():
url = request.form['url']
computer_id = request.form['computer_id']
if app.config['computer_id'] == computer_id:
execute_db('software', 'INSERT OR REPLACE INTO %s VALUES(?)', [url])
if request.form['state'] == 'destroyed':
execute_db('software', 'DELETE FROM %s WHERE url = ?', [url])
else:
execute_db('software', 'INSERT OR REPLACE INTO %s VALUES(?)', [url])
else:
raise UnauthorizedError, "Only accept request for: %s" % \
app.config['computer_id']
......
......@@ -36,12 +36,14 @@ __all__ = ["slap", "ComputerPartition", "Computer", "SoftwareRelease",
import httplib
import logging
import re
import socket
import ssl
import traceback
import urllib
import urlparse
from xml.sax import saxutils
import zope.interface
from interface import slap as interface
from xml_marshaller import xml_marshaller
......@@ -339,6 +341,19 @@ class Computer(SlapDocument):
return xml_marshaller.loads(xml)
def parsed_error_message(status, body, path):
m = re.search('(Error Value:\n.*)', body, re.MULTILINE)
if m:
match = ' '.join(line.strip() for line in m.group(0).split('\n'))
return '%s (status %s while calling %s)' % (
saxutils.unescape(match),
status,
path
)
else:
return 'Server responded with wrong code %s with %s' % (status, path)
class ComputerPartition(SlapRequester):
zope.interface.implements(interface.IComputerPartition)
......@@ -594,8 +609,9 @@ class ConnectionHelper:
elif self.response.status == httplib.FORBIDDEN:
raise Unauthorized(path)
elif self.response.status != httplib.OK:
message = 'Server responded with wrong code %s with %s' % \
(self.response.status, path)
message = parsed_error_message(self.response.status,
self.response.read(),
path)
raise ServerError(message)
finally:
socket.setdefaulttimeout(default_timeout)
......@@ -625,8 +641,9 @@ class ConnectionHelper:
elif self.response.status == httplib.FORBIDDEN:
raise Unauthorized("%s - %s" % (path, parameter_dict))
elif self.response.status != httplib.OK:
message = 'Server responded with wrong code %s with %s' % \
(self.response.status, path)
message = parsed_error_message(self.response.status,
self.response.read(),
path)
raise ServerError(message)
finally:
socket.setdefaulttimeout(default_timeout)
......
......@@ -107,7 +107,7 @@ class TestcheckOption (BasicMixin, unittest.TestCase):
option = "--logfile /opt/slapgrid/slapformat.log"
entry.checkOption(option)
self.assertNotEqual(original_sysargv, sys.argv)
self.assertTrue(option in " ".join(sys.argv))
self.assertIn(option, ' '.join(sys.argv))
class TestCall (BasicMixin, unittest.TestCase):
......@@ -133,7 +133,7 @@ class TestCall (BasicMixin, unittest.TestCase):
self.assertEqual(e[0], 0)
self.assertNotEqual(original_sysargv, sys.argv)
for x in options:
self.assertTrue(x in " ".join(sys.argv))
self.assertIn(x, ' '.join(sys.argv))
self.assertEqual(config_path, sys.argv[1])
def test_config_and_missing_option_are_added(self):
......@@ -158,7 +158,7 @@ class TestCall (BasicMixin, unittest.TestCase):
self.assertEqual(e[0], 0)
self.assertNotEqual(original_sysargv, sys.argv)
for x in (missing_option, present_option):
self.assertTrue(x in " ".join(sys.argv))
self.assertIn(x, ' '.join(sys.argv))
self.assertFalse(default_present_option in " ".join(sys.argv))
self.assertEqual(config_path, sys.argv[1])
......
......@@ -91,10 +91,8 @@ class TestSlap(SlapMixin):
"""
slap_instance = slapos.slap.slap()
slap_instance.initializeConnection(self.server_url)
self.assertTrue(
slap_instance._connection_helper.host in self.server_url)
self.assertTrue(
slap_instance._connection_helper.path in self.server_url)
self.assertIn(slap_instance._connection_helper.host, self.server_url)
self.assertIn(slap_instance._connection_helper.path, self.server_url)
def test_slap_initialisation_wrong_url(self):
"""
......@@ -785,7 +783,7 @@ class TestOpenOrder(SlapMixin):
computer_partition = open_order.request(software_release_uri, 'myrefe')
self.assertTrue(isinstance(computer_partition,
slapos.slap.ComputerPartition))
self.assertTrue(requested_partition_id, computer_partition.getId())
self.assertEqual(requested_partition_id, computer_partition.getId())
if __name__ == '__main__':
print 'You can point to any SLAP server by setting TEST_SLAP_SERVER_URL '\
......
This diff is collapsed.
......@@ -27,7 +27,7 @@
import logging
import os
import unittest
import unittest2
from slapos.grid import SlapObject
from slapos.grid import utils
......@@ -53,7 +53,7 @@ originalBootstrapBuildout = utils.bootstrapBuildout
originalLaunchBuildout = utils.launchBuildout
originalUploadSoftwareRelease = SlapObject.Software.uploadSoftwareRelease
class TestSoftwareSlapObject(BasicMixin, unittest.TestCase):
class TestSoftwareSlapObject(BasicMixin, unittest2.TestCase):
"""
Test for Software class.
"""
......@@ -107,22 +107,14 @@ class TestSoftwareSlapObject(BasicMixin, unittest.TestCase):
software.install()
command_list = FakeCallAndRead.external_command_list
self.assertTrue('buildout:networkcache-section=networkcache'
in command_list)
self.assertTrue('networkcache:signature-private-key-file=%s' %
self.signature_private_key_file in command_list)
self.assertTrue('networkcache:upload-cache-url=%s' % self.upload_cache_url
in command_list)
self.assertTrue('networkcache:upload-dir-url=%s' % self.upload_dir_url
in command_list)
self.assertTrue('networkcache:shacache-cert-file=%s' % self.shacache_cert_file
in command_list)
self.assertTrue('networkcache:shacache-key-file=%s' % self.shacache_key_file
in command_list)
self.assertTrue('networkcache:shadir-cert-file=%s' % self.shadir_cert_file
in command_list)
self.assertTrue('networkcache:shadir-key-file=%s' % self.shadir_key_file
in command_list)
self.assertIn('buildout:networkcache-section=networkcache', command_list)
self.assertIn('networkcache:signature-private-key-file=%s' % self.signature_private_key_file, command_list)
self.assertIn('networkcache:upload-cache-url=%s' % self.upload_cache_url, command_list)
self.assertIn('networkcache:upload-dir-url=%s' % self.upload_dir_url, command_list)
self.assertIn('networkcache:shacache-cert-file=%s' % self.shacache_cert_file, command_list)
self.assertIn('networkcache:shacache-key-file=%s' % self.shacache_key_file, command_list)
self.assertIn('networkcache:shadir-cert-file=%s' % self.shadir_cert_file, command_list)
self.assertIn('networkcache:shadir-key-file=%s' % self.shadir_key_file, command_list)
def test_software_install_without_networkcache(self):
"""
......
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