# Copyright (C) 2010-2014 GRNET S.A.
#
# 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, see .
import re
import datetime
import simplejson
import copy
from snfdeploy import base
from snfdeploy import config
from snfdeploy import constants
from snfdeploy import context
from snfdeploy.lib import FQDN
#
# Helper decorators that wrap methods of Component
# class and update its execution context (self, self.ctx, context):
#
def parse_user_info(fn):
""" Parse the output of DB.get_user_info_from_db()
For the given user email found in config,
update user_id, user_auth_token and user_uuid attributes of context.
"""
def wrapper(*args, **kwargs):
user_email = config.user_email
result = fn(*args, **kwargs)
r = re.compile(r"(\d+)[ |]*(\S+)[ |]*(\S+)[ |]*" + user_email, re.M)
match = r.search(result)
if config.dry_run:
context.user_id, context.user_auth_token, context.user_uuid = \
("dummy_uid", "dummy_user_auth_token", "dummy_user_uuid")
elif match:
context.user_id, context.user_auth_token, context.user_uuid = \
match.groups()
else:
raise BaseException("Cannot parse info for user %s" % user_email)
return result
return wrapper
def parse_service_info(fn):
""" Parse the output of Astakos.get_services()
For the given service (found in ctx.admin_service)
updates service_id and service_token attributes of context.
"""
def wrapper(*args, **kwargs):
cl = args[0]
service = cl.ctx.admin_service
result = fn(*args, **kwargs)
r = re.compile(r"(\d+)[ ]*%s[ ]*(\S+)" % service, re.M)
match = r.search(result)
if config.dry_run:
context.service_id, context.service_token = \
("dummy_service_id", "dummy_service_token")
elif match:
context.service_id, context.service_token = \
match.groups()
else:
raise BaseException("Cannot parse info for service %s" % service)
return result
return wrapper
def parse_backend_info(fn):
""" Parse the output of Cyclades.list_backends()
For the given cluster (found in ctx.admin_cluster)
updates the backend_id attributes of context.
"""
def wrapper(*args, **kwargs):
cl = args[0]
fqdn = cl.ctx.admin_cluster.fqdn
result = fn(*args, **kwargs)
r = re.compile(r"(\d+)[ ]*%s.*" % fqdn, re.M)
match = r.search(result)
if config.dry_run:
context.backend_id = "dummy_backend_id"
elif match:
context.backend_id, = match.groups()
else:
raise BaseException("Cannot parse info for backend %s" % fqdn)
return result
return wrapper
def update_admin(fn):
""" Initializes the admin roles for each component
Initialize the admin roles (NS, Astakos, Cyclades, etc.) and make them
available under self.NS, self.ASTAKOS, etc. These have the same execution
context of the current components besides the target node which gets
derived from the corresponding config.
"""
def wrapper(*args, **kwargs):
"""If used as decorator of a class method first argument is self."""
cl = args[0]
ctx = copy.deepcopy(cl.ctx)
ctx.admin_service = cl.service
ctx.admin_cluster = cl.cluster
ctx.admin_node = cl.node
ctx.admin_fqdn = cl.fqdn
cl.NS = NS(node=ctx.ns.node, ctx=ctx)
cl.NFS = NFS(node=ctx.nfs.node, ctx=ctx)
cl.DB = DB(node=ctx.db.node, ctx=ctx)
cl.ASTAKOS = Astakos(node=ctx.astakos.node, ctx=ctx)
cl.CYCLADES = Cyclades(node=ctx.cyclades.node, ctx=ctx)
return fn(*args, **kwargs)
return wrapper
def update_cluster_admin(fn):
""" Initializes the cluster admin roles for each component
Finds the master role for the corresponding cluster
"""
def wrapper(*args, **kwargs):
"""If used as decorator of a class method first argument is self."""
cl = args[0]
ctx = copy.deepcopy(cl.ctx)
ctx.admin_cluster = cl.cluster
cl.MASTER = Master(node=ctx.master.node, ctx=ctx)
return fn(*args, **kwargs)
return wrapper
def export_and_import_service(fn):
""" Export and import synnefo service
Used in Astakos, Pithos, and Cyclades service admin_post method
"""
def wrapper(*args, **kwargs):
cl = args[0]
f = config.jsonfile
cl.export_service()
cl.get(f, f + ".local")
cl.ASTAKOS.put(f + ".local", f)
cl.ASTAKOS.import_service()
return fn(*args, **kwargs)
return wrapper
# ########################## Components ############################
# A Component() gets initialized with an execution context that is a
# configuration snapshot of the target setup, cluster and node. A
# component implements the following helper methods: check, install,
# prepare, configure, restart, initialize, and test. All those methods
# will be executed on the target node with this order during setup.
#
# Additionally each Component class implements admin_pre, and
# admin_post methods which invoke actions on different components on
# the same execution context before and after installation. For
# example before a backend gets installed, its FQDN must resolve to
# the master floating IP, so we have to run some actions on the ns
# node and after installation we must add it to cyclades (snf-manage
# backend-add on the cyclades node).
#
# Component() inherits ComponentRunner() which practically exports the
# setup() method. This will first check if the required components are
# installed, will install them if not and update the status of target
# node.
#
# ComponentRunner() inherits FabricRunner() which practically wraps
# basic fabric commands (put, get, run) with the correct execution
# environment.
#
# Each component gets initialized with an execution context and uses
# the config module for accessing global wide options. The context
# provides node, cluster, and setup related info.
class HW(base.Component):
@base.run_cmds
def test(self):
return [
"ping -c 1 %s" % self.node.ip,
"ping -c 1 www.google.com",
"apt-get update",
]
class SSH(base.Component):
@base.run_cmds
def prepare(self):
return [
"mkdir -p /root/.ssh",
"for f in $(ls /root/.ssh/*); do cp $f $f.bak ; done",
"echo StrictHostKeyChecking no >> /etc/ssh/ssh_config",
]
def _configure(self):
files = [
"authorized_keys", "id_dsa", "id_dsa.pub", "id_rsa", "id_rsa.pub"
]
ssh = [("/root/.ssh/%s" % f, {}, {"mode": 0600}) for f in files]
return ssh
@base.run_cmds
def initialize(self):
f = "/root/.ssh/authorized_keys"
return [
"test -e {0}.bak && cat {0}.bak >> {0} || true".format(f)
]
@base.run_cmds
def test(self):
return ["ssh %s date" % self.node.ip]
class DNS(base.Component):
@update_admin
def admin_pre(self):
self.NS.update_ns()
@base.run_cmds
def prepare(self):
return [
"chattr -i /etc/resolv.conf",
"sed -i 's/^127.*$/127.0.0.1 localhost/g' /etc/hosts",
"echo %s > /etc/hostname" % self.node.hostname,
"hostname %s" % self.node.hostname
]
def _configure(self):
r1 = {
"date": str(datetime.datetime.today()),
"domain": self.node.domain,
"ns_node_ip": self.ctx.ns.ip,
}
resolv = [
("/etc/resolv.conf", r1, {})
]
return resolv
@base.run_cmds
def initialize(self):
return ["chattr +i /etc/resolv.conf"]
class DDNS(base.Component):
REQUIRED_PACKAGES = [
"dnsutils",
]
@base.run_cmds
def prepare(self):
return [
"mkdir -p /root/ddns/"
]
def _configure(self):
return [
("/root/ddns/" + k, {}, {}) for k in config.ddns_keys
]
class NS(base.Component):
REQUIRED_PACKAGES = [
"bind9",
]
alias = constants.NS
def required_components(self):
return [HW, SSH, DDNS]
def _nsupdate(self, cmd):
ret = """
nsupdate -k {0} > /dev/null <> /etc/apt/apt.conf",
"curl -k https://dev.grnet.gr/files/apt-grnetdev.pub | \
apt-key add -",
]
def _configure(self):
return [
("/etc/apt/sources.list.d/synnefo.wheezy.list", {}, {})
]
@base.run_cmds
def initialize(self):
return [
"apt-get update",
]
class MQ(base.Component):
REQUIRED_PACKAGES = ["rabbitmq-server"]
alias = constants.MQ
def required_components(self):
return [HW, SSH, DNS, APT]
@update_admin
def admin_pre(self):
self.NS.update_ns()
@base.run_cmds
def check(self):
return ["ping -c 1 %s" % self.node.fqdn]
@base.run_cmds
def initialize(self):
u = config.synnefo_user
p = config.synnefo_rabbitmq_passwd
return [
"rabbitmqctl add_user %s %s" % (u, p),
"rabbitmqctl set_permissions %s \".*\" \".*\" \".*\"" % u,
"rabbitmqctl delete_user guest",
"rabbitmqctl set_user_tags %s administrator" % u,
]
class DB(base.Component):
REQUIRED_PACKAGES = ["postgresql"]
alias = constants.DB
def required_components(self):
return [HW, SSH, DNS, APT]
@update_admin
def admin_pre(self):
self.NS.update_ns()
@base.run_cmds
def check(self):
return ["ping -c 1 %s" % self.node.fqdn]
@parse_user_info
@base.run_cmds
def get_user_info_from_db(self):
cmd = """
cat > /tmp/psqlcmd <> %s""" % (opts, f)]
@base.run_cmds
def prepare(self):
f = "/etc/postgresql/*/main/postgresql.conf"
ret = ["""echo "listen_addresses = '*'" >> %s""" % f]
return ret + self.make_db_fast()
@base.run_cmds
def initialize(self):
script = "/tmp/db-init.psql"
cmd = "su - postgres -c \"psql -w -f %s\" " % script
return [cmd]
@base.run_cmds
def restart(self):
return ["/etc/init.d/postgresql restart"]
@base.run_cmds
def destroy_db(self):
return [
"""su - postgres -c ' psql -w -c "drop database snf_apps" '""",
"""su - postgres -c ' psql -w -c "drop database snf_pithos" '"""
]
class VMC(base.Component):
def extra_components(self):
if self.cluster.synnefo:
return [
Image, GTools, GanetiCollectd,
PithosBackend, Archip, ArchipGaneti
]
else:
return [ExtStorage, Archip, ArchipGaneti]
def required_components(self):
return [
HW, SSH, DNS, DDNS, APT, Mount, Ganeti, Network,
] + self.extra_components()
@update_cluster_admin
def admin_post(self):
self.MASTER.add_node(self.node)
class Ganeti(base.Component):
REQUIRED_PACKAGES = [
"qemu-kvm",
"python-bitarray",
"ganeti-htools",
"ganeti-haskell",
"snf-ganeti",
"ganeti2",
"bridge-utils",
"lvm2",
"drbd8-utils",
"ganeti-instance-debootstrap",
]
@update_admin
def admin_pre(self):
self.NS.update_ns()
@base.run_cmds
def check(self):
commands = [
"getent hosts %s | grep -v ^127" % self.node.hostname,
"hostname -f | grep %s" % self.node.fqdn,
]
return commands
def _configure(self):
return [
("/etc/ganeti/file-storage-paths", {}, {}),
]
def _prepare_lvm(self):
ret = []
disk = self.node.extra_disk
if disk:
ret = [
"test -e %s" % disk,
"pvcreate %s" % disk,
"vgcreate %s %s" % (self.cluster.vg, disk)
]
return ret
def _prepare_net_infra(self):
br = config.common_bridge
return [
"brctl addbr {0}; ip link set {0} up".format(br)
]
@base.run_cmds
def prepare(self):
return [
"mkdir -p /srv/ganeti/file-storage/",
"sed -i 's/^127.*$/127.0.0.1 localhost/g' /etc/hosts"
] + self._prepare_net_infra() + self._prepare_lvm()
@base.run_cmds
def restart(self):
return ["/etc/init.d/ganeti restart"]
class Master(base.Component):
@property
def fqdn(self):
return self.cluster
def required_components(self):
return [
HW, SSH, DNS, DDNS, APT, Mount, Ganeti
]
@update_admin
def admin_pre(self):
self.NS.update_ns()
@base.run_cmds
def check(self):
commands = [
"host %s" % self.cluster.fqdn,
]
return commands
@base.run_cmds
def add_qa_rapi_user(self):
cmd = """
echo ganeti-qa qa_example_passwd write >> /var/lib/ganeti/rapi/users
"""
return [cmd]
def _add_rapi_user(self):
user = config.synnefo_user
passwd = config.synnefo_rapi_passwd
x = "%s:Ganeti Remote API:%s" % (user, passwd)
cmd = """
cat >> /var/lib/ganeti/rapi/users < %s" % f
]
@base.run_cmds
def import_service(self):
f = config.jsonfile
return [
"snf-manage service-import --json=%s" % f
]
@base.run_cmds
def set_astakos_default_quota(self):
cmd = "snf-manage resource-modify"
return [
"%s --base-default 2 astakos.pending_app" % cmd,
"%s --project-default 0 astakos.pending_app" % cmd,
]
@base.run_cmds
def set_cyclades_default_quota(self):
cmd = "snf-manage resource-modify"
return [
"%s --base-default 4 cyclades.vm" % cmd,
"%s --base-default 40G cyclades.disk" % cmd,
"%s --base-default 16G cyclades.total_ram" % cmd,
"%s --base-default 8G cyclades.ram" % cmd,
"%s --base-default 32 cyclades.total_cpu" % cmd,
"%s --base-default 16 cyclades.cpu" % cmd,
"%s --base-default 4 cyclades.network.private" % cmd,
"%s --base-default 4 cyclades.floating_ip" % cmd,
"%s --project-default 0 cyclades.vm" % cmd,
"%s --project-default 0 cyclades.disk" % cmd,
"%s --project-default inf cyclades.total_ram" % cmd,
"%s --project-default 0 cyclades.ram" % cmd,
"%s --project-default inf cyclades.total_cpu" % cmd,
"%s --project-default 0 cyclades.cpu" % cmd,
"%s --project-default 0 cyclades.network.private" % cmd,
"%s --project-default 0 cyclades.floating_ip" % cmd,
]
@base.run_cmds
def set_pithos_default_quota(self):
cmd = "snf-manage resource-modify"
return [
"%s --base-default 40G pithos.diskspace" % cmd,
"%s --project-default 0 pithos.diskspace" % cmd,
]
@base.run_cmds
def modify_all_quota(self):
cmd = "snf-manage project-modify --all-base-projects --limit"
return [
"%s pithos.diskspace 40G 40G" % cmd,
"%s astakos.pending_app 2 2" % cmd,
"%s cyclades.vm 4 4" % cmd,
"%s cyclades.disk 40G 40G" % cmd,
"%s cyclades.total_ram 16G 16G" % cmd,
"%s cyclades.ram 8G 8G" % cmd,
"%s cyclades.total_cpu 32 32" % cmd,
"%s cyclades.cpu 16 16" % cmd,
"%s cyclades.network.private 4 4" % cmd,
"%s cyclades.floating_ip 4 4" % cmd,
]
@parse_service_info
@base.run_cmds
def get_services(self):
return [
"snf-manage component-list -o id,name,token"
]
def _configure(self):
r1 = {
"ACCOUNTS": self.ctx.astakos.fqdn,
"domain": self.node.domain,
"CYCLADES": self.ctx.cyclades.fqdn,
"PITHOS": self.ctx.pithos.fqdn,
}
return [
("/etc/synnefo/astakos.conf", r1, {})
]
@base.run_cmds
def initialize(self):
return [
"snf-manage syncdb --noinput",
"snf-manage migrate im --delete-ghost-migrations",
"snf-manage migrate quotaholder_app",
"snf-manage migrate oa2",
"snf-manage loaddata groups",
] + self._astakos_oa2() + self._astakos_register_components()
def _astakos_oa2(self):
secret = config.oa2_secret
view = "https://%s/pithos/ui/view" % self.ctx.pithos.fqdn
cmd = "snf-manage oauth2-client-add pithos-view \
--secret=%s --is-trusted --url %s || true" % (secret, view)
return [cmd]
def _astakos_register_components(self):
# base urls
cbu = "https://%s/cyclades" % self.ctx.cyclades.fqdn
pbu = "https://%s/pithos" % self.ctx.pithos.fqdn
abu = "https://%s/astakos" % self.ctx.astakos.fqdn
cmsurl = "https://%s/home" % self.ctx.cms.fqdn
cmd = "snf-manage component-add"
h = "%s home --base-url %s --ui-url %s" % (cmd, cmsurl, cmsurl)
c = "%s cyclades --base-url %s --ui-url %s/ui" % (cmd, cbu, cbu)
p = "%s pithos --base-url %s --ui-url %s/ui" % (cmd, pbu, pbu)
a = "%s astakos --base-url %s --ui-url %s/ui" % (cmd, abu, abu)
return [h, c, p, a]
@base.run_cmds
def add_user(self):
info = (
config.user_passwd,
config.user_email,
config.user_name,
config.user_lastname,
)
cmd = "snf-manage user-add --password %s %s %s %s" % info
return [cmd]
@update_admin
@base.run_cmds
def activate_user(self):
self.DB.get_user_info_from_db()
user_id = context.user_id
return [
"snf-manage user-modify --verify %s" % user_id,
"snf-manage user-modify --accept %s" % user_id,
]
@update_admin
@export_and_import_service
def admin_post(self):
self.set_astakos_default_quota()
class CMS(base.Component):
REQUIRED_PACKAGES = [
"snf-cloudcms"
]
alias = constants.CMS
def required_components(self):
return [HW, SSH, DNS, APT, Apache, Gunicorn, Common, WEB]
@update_admin
def admin_pre(self):
self.NS.update_ns()
self.DB.allow_db_access()
self.DB.restart()
@property
def conflicts(self):
return [Astakos, Pithos, Cyclades]
def _configure(self):
r1 = {
"ACCOUNTS": self.ctx.astakos.fqdn
}
r2 = {
"DOMAIN": self.node.domain
}
return [
("/etc/synnefo/cms.conf", r1, {}),
("/tmp/sites.json", r2, {}),
("/tmp/page.json", {}, {}),
]
@base.run_cmds
def initialize(self):
return [
"snf-manage syncdb",
"snf-manage migrate --delete-ghost-migrations",
"snf-manage loaddata /tmp/sites.json",
"snf-manage loaddata /tmp/page.json",
"snf-manage createsuperuser --username=admin \
--email=admin@%s --noinput" % self.node.domain,
]
@base.run_cmds
def restart(self):
return ["/etc/init.d/gunicorn restart"]
class Mount(base.Component):
REQUIRED_PACKAGES = [
"nfs-common"
]
@update_admin
def admin_pre(self):
self.NFS.update_exports()
self.NFS.restart()
@property
def conflicts(self):
return [NFS]
@base.run_cmds
def prepare(self):
ret = []
for d in [config.pithos_dir, config.image_dir, config.archip_dir]:
ret.append("mkdir -p %s" % d)
cmd = """
cat >> /etc/fstab <> /etc/exports < %s" % f
]
def _configure(self):
r1 = {
"ACCOUNTS": self.ctx.astakos.fqdn,
"PITHOS": self.ctx.pithos.fqdn,
"db_node": self.ctx.db.ip,
"synnefo_user": config.synnefo_user,
"synnefo_db_passwd": config.synnefo_db_passwd,
"pithos_dir": config.pithos_dir,
"PITHOS_SERVICE_TOKEN": context.service_token,
"oa2_secret": config.oa2_secret,
}
r2 = {
"ACCOUNTS": self.ctx.astakos.fqdn,
"PITHOS_UI_CLOUDBAR_ACTIVE_SERVICE": context.service_id,
}
return [
("/etc/synnefo/pithos.conf", r1, {}),
("/etc/synnefo/webclient.conf", r2, {}),
]
@base.run_cmds
def initialize(self):
return ["pithos-migrate stamp head"]
@base.run_cmds
def restart(self):
return [
"/etc/init.d/gunicorn restart",
]
@update_admin
@export_and_import_service
def admin_post(self):
self.ASTAKOS.set_pithos_default_quota()
class PithosBackend(base.Component):
REQUIRED_PACKAGES = [
"snf-pithos-backend",
]
def _configure(self):
r1 = {
"db_node": self.ctx.db.ip,
"synnefo_user": config.synnefo_user,
"synnefo_db_passwd": config.synnefo_db_passwd,
"pithos_dir": config.pithos_dir,
}
return [
("/etc/synnefo/backend.conf", r1, {}),
]
class Cyclades(base.Component):
REQUIRED_PACKAGES = [
"memcached",
"python-memcache",
"kamaki",
"snf-cyclades-app",
"python-django-south",
]
alias = constants.CYCLADES
service = constants.CYCLADES
def required_components(self):
return [
HW, SSH, DNS, APT,
Apache, Gunicorn, Common, WEB, VNC, PithosBackend, Archip
]
@update_admin
def admin_pre(self):
self.NS.update_ns()
self.ASTAKOS.get_services()
self.DB.allow_db_access()
self.DB.restart()
@property
def conflicts(self):
return [CMS]
def _add_network(self):
subnet = config.synnefo_public_network_subnet
gw = config.synnefo_public_network_gateway
ntype = config.synnefo_public_network_type
link = config.common_bridge
cmd = """
snf-manage network-create --subnet={0} --gateway={1} --public \
--dhcp=True --flavor={2} --mode=bridged --link={3} --name=Internet \
--floating-ip-pool=True
""".format(subnet, gw, ntype, link)
return [cmd]
@base.check_if_testing
def _add_network6(self):
subnet = "babe::/64"
gw = "babe::1"
ntype = config.synnefo_public_network_type
link = config.common_bridge
cmd = """
snf-manage network-create --subnet6={0} \
--gateway6={1} --public --dhcp=True --flavor={2} --mode=bridged \
--link={3} --name=IPv6PublicNetwork
""".format(subnet, gw, ntype, link)
return [cmd]
@base.run_cmds
def export_service(self):
f = config.jsonfile
return [
"snf-manage service-export-cyclades > %s" % f
]
@parse_backend_info
@base.run_cmds
def list_backends(self):
return [
"snf-manage backend-list"
]
@base.run_cmds
def add_backend(self):
cluster = self.ctx.admin_cluster.fqdn
user = config.synnefo_user
passwd = config.synnefo_rapi_passwd
return [
"snf-manage backend-add --clustername=%s --user=%s --pass=%s" %
(cluster, user, passwd)
]
@base.run_cmds
def undrain_backend(self):
backend_id = context.backend_id
return [
"snf-manage backend-modify --drained=False %s" % str(backend_id)
]
@base.run_cmds
def prepare(self):
return ["sed -i 's/false/true/' /etc/default/snf-dispatcher"]
def _configure(self):
r1 = {
"ACCOUNTS": self.ctx.astakos.fqdn,
"CYCLADES": self.ctx.cyclades.fqdn,
"mq_node": self.ctx.mq.ip,
"db_node": self.ctx.db.ip,
"synnefo_user": config.synnefo_user,
"synnefo_db_passwd": config.synnefo_db_passwd,
"synnefo_rabbitmq_passwd": config.synnefo_rabbitmq_passwd,
"pithos_dir": config.pithos_dir,
"common_bridge": config.common_bridge,
"HOST": self.ctx.cyclades.ip,
"domain": self.node.domain,
"CYCLADES_SERVICE_TOKEN": context.service_token,
"STATS": self.ctx.stats.fqdn,
"SYNNEFO_VNC_PASSWD": config.synnefo_vnc_passwd,
"CYCLADES_NODE_IP": self.ctx.cyclades.ip
}
return [
("/etc/synnefo/cyclades.conf", r1, {})
]
@base.run_cmds
def initialize(self):
cpu = config.flavor_cpu
ram = config.flavor_ram
disk = config.flavor_disk
storage = config.flavor_storage
return [
"snf-manage syncdb",
"snf-manage migrate --delete-ghost-migrations",
"snf-manage pool-create --type=mac-prefix \
--base=aa:00:0 --size=65536",
"snf-manage pool-create --type=bridge --base=prv --size=20",
"snf-manage flavor-create %s %s %s %s" % (cpu, ram, disk, storage),
] + self._add_network() + self._add_network6()
@base.run_cmds
def restart(self):
return [
"/etc/init.d/gunicorn restart",
"/etc/init.d/snf-dispatcher restart",
]
@update_admin
@export_and_import_service
def admin_post(self):
self.ASTAKOS.set_cyclades_default_quota()
class VNC(base.Component):
REQUIRED_PACKAGES = [
"snf-vncauthproxy"
]
@base.run_cmds
def prepare(self):
return ["mkdir -p /var/lib/vncauthproxy"]
def _configure(self):
return [
("/var/lib/vncauthproxy/users", {}, {})
]
@base.run_cmds
def initialize(self):
# user = config.synnefo_user
# passwd = config.synnefo_vnc_passwd
# TODO: run vncauthproxy-passwd
return []
@base.run_cmds
def restart(self):
return [
"/etc/init.d/vncauthproxy restart"
]
class Kamaki(base.Component):
REQUIRED_PACKAGES = [
"python-progress",
"kamaki",
]
@update_admin
def admin_pre(self):
self.ASTAKOS.add_user()
self.ASTAKOS.activate_user()
self.DB.get_user_info_from_db()
@base.run_cmds
def initialize(self):
url = "https://%s/astakos/identity/v2.0" % self.ctx.astakos.fqdn
token = context.user_auth_token
return [
"kamaki config set cloud.default.url %s" % url,
"kamaki config set cloud.default.token %s" % token,
"kamaki container create images",
]
def _fetch_image(self):
url = config.debian_base_url
image = "debian_base.diskdump"
return [
"test -e /tmp/%s || wget %s -O /tmp/%s" % (image, url, image)
]
def _upload_image(self):
image = "debian_base.diskdump"
return [
"kamaki file upload --container images /tmp/%s %s" % (image, image)
]
def _register_image(self):
image = "debian_base.diskdump"
image_location = "/images/%s" % image
cmd = """
kamaki image register --name "Debian Base" --location {0} \
--public --disk-format=diskdump \
--property OSFAMILY=linux --property ROOT_PARTITION=1 \
--property description="Debian Squeeze Base System" \
--property size=450M --property kernel=2.6.32 \
--property GUI="No GUI" --property sortorder=1 \
--property USERS=root --property OS=debian
""".format(image_location)
return [
"sleep 5",
cmd
]
@base.run_cmds
def test(self):
return self._fetch_image() + self._upload_image() + \
self._register_image()
class Burnin(base.Component):
REQUIRED_PACKAGES = [
"kamaki",
"snf-tools",
]
class Collectd(base.Component):
REQUIRED_PACKAGES = [
"collectd",
]
def _configure(self):
return [
("/etc/collectd/collectd.conf", {}, {}),
]
@base.run_cmds
def restart(self):
return [
"/etc/init.d/collectd restart",
]
class Stats(base.Component):
REQUIRED_PACKAGES = [
"snf-stats-app",
]
alias = constants.STATS
def required_components(self):
return [
HW, SSH, DNS, APT,
Apache, Gunicorn, Common, WEB, Collectd
]
@update_admin
def admin_pre(self):
self.NS.update_ns()
@base.run_cmds
def prepare(self):
return [
"mkdir -p /var/cache/snf-stats-app/",
"chown www-data:www-data /var/cache/snf-stats-app/",
]
def _configure(self):
r1 = {
"STATS": self.ctx.stats.fqdn,
}
return [
("/etc/synnefo/stats.conf", r1, {}),
("/etc/collectd/synnefo-stats.conf", r1, {}),
]
@base.run_cmds
def restart(self):
return [
"/etc/init.d/gunicorn restart",
"/etc/init.d/apache2 restart",
]
class GanetiCollectd(base.Component):
def _configure(self):
r1 = {
"STATS": self.ctx.stats.fqdn,
}
return [
("/etc/collectd/passwd", {}, {}),
("/etc/collectd/synnefo-ganeti.conf", r1, {}),
]
class Archip(base.Component):
REQUIRED_PACKAGES = [
"librados2",
"archipelago",
"archipelago-dbg",
"archipelago-modules-dkms",
"archipelago-modules-source",
"archipelago-rados",
"archipelago-rados-dbg",
"libxseg0",
"libxseg0-dbg",
"python-archipelago",
"python-xseg",
]
@base.run_cmds
def prepare(self):
return ["mkdir -p /etc/archipelago"]
def _configure(self):
r1 = {"HOST": self.node.fqdn}
r2 = {"SEGMENT_SIZE": config.segment_size}
return [
("/etc/gunicorn.d/synnefo-archip", r1,
{"remote": "/etc/gunicorn.d/synnefo"}),
("/etc/archipelago/pithos.conf.py", {}, {}),
("/etc/archipelago/archipelago.conf", r2, {})
]
@base.run_cmds
def restart(self):
return [
"/etc/init.d/gunicorn restart",
"archipelago restart"
]
class ArchipGaneti(base.Component):
REQUIRED_PACKAGES = [
"archipelago-ganeti",
]
class ExtStorage(base.Component):
def prepare(self):
return ["mkdir -p /usr/local/lib/ganeti/"]
def initialize(self):
url = "http://code.grnet.gr/git/extstorage"
extdir = "/usr/local/lib/ganeti/extstorage"
return [
"git clone %s %s" % (url, extdir)
]
class Client(base.Component):
REQUIRED_PACKAGES = [
"iceweasel"
]
alias = constants.CLIENT
def required_components(self):
return [HW, SSH, DNS, APT, Kamaki, Burnin]
class GanetiDev(base.Component):
REQUIRED_PACKAGES = [
"automake",
"bridge-utils",
"cabal-install",
"fakeroot",
"fping",
"ghc",
"ghc-haddock",
"git",
"graphviz",
"hlint",
"hscolour",
"iproute",
"iputils-arping",
"libcurl4-openssl-dev",
"libghc-attoparsec-dev",
"libghc-crypto-dev",
"libghc-curl-dev",
"libghc-haddock-dev",
"libghc-hinotify-dev",
"libghc-hslogger-dev",
"libghc-hunit-dev",
"libghc-json-dev",
"libghc-network-dev",
"libghc-parallel-dev",
"libghc-quickcheck2-dev",
"libghc-regex-pcre-dev",
"libghc-snap-server-dev",
"libghc-temporary-dev",
"libghc-test-framework-dev",
"libghc-test-framework-hunit-dev",
"libghc-test-framework-quickcheck2-dev",
"libghc-text-dev",
"libghc-utf8-string-dev",
"libghc-vector-dev",
"lvm2",
"make",
"ndisc6",
"openssl",
"pandoc",
"pep8",
"pylint",
"python",
"python-bitarray",
"python-coverage",
"python-epydoc",
"python-ipaddr",
"python-openssl",
"python-pip",
"python-pycurl",
"python-pyinotify",
"python-pyparsing",
"python-setuptools",
"python-simplejson",
"python-sphinx",
"python-yaml",
"qemu-kvm",
"socat",
"ssh",
"vim"
]
CABAL = [
"hinotify==0.3.2",
"base64-bytestring",
"lifted-base==0.2.0.3",
"lens==3.10",
]
def _cabal(self):
ret = ["cabal update"]
for p in self.CABAL:
ret.append("cabal install %s" % p)
return ret
@base.run_cmds
def prepare(self):
return self._cabal() + [
"git clone git://git.ganeti.org/ganeti.git"
]
def _configure(self):
sample_nodes = []
for node in self.ctx.nodes:
n = config.get_info(node=node)
sample_nodes.append({
"primary": n.fqdn,
"secondary": n.ip,
})
repl = {
"CLUSTER_NAME": self.cluster.name,
"VG": self.cluster.vg,
"CLUSTER_NETDEV": self.cluster.netdev,
"NODES": simplejson.dumps({"nodes": sample_nodes}),
"DOMAIN": self.cluster.domain
}
return [
("/root/qa-sample.json", repl, {}),
]
@base.run_cmds
def initialize(self):
return [
"cd ganeti; ./autogen.sh",
"cd ganeti; ./configure --localstatedir=/var --sysconfdir=/etc",
]
@base.run_cmds
def test(self):
ret = []
for n in self.ctx.nodes:
info = config.get_info(node=n)
ret.append("ssh %s date" % info.name)
ret.append("ssh %s date" % info.ip)
ret.append("ssh %s date" % info.fqdn)
return ret
@update_admin
@update_cluster_admin
def admin_post(self):
self.MASTER.add_qa_rapi_user()
self.NS.add_qa_instances()
class Router(base.Component):
REQUIRED_PACKAGES = [
"iptables"
]