components.py 55.5 KB
Newer Older
Vangelis Koukis's avatar
Vangelis Koukis committed
1
# Copyright (C) 2010-2014 GRNET S.A.
2
#
Vangelis Koukis's avatar
Vangelis Koukis committed
3 4 5 6
# 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.
7
#
Vangelis Koukis's avatar
Vangelis Koukis committed
8 9 10 11
# 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.
12
#
Vangelis Koukis's avatar
Vangelis Koukis committed
13 14
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

16
import re
17
import datetime
18
import simplejson
19
import copy
20
import os
21 22 23 24
from snfdeploy import base
from snfdeploy import config
from snfdeploy import constants
from snfdeploy import context
25
from snfdeploy.lib import FQDN, evaluate
26 27


28 29 30
_USER_INFO_RE = lambda x: \
    re.compile(r"(\d+)[ |]*(\S+)[ |]*(\S+)[ |]*%s.*" % x, re.M)
_USER_INFO = ["user_id", "user_auth_token", "user_uuid"]
31

32 33
_SERVICE_INFO_RE = lambda x: re.compile(r"(\d+)[ ]*%s[ ]*(\S+)" % x, re.M)
_SERVICE_INFO = ["service_id", "service_token"]
34

35 36
_BACKEND_INFO_RE = lambda x: re.compile(r"(\d+)[ ]*%s.*" % x, re.M)
_BACKEND_INFO = ["backend_id"]
37

38 39
_VOLUME_INFO_RE = lambda x: re.compile(r"(\d+)[ ]*%s.*" % x, re.M)
_VOLUME_INFO = ["volume_type_id"]
40 41


42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
# Helper decorator that wraps get_* methods of certain Components
# Those methods take one argument; the identity (mail, service, backend)
# to look for. It parses the output of those methods and updates the
# context's keys with the matched groups.
def parse(regex, keys):
    def wrap(f):
        def wrapped_f(cl, what):
            result = f(cl, what)
            match = regex(what).search(result)
            if config.dry_run:
                evaluate(context, **dict(zip(keys, ["dummy"] * len(keys))))
            elif match:
                evaluate(context, **dict(zip(keys, match.groups())))
            else:
                raise BaseException("Cannot parse info for %s" % what)
        return wrapped_f
    return wrap
59 60


61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
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)
83
        cl.ADMIN = Admin(node=ctx.admin.node, ctx=ctx)
84
        cl.CLIENT = Client(node=ctx.client.node, ctx=ctx)
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
        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


122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
def cert_override(fn):
    """ Create all needed entries for cert_override.txt file

    Append them in a tmp file and upload them to client node

    """
    def wrapper(*args, **kwargs):
        cl = args[0]
        f = "/tmp/" + constants.CERT_OVERRIDE + "_" + cl.service
        for domain in [cl.node.domain, cl.node.cname, cl.node.ip]:
            cmd = """
python /root/firefox_cert_override.py {0} {1}:443 >> {2}
""".format(constants.CERT_PATH, domain, f)
            cl.run(cmd)
        cl.get(f, f + ".local")
        cl.CLIENT.put(f + ".local", f)
        return fn(*args, **kwargs)
    return wrapper


142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
# ########################## 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.
170

171 172
class HW(base.Component):
    @base.run_cmds
173 174
    def test(self):
        return [
175
            "ping -c 1 %s" % self.node.ip,
176 177 178 179
            "ping -c 1 www.google.com",
            "apt-get update",
            ]

180

181 182
class SSH(base.Component):
    @base.run_cmds
183 184 185 186 187 188 189
    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",
            ]

190
    def _configure(self):
191 192 193
        files = [
            "authorized_keys", "id_dsa", "id_dsa.pub", "id_rsa", "id_rsa.pub"
            ]
194
        ssh = [("/root/.ssh/%s" % f, {}, {"mode": 0600}) for f in files]
195 196
        return ssh

197
    @base.run_cmds
198 199 200
    def initialize(self):
        f = "/root/.ssh/authorized_keys"
        return [
201
            "test -e {0}.bak && cat {0}.bak >> {0} || true".format(f)
202 203
            ]

204
    @base.run_cmds
205
    def test(self):
206 207
        return ["ssh %s date" % self.node.ip]

208

209 210 211 212
class DNS(base.Component):
    @update_admin
    def admin_pre(self):
        self.NS.update_ns()
213

214
    @base.run_cmds
215 216 217 218
    def prepare(self):
        return [
            "chattr -i /etc/resolv.conf",
            "sed -i 's/^127.*$/127.0.0.1 localhost/g' /etc/hosts",
219 220
            "echo %s > /etc/hostname" % self.node.hostname,
            "hostname %s" % self.node.hostname
221 222
            ]

223
    def _configure(self):
224 225
        r1 = {
            "date": str(datetime.datetime.today()),
226 227
            "domain": self.node.domain,
            "ns_node_ip": self.ctx.ns.ip,
228 229 230 231 232 233
            }
        resolv = [
            ("/etc/resolv.conf", r1, {})
            ]
        return resolv

234
    @base.run_cmds
235 236 237 238
    def initialize(self):
        return ["chattr +i /etc/resolv.conf"]


239
class DDNS(base.Component):
240 241 242 243
    REQUIRED_PACKAGES = [
        "dnsutils",
        ]

244
    @base.run_cmds
245 246 247 248 249
    def prepare(self):
        return [
            "mkdir -p /root/ddns/"
            ]

250
    def _configure(self):
251
        return [
252
            ("/root/ddns/" + k, {}, {}) for k in config.ddns_keys
253 254 255
            ]


256
class NS(base.Component):
257 258 259 260
    REQUIRED_PACKAGES = [
        "bind9",
        ]

261 262 263 264 265 266
    alias = constants.NS

    def required_components(self):
        return [HW, SSH, DDNS]

    def _nsupdate(self, cmd):
267 268 269 270 271 272
        ret = """
nsupdate -k {0} > /dev/null <<EOF || true
server {1}
{2}
send
EOF
273
""".format(config.ddns_private_key, self.ctx.ns.ip, cmd)
274 275
        return ret

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
    @base.run_cmds
    def update_ns(self, info=None):
        if not info:
            info = self.ctx.admin_fqdn
        return [
            self._nsupdate("update add %s" % info.arecord),
            self._nsupdate("update add %s" % info.ptrrecord),
            self._nsupdate("update add %s" % info.cnamerecord),
            ]

    def add_qa_instances(self):
        instances = [
            ("xen-test-inst1", "1.2.3.4"),
            ("xen-test-inst2", "1.2.3.5"),
            ("xen-test-inst3", "1.2.3.6"),
            ("xen-test-inst4", "1.2.3.7"),
            ]
        for name, ip in instances:
            info = {
                "name": name,
                "ip": ip,
                "domain": self.node.domain
                }
            node_info = FQDN(**info)
            self.update_ns(node_info)

    @base.run_cmds
303 304 305 306 307 308 309 310
    def prepare(self):
        return [
            "mkdir -p /etc/bind/zones",
            "chmod g+w /etc/bind/zones",
            "mkdir -p /etc/bind/rev",
            "chmod g+w /etc/bind/rev",
            ]

311 312 313
    def _configure(self):
        d = self.node.domain
        ip = self.node.ip
314 315 316 317 318 319 320 321 322 323 324
        return [
            ("/etc/bind/named.conf.local", {"domain": d}, {}),
            ("/etc/bind/zones/example.com",
             {"domain": d, "ns_node_ip": ip},
             {"remote": "/etc/bind/zones/%s" % d}),
            ("/etc/bind/zones/vm.example.com",
             {"domain": d, "ns_node_ip": ip},
             {"remote": "/etc/bind/zones/vm.%s" % d}),
            ("/etc/bind/rev/synnefo.in-addr.arpa.zone", {"domain": d}, {}),
            ("/etc/bind/rev/synnefo.ip6.arpa.zone", {"domain": d}, {}),
            ("/etc/bind/named.conf.options",
325
             {"node_ips": ";".join(self.ctx.all_ips)}, {}),
326 327 328
            ("/root/ddns/ddns.key", {}, {"remote": "/etc/bind/ddns.key"}),
            ]

329
    @base.run_cmds
330 331 332 333
    def restart(self):
        return ["/etc/init.d/bind9 restart"]


334
class APT(base.Component):
335 336 337
    """ Setup apt repos and check fqdns """
    REQUIRED_PACKAGES = ["curl"]

338
    @base.run_cmds
339 340 341
    def prepare(self):
        return [
            "echo 'APT::Install-Suggests \"false\";' >> /etc/apt/apt.conf",
342 343
            "curl -k https://dev.grnet.gr/files/apt-grnetdev.pub | \
                apt-key add -",
344 345
            ]

346
    def _configure(self):
347 348 349 350
        return [
            ("/etc/apt/sources.list.d/synnefo.wheezy.list", {}, {})
            ]

351
    @base.run_cmds
352 353 354 355 356 357
    def initialize(self):
        return [
            "apt-get update",
            ]


358
class MQ(base.Component):
359 360
    REQUIRED_PACKAGES = ["rabbitmq-server"]

361 362 363 364 365 366 367 368 369 370
    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
371
    def check(self):
372
        return ["ping -c 1 %s" % self.node.cname]
373

374
    @base.run_cmds
375
    def initialize(self):
376 377
        u = config.synnefo_user
        p = config.synnefo_rabbitmq_passwd
378 379 380 381 382 383 384 385
        return [
            "rabbitmqctl add_user %s %s" % (u, p),
            "rabbitmqctl set_permissions %s \".*\" \".*\" \".*\"" % u,
            "rabbitmqctl delete_user guest",
            "rabbitmqctl set_user_tags %s administrator" % u,
            ]


386
class DB(base.Component):
387 388
    REQUIRED_PACKAGES = ["postgresql"]

389 390 391 392 393 394 395 396 397 398
    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
399
    def check(self):
400
        return ["ping -c 1 %s" % self.node.cname]
401

402
    @parse(_USER_INFO_RE, _USER_INFO)
403
    @base.run_cmds
404
    def get_user_info_from_db(self, user_email):
405 406 407 408 409 410 411
        cmd = """
cat > /tmp/psqlcmd <<EOF
select id, auth_token, uuid, email from auth_user, im_astakosuser \
where auth_user.id = im_astakosuser.user_ptr_id and auth_user.email = '{0}';
EOF

su - postgres -c  "psql -w -d snf_apps -f /tmp/psqlcmd"
412
""".format(user_email)
413 414 415

        return [cmd]

416 417 418 419 420
    @base.run_cmds
    def allow_db_access(self):
        user = "all"
        method = "md5"
        ip = self.ctx.admin_node.ip
421 422
        f = "/etc/postgresql/*/main/pg_hba.conf"
        cmd1 = "echo host all %s %s/32 %s >> %s" % \
423
            (user, ip, method, f)
424
        cmd2 = "sed -i 's/\(host.*127.0.0.1.*\)md5/\\1trust/' %s" % f
425
        return [cmd1, cmd2]
426

427 428 429
    def _configure(self):
        u = config.synnefo_user
        p = config.synnefo_db_passwd
430 431 432 433 434
        replace = {"synnefo_user": u, "synnefo_db_passwd": p}
        return [
            ("/tmp/db-init.psql", replace, {}),
            ]

435
    @base.check_if_testing
436 437 438 439 440
    def make_db_fast(self):
        f = "/etc/postgresql/*/main/postgresql.conf"
        opts = "fsync=off\nsynchronous_commit=off\nfull_page_writes=off\n"
        return ["""echo -e "%s" >> %s""" % (opts, f)]

441
    @base.run_cmds
442 443
    def prepare(self):
        f = "/etc/postgresql/*/main/postgresql.conf"
444 445
        ret = ["""echo "listen_addresses = '*'" >> %s""" % f]
        return ret + self.make_db_fast()
446

447
    @base.run_cmds
448 449 450 451 452
    def initialize(self):
        script = "/tmp/db-init.psql"
        cmd = "su - postgres -c \"psql -w -f %s\" " % script
        return [cmd]

453
    @base.run_cmds
454 455 456
    def restart(self):
        return ["/etc/init.d/postgresql restart"]

457
    @base.run_cmds
458 459 460 461 462 463 464
    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" '"""
            ]


465 466 467 468
class VMC(base.Component):

    def extra_components(self):
        if self.cluster.synnefo:
469 470
            return [
                Image, GTools, GanetiCollectd,
471
                PithosBackend, ExtStorage, Archip, ArchipGaneti
472
                ]
473
        else:
474
            return [ExtStorage, Archip, ArchipGaneti]
475

476 477
    def required_components(self):
        return [
478
            HW, SSH, DNS, DDNS, APT, Mount, LVM, DRBD, Ganeti, Network,
479 480 481 482 483
            ] + self.extra_components()

    @update_cluster_admin
    def admin_post(self):
        self.MASTER.add_node(self.node)
484 485
        self.MASTER.enable_lvm()
        self.MASTER.enable_drbd()
486 487


488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
class LVM(base.Component):
    REQUIRED_PACKAGES = [
        "lvm2",
        ]

    @base.run_cmds
    def initialize(self):
        extra_disk_dev = self.node.extra_disk
        extra_disk_file = "/disk"
        # If extra disk found use it
        # else create a raw file and losetup it
        cmd = """
if [ -b "{0}" ]; then
  pvcreate {0} && vgcreate {2} {0}
else
  truncate -s {3} {1}
  loop_dev=$(losetup -f --show {1})
  pvcreate $loop_dev
  vgcreate {2} $loop_dev
fi
""".format(extra_disk_dev, extra_disk_file,
           self.cluster.vg, self.cluster.vg_size)

        return [cmd]


class DRBD(base.Component):
    REQUIRED_PACKAGES = [
        "drbd8-utils",
        ]

    def _configure(self):
        return [
            ("/etc/modprobe.d/drbd.conf", {}, {}),
            ]

    def prepare(self):
        return [
            "echo drbd >> /etc/modules",
            ]

    @base.run_cmds
    def initialize(self):
        return [
            "modprobe -rv drbd || true",
            "modprobe -v drbd",
            ]


537
class Ganeti(base.Component):
538 539 540
    REQUIRED_PACKAGES = [
        "qemu-kvm",
        "python-bitarray",
541
        "bridge-utils",
542 543
        "snf-ganeti",
        "ganeti2",
544
        "ganeti-instance-debootstrap"
545 546
        ]

547 548 549 550 551
    @update_admin
    def admin_pre(self):
        self.NS.update_ns()

    @base.run_cmds
552 553
    def check(self):
        commands = [
554 555
            "getent hosts %s | grep -v ^127" % self.node.hostname,
            "hostname -f | grep %s" % self.node.fqdn,
556 557 558
            ]
        return commands

559
    def _configure(self):
560 561 562
        r = {
            "SHARED_GANETI_DIR": config.ganeti_dir,
            }
563
        return [
564
            ("/etc/ganeti/file-storage-paths", r, {}),
565
            ("/etc/default/ganeti-instance-debootstrap", {}, {}),
566 567
            ]

568 569
    def _prepare_net_infra(self):
        br = config.common_bridge
570 571 572 573
        return [
            "brctl addbr {0}; ip link set {0} up".format(br)
            ]

574
    @base.run_cmds
575 576
    def prepare(self):
        return [
577 578
            "mkdir -p %s/file-storage/" % config.ganeti_dir,
            "mkdir -p %s/shared-file-storage/" % config.ganeti_dir,
579
            "sed -i 's/^127.*$/127.0.0.1 localhost/g' /etc/hosts",
580
            ] + self._prepare_net_infra()
581

582
    @base.run_cmds
583
    def restart(self):
584
        return ["/etc/init.d/ganeti restart"]
585 586


587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
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
610 611 612 613
    def add_qa_rapi_user(self):
        cmd = """
echo ganeti-qa qa_example_passwd write >> /var/lib/ganeti/rapi/users
"""
614
        return [cmd]
615

616 617 618
    def _add_rapi_user(self):
        user = config.synnefo_user
        passwd = config.synnefo_rapi_passwd
619
        x = "%s:Ganeti Remote API:%s" % (user, passwd)
620

621 622 623 624
        cmd = """
cat >> /var/lib/ganeti/rapi/users <<EOF
%s {HA1}$(echo -n %s | openssl md5 | sed 's/^.* //') write
EOF
625
""" % (user, x)
626

627
        return [cmd]
628

629 630
    @base.run_cmds
    def add_node(self, info):
631 632 633 634 635 636 637 638 639 640 641 642 643
        add = """
gnt-node list {0} || gnt-node add --no-ssh-key-check {0}
""".format(info.fqdn)

        mod_vm = """
gnt-node modify --vm-capable=yes {0}
""".format(info.fqdn)

        mod_master = """
gnt-node modify --master-capable=yes {0}
""".format(info.fqdn)

        return [add, mod_vm, mod_master]
644

645 646
    @base.run_cmds
    def enable_lvm(self):
647
        vg = self.cluster.vg
648
        return [
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
            # This is needed because MIN_VG_SIZE is constant and set to 20G
            # and cluster modify --vg-name may result to:
            # volume group 'ganeti' too small
            # But this check is made only ff a vm-capable node is found
            "gnt-cluster modify --enabled-disk-templates file,ext,plain \
                                --vg-name=%s" % vg,
            "gnt-cluster modify --ipolicy-disk-template file,ext,plain",
            ]

    @base.run_cmds
    def enable_drbd(self):
        vg = self.cluster.vg
        return [
            "gnt-cluster modify --enabled-disk-templates file,ext,plain,drbd \
                                --drbd-usermode-helper=/bin/true",
            "gnt-cluster modify --ipolicy-disk-template file,ext,plain,drbd",
665 666 667 668
            "gnt-cluster modify --disk-parameters=drbd:metavg=%s" % vg,
            "gnt-group modify --disk-parameters=drbd:metavg=%s default" % vg,
            ]

669
    @base.run_cmds
670
    def initialize(self):
671 672
        std = "cpu-count=1,disk-count=1,disk-size=1024"
        std += ",memory-size=128,nic-count=1,spindle-use=1"
673

674 675 676 677 678
        bound_min = "cpu-count=1,disk-count=1,disk-size=512"
        bound_min += ",memory-size=128,nic-count=0,spindle-use=1"

        bound_max = "cpu-count=8,disk-count=16,disk-size=1048576"
        bound_max += ",memory-size=32768,nic-count=8,spindle-use=12"
679 680

        init = """
681 682 683 684 685 686 687 688
gnt-cluster init --enabled-hypervisors=kvm \
    --nic-parameters link={0},mode=bridged \
    --master-netdev {1} \
    --default-iallocator hail \
    --hypervisor-parameters kvm:kernel_path=,vnc_bind_address=0.0.0.0 \
    --no-ssh-init --no-etc-hosts \
    --ipolicy-std-specs {2} \
    --ipolicy-bounds-specs min:{3}/max:{4} \
689
    --enabled-disk-templates file,ext \
690 691 692 693
    {5}
        """.format(config.common_bridge, self.cluster.netdev,
                   std, bound_min, bound_max, self.cluster.fqdn)

694 695 696
        modify = "gnt-node modify --vm-capable=no %s" % self.node.fqdn

        return [init, modify] + self._add_rapi_user()
697 698

    @base.run_cmds
699 700 701
    def restart(self):
        return ["/etc/init.d/ganeti restart"]

702 703 704 705 706 707
    @update_admin
    @update_cluster_admin
    def admin_post(self):
        if self.cluster.synnefo:
            self.CYCLADES._debug("Adding backend: %s" % self.cluster.fqdn)
            self.CYCLADES.add_backend()
708
            self.CYCLADES.list_backends(self.cluster.fqdn)
709 710
            self.CYCLADES.undrain_backend()

711

712
class Image(base.Component):
713 714 715 716
    REQUIRED_PACKAGES = [
        "snf-image",
        ]

717
    @base.run_cmds
718
    def check(self):
719
        return ["mkdir -p %s" % config.images_dir]
720

721
    @base.run_cmds
722
    def prepare(self):
723
        url = config.debian_base_url
724
        d = config.images_dir
725 726
        image = "debian_base.diskdump"
        return [
727 728
            "test -e %s/%s || wget %s -O %s/%s" % (d, image, url, d, image),
            "mv /etc/default/snf-image /etc/default/snf-image.orig",
729 730
            ]

731
    def _configure(self):
732 733
        tmpl = "/etc/default/snf-image"
        replace = {
734 735
            "synnefo_user": config.synnefo_user,
            "synnefo_db_passwd": config.synnefo_db_passwd,
736
            "db_node": self.ctx.db.cname,
737
            "image_dir": config.images_dir,
738 739 740
            }
        return [(tmpl, replace, {})]

741
    @base.run_cmds
742
    def initialize(self):
743 744 745
        # This is done during postinstall phase
        # snf-image-update-helper -y
        return []
746 747


748
class GTools(base.Component):
749 750 751 752
    REQUIRED_PACKAGES = [
        "snf-cyclades-gtools",
        ]

753
    @base.run_cmds
754
    def check(self):
755
        return ["ping -c1 %s" % self.ctx.mq.cname]
756

757
    @base.run_cmds
758 759 760
    def prepare(self):
        return [
            "sed -i 's/false/true/' /etc/default/snf-ganeti-eventd",
761
            "chown -R root:archipelago /etc/synnefo/",
762 763
            ]

764
    def _configure(self):
765 766
        tmpl = "/etc/synnefo/gtools.conf"
        replace = {
767 768
            "synnefo_user": config.synnefo_user,
            "synnefo_rabbitmq_passwd": config.synnefo_rabbitmq_passwd,
769
            "mq_node": self.ctx.mq.cname,
770 771 772
            }
        return [(tmpl, replace, {})]

773
    @base.run_cmds
774 775 776 777
    def restart(self):
        return ["/etc/init.d/snf-ganeti-eventd restart"]


778
class Network(base.Component):
779 780 781 782 783 784
    REQUIRED_PACKAGES = [
        "python-nfqueue",
        "snf-network",
        "nfdhcpd",
        ]

785
    def _configure(self):
786
        r1 = {
787
            "ns_node_ip": self.ctx.ns.ip
788 789
            }
        r2 = {
790 791 792 793 794 795
            "common_bridge": config.common_bridge,
            "public_iface": self.node.public_iface,
            "subnet": config.synnefo_public_network_subnet,
            "gateway": config.synnefo_public_network_gateway,
            "router_ip": self.ctx.router.ip,
            "node_ip": self.node.ip,
796 797
            }
        r3 = {
798 799 800
            "domain": self.node.domain,
            "server": self.ctx.ns.ip,
            "keyfile": config.ddns_private_key,
801 802 803 804 805 806 807 808
            }

        return [
            ("/etc/nfdhcpd/nfdhcpd.conf", r1, {}),
            ("/etc/rc.local", r2, {"mode": 0755}),
            ("/etc/default/snf-network", r3, {}),
            ]

809
    @base.run_cmds
810 811 812
    def initialize(self):
        return ["/etc/init.d/rc.local start"]

813
    @base.run_cmds
814 815 816 817
    def restart(self):
        return ["/etc/init.d/nfdhcpd restart"]


818
class Apache(base.Component):
819 820
    REQUIRED_PACKAGES = [
        "apache2",
821
        "python-openssl",
822 823
        ]

824
    @base.run_cmds
825 826 827 828 829 830 831
    def prepare(self):
        return [
            "a2enmod ssl", "a2enmod rewrite", "a2dissite default",
            "a2enmod headers",
            "a2enmod proxy_http", "a2dismod autoindex",
            ]

832 833
    def _configure(self):
        r1 = {"HOST": self.node.fqdn}
834 835 836
        return [
            ("/etc/apache2/sites-available/synnefo", r1, {}),
            ("/etc/apache2/sites-available/synnefo-ssl", r1, {}),
837
            ("/root/firefox_cert_override.py", {}, {})
838 839
            ]

840
    @base.run_cmds
841 842 843 844 845
    def initialize(self):
        return [
            "a2ensite synnefo", "a2ensite synnefo-ssl",
            ]

846
    @base.run_cmds
847 848
    def restart(self):
        return [
849 850
            "/etc/init.d/apache2 restart",
            ]
851 852


853
class Gunicorn(base.Component):
854
    REQUIRED_PACKAGES = [
855
        "python-gevent",
856 857 858
        "gunicorn",
        ]

859
    @base.run_cmds
860 861
    def prepare(self):
        return [
862
            "chown root:www-data /var/log/gunicorn",
863 864
            ]

865 866
    def _configure(self):
        r1 = {"HOST": self.node.fqdn}
867 868 869 870
        return [
            ("/etc/gunicorn.d/synnefo", r1, {}),
            ]

871
    @base.run_cmds
872 873
    def restart(self):
        return [
874 875
            "/etc/init.d/gunicorn restart",
            ]
876 877


878
class Common(base.Component):
879
    REQUIRED_PACKAGES = [
880
        "ntp",
881 882 883 884
        "snf-common",
        "snf-branding",
        ]

885
    def _configure(self):
886
        r1 = {
887 888 889 890
            "EMAIL_SUBJECT_PREFIX": self.node.hostname,
            "domain": self.node.domain,
            "HOST": self.node.fqdn,
            "MAIL_DIR": config.mail_dir,
891 892 893 894 895
            }
        return [
            ("/etc/synnefo/common.conf", r1, {}),
            ]

896
    @base.run_cmds
897
    def initialize(self):
898
        return ["mkdir -p {0}; chmod 777 {0}".format(config.mail_dir)]
899

900
    @base.run_cmds
901 902
    def restart(self):
        return [
903 904
            "/etc/init.d/gunicorn restart",
            ]
905 906


907
class Webproject(base.Component):
908
    REQUIRED_PACKAGES = [
909
        "python-psycopg2",
910 911
        "python-astakosclient",
        "snf-django-lib",
912 913 914
        "snf-webproject",
        ]

915
    @base.run_cmds
916
    def check(self):
917
        return ["ping -c1 %s" % self.ctx.db.cname]
918

919
    def _configure(self):
920
        r1 = {
921 922
            "synnefo_user": config.synnefo_user,
            "synnefo_db_passwd": config.synnefo_db_passwd,
923
            "db_node": self.ctx.db.cname,
924
            "domain": self.node.domain,
925
            "webproject_secret": config.webproject_secret,
926 927 928 929 930
            }
        return [
            ("/etc/synnefo/webproject.conf", r1, {}),
            ]

931
    @base.run_cmds
932 933
    def restart(self):
        return [
934 935
            "/etc/init.d/gunicorn restart",
            ]
936 937


938
class Astakos(base.Component):
939 940 941 942
    REQUIRED_PACKAGES = [
        "snf-astakos-app",
        ]

943
    alias = constants.ASTAKOS
944
    service = constants.ASTAKOS
945 946

    def required_components(self):
947
        return [HW, SSH, DNS, APT, Apache, Gunicorn, Common, Webproject]
948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965

    @base.run_cmds
    def setup_user(self):
        self._debug("Setting up user")
        return self._set_default_quota() + \
            self._add_user() + self._activate_user()

    @update_admin
    def admin_pre(self):
        self.NS.update_ns()
        self.DB.allow_db_access()
        self.DB.restart()

    @property
    def conflicts(self):
        return [CMS]

    @base.run_cmds
966
    def export_service(self):
967
        f = config.jsonfile
968 969 970 971
        return [
            "snf-manage service-export-astakos > %s" % f
            ]

972
    @base.run_cmds
973
    def import_service(self):
974
        f = config.jsonfile
975 976 977 978
        return [
            "snf-manage service-import --json=%s" % f
            ]

979 980
    @base.run_cmds
    def set_astakos_default_quota(self):
981 982
        cmd = "snf-manage resource-modify"
        return [
983
            "%s --system-default 2 astakos.pending_app" % cmd,
984 985 986 987 988 989 990
            "%s --project-default 0 astakos.pending_app" % cmd,
            ]

    @base.run_cmds
    def set_cyclades_default_quota(self):
        cmd = "snf-manage resource-modify"
        return [
991 992 993 994 995 996 997 998
            "%s --system-default 4 cyclades.vm" % cmd,
            "%s --system-default 40G cyclades.disk" % cmd,
            "%s --system-default 16G cyclades.total_ram" % cmd,
            "%s --system-default 8G cyclades.ram" % cmd,
            "%s --system-default 32 cyclades.total_cpu" % cmd,
            "%s --system-default 16 cyclades.cpu" % cmd,
            "%s --system-default 4 cyclades.network.private" % cmd,
            "%s --system-default 4 cyclades.floating_ip" % cmd,
999 1000 1001 1002 1003 1004 1005 1006
            "%s --project-default 0 cyclades.vm" % cmd,
            "%s --project-default 0 cyclades.disk" % cmd,