Statistics
| Branch: | Tag: | Revision:

root / snf-deploy / snfdeploy / __init__.py @ 0cfa5e03

History | View | Annotate | Download (20.2 kB)

1
import time
2
import os
3
import argparse
4
import sys
5
import re
6
import random
7
import ast
8
from snfdeploy.lib import check_pidfile, create_dir, get_default_route, \
9
    random_mac, Conf, Env
10
from snfdeploy import fabfile
11
from fabric.api import hide, settings, execute, show
12

    
13

    
14
def print_available_actions(command):
15

    
16
    if command == "keygen":
17
        print """
18
Usage: snf-deploy keygen [--force]
19

20
  Generate new ssh keys (both rsa and dsa keypairs)
21

22
  """
23

    
24
    if command == "vcluster":
25
        print """
26
Usage: snf-deploy vcluster
27

28
  Run the following actions concerning the local virtual cluster:
29

30
    - Download base image and create additional disk \
31
(if --create-extra-disk is passed)
32
    - Does all the network related actions (bridge, iptables, NAT)
33
    - Launches dnsmasq for dhcp server on bridge
34
    - Creates the virtual cluster (with kvm)
35

36
  """
37

    
38
    if command == "prepare":
39
        print """
40
Usage: snf-deploy prepare
41

42
  Run the following actions concerning deployment preparation:
43

44
    - Setup an internal Domain Name Server
45
    - Tweak hosts and add ssh keys
46
    - Check network setup
47
    - Setup apt repository and apt-get update
48
    - Setup the nfs server and clients among all nodes
49

50
  """
51

    
52
    if command == "backend":
53
        print """
54
Usage: snf-deploy backend [update]
55

56
  Run the following actions concerning a ganeti backend:
57

58
    - Create and add a backend to cyclades
59
    - Does all the net-infra specific actions in backend nodes
60
      (create/connect bridges, iptables..)
61
    - Does all the storage-infra specific actions in backend nodes
62
      depending on the --extra-disk option \
63
(create VG, enable lvm/drbd storage..)
64

65
    or
66

67
    - Update packages in an already registered backend in cyclades.
68

69
  """
70

    
71
    if command == "run":
72
        print """
73
Usage: snf-deploy run <action> [<action>...]
74

75
  Run any of the following fabric commands:
76

77

78
    Setup commands:        Init commands:                Admin commands:
79
      setup_apache           add_pools                     activate_user
80
      setup_apt              add_rapi_user                 add_backend
81
      setup_astakos          add_nodes                     add_image_locally
82
      setup_cms              astakos_loaddata              add_network
83
      setup_collectd
84
      setup_common           astakos_register_components   add_ns
85
      setup_cyclades         cms_loaddata                  add_user
86
      setup_db               cyclades_loaddata             connect_bridges
87
      setup_ganeti           enable_drbd                   create_bridges
88
      setup_ganeti_collectd
89
      setup_gtools           init_cluster                  create_vlans
90
      setup_gunicorn         setup_nfs_clients             destroy_db
91
      setup_hosts            setup_nfs_server              \
92
get_auth_token_from_db
93
      setup_image_helper     update_ns_for_ganeti          get_service_details
94
      setup_image_host                                     gnt_instance_add
95
      setup_iptables                                       gnt_network_add
96
      setup_kamaki         Test commands:                  register_image
97
      setup_lvm              test                          restart_services
98
      setup_mq                                             setup_drbd_dparams
99
      setup_net_infra
100
      setup_network
101
      setup_ns
102
      setup_pithos
103
      setup_pithos_dir
104
      setup_router
105
      setup_stats
106
      setup_stats_collectd
107
      setup_vncauthproxy
108
      setup_webproject
109

110
  """
111

    
112
    sys.exit(1)
113

    
114

    
115
def create_dnsmasq_files(args, env):
116

    
117
    print("Customize dnsmasq..")
118
    out = env.dns
119

    
120
    hostsfile = open(out + "/dhcp-hostsfile", "w")
121
    optsfile = open(out + "/dhcp-optsfile", "w")
122
    conffile = open(out + "/conf-file", "w")
123

    
124
    for node, info in env.nodes_info.iteritems():
125
        # serve ip and hostname to nodes
126
        hostsfile.write("%s,%s,%s,2m\n" % (info.mac, info.ip, info.hostname))
127

    
128
    hostsfile.write("52:54:56:*:*:*,ignore\n")
129

    
130
    # Netmask
131
    optsfile.write("1,%s\n" % env.net.netmask)
132
    # Gateway
133
    optsfile.write("3,%s\n" % env.gateway)
134
    # Namesevers
135
    optsfile.write("6,%s\n" % "8.8.8.8")
136

    
137
    dnsconf = """
138
user=dnsmasq
139
bogus-priv
140
no-poll
141
no-negcache
142
leasefile-ro
143
bind-interfaces
144
except-interface=lo
145
dhcp-fqdn
146
no-resolv
147
# disable DNS
148
port=0
149
""".format(env.ns.ip)
150

    
151
    dnsconf += """
152
# serve domain and search domain for resolv.conf
153
domain={5}
154
interface={0}
155
dhcp-hostsfile={1}
156
dhcp-optsfile={2}
157
dhcp-range={0},{4},static,2m
158
""".format(env.bridge, hostsfile.name, optsfile.name,
159
           env.domain, env.net.network, env.domain)
160

    
161
    conffile.write(dnsconf)
162

    
163
    hostsfile.close()
164
    optsfile.close()
165
    conffile.close()
166

    
167

    
168
def cleanup(args, env):
169
    print("Cleaning up bridge, NAT, resolv.conf...")
170

    
171
    for f in os.listdir(env.run):
172
        if re.search(".pid$", f):
173
            check_pidfile(os.path.join(env.run, f))
174

    
175
    create_dir(env.run, True)
176
    # create_dir(env.cmd, True)
177
    cmd = """
178
    iptables -t nat -D POSTROUTING -s {0} -o {1} -j MASQUERADE
179
    echo 0 > /proc/sys/net/ipv4/ip_forward
180
    iptables -D INPUT -i {2} -j ACCEPT
181
    iptables -D FORWARD -i {2} -j ACCEPT
182
    iptables -D OUTPUT -o {2} -j ACCEPT
183
    """.format(env.subnet, get_default_route()[1], env.bridge)
184
    os.system(cmd)
185

    
186
    cmd = """
187
    ip link show {0} && ip addr del {1}/{2} dev {0}
188
    sleep 1
189
    ip link set {0} down
190
    sleep 1
191
    brctl delbr {0}
192
    """.format(env.bridge, env.gateway, env.net.prefixlen)
193
    os.system(cmd)
194

    
195

    
196
def network(args, env):
197
    print("Create bridge..Add gateway IP..Activate NAT.."
198
          "Append NS options to resolv.conf")
199

    
200
    cmd = """
201
    ! ip link show {0} && brctl addbr {0} && ip link set {0} up
202
    sleep 1
203
    ip link set promisc on dev {0}
204
    ip addr add {1}/{2} dev {0}
205
    """.format(env.bridge, env.gateway, env.net.prefixlen)
206
    os.system(cmd)
207

    
208
    cmd = """
209
    iptables -t nat -A POSTROUTING -s {0} -o {1} -j MASQUERADE
210
    echo 1 > /proc/sys/net/ipv4/ip_forward
211
    iptables -I INPUT 1 -i {2} -j ACCEPT
212
    iptables -I FORWARD 1 -i {2} -j ACCEPT
213
    iptables -I OUTPUT 1 -o {2} -j ACCEPT
214
    """.format(env.subnet, get_default_route()[1], env.bridge)
215
    os.system(cmd)
216

    
217

    
218
def image(args, env):
219
    if env.os == "ubuntu":
220
        url = env.ubuntu_image_url
221
    else:
222
        url = env.squeeze_image_url
223

    
224
    disk0 = "{0}/{1}.disk0".format(env.images, env.os)
225
    disk1 = "{0}/{1}.disk1".format(env.images, env.os)
226

    
227
    if url and not os.path.exists(disk0):
228
        cmd = "wget {0} -O {1}".format(url, disk0)
229
        os.system(cmd)
230

    
231
    if ast.literal_eval(env.create_extra_disk) and not os.path.exists(disk1):
232
        if env.lvg:
233
            cmd = "lvcreate -L30G -n{0}.disk1 {1}".format(env.os, env.lvg)
234
            os.system(cmd)
235
            cmd = "ln -s /dev/{0}/{1}.disk1 {2}".format(env.lvg, env.os, disk1)
236
            os.system(cmd)
237
        else:
238
            cmd = "dd if=/dev/zero of={0} bs=10M count=3000".format(disk1)
239
            os.system(cmd)
240

    
241

    
242
def fabcommand(args, env, actions, nodes=[]):
243
    levels = ["status", "aborts", "warnings", "running",
244
              "stdout", "stderr", "user", "debug"]
245

    
246
    level_aliases = {
247
        "output": ["stdout", "stderr"],
248
        "everything": ["warnings", "running", "user", "output"]
249
    }
250

    
251
    lhide = level_aliases["everything"]
252
    lshow = []
253

    
254
    if args.verbose == 1:
255
        lshow = levels[:3]
256
        lhide = levels[3:]
257
    elif args.verbose == 2:
258
        lshow = levels[:4]
259
        lhide = levels[4:]
260
    elif args.verbose >= 3 or args.debug:
261
        lshow = levels
262
        lhide = []
263

    
264
#   fabcmd += " --fabfile {4}/fabfile.py \
265
# setup_env:confdir={0},packages={1},templates={2},cluster_name={3},\
266
# autoconf={5},disable_colors={6},key_inject={7} \
267
# ".format(args.confdir, env.packages, env.templates, args.cluster_name,
268
#          env.lib, args.autoconf, args.disable_colors, args.key_inject)
269

    
270
    if nodes:
271
        ips = [env.nodes_info[n].ip for n in nodes]
272

    
273
    fabfile.setup_env(args)
274
    with settings(hide(*lhide), show(*lshow)):
275
        print " ".join(actions)
276
        for a in actions:
277
            fn = getattr(fabfile, a)
278
            if not args.dry_run:
279
                if nodes:
280
                    execute(fn, hosts=ips)
281
                else:
282
                    execute(fn)
283

    
284

    
285
def cluster(args, env):
286
    for hostname, mac in env.node2mac.iteritems():
287
        launch_vm(args, env, hostname, mac)
288

    
289
    time.sleep(30)
290
    os.system("reset")
291

    
292

    
293
def launch_vm(args, env, hostname, mac):
294
    check_pidfile("%s/%s.pid" % (env.run, hostname))
295

    
296
    print("Launching cluster node {0}..".format(hostname))
297
    os.environ["BRIDGE"] = env.bridge
298
    if args.vnc:
299
        graphics = "-vnc :{0}".format(random.randint(1, 1000))
300
    else:
301
        graphics = "-nographic"
302

    
303
    disks = """ \
304
-drive file={0}/{1}.disk0,format=raw,if=none,id=drive0,snapshot=on \
305
-device virtio-blk-pci,drive=drive0,id=virtio-blk-pci.0 \
306
""".format(env.images, env.os)
307

    
308
    if ast.literal_eval(env.create_extra_disk):
309
        disks += """ \
310
-drive file={0}/{1}.disk1,format=raw,if=none,id=drive1,snapshot=on \
311
-device virtio-blk-pci,drive=drive1,id=virtio-blk-pci.1 \
312
""".format(env.images, env.os)
313

    
314
    ifup = env.lib + "/ifup"
315
    nics = """ \
316
-netdev tap,id=netdev0,script={0},downscript=no \
317
-device virtio-net-pci,mac={1},netdev=netdev0,id=virtio-net-pci.0 \
318
-netdev tap,id=netdev1,script={0},downscript=no \
319
-device virtio-net-pci,mac={2},netdev=netdev1,id=virtio-net-pci.1 \
320
-netdev tap,id=netdev2,script={0},downscript=no \
321
-device virtio-net-pci,mac={3},netdev=netdev2,id=virtio-net-pci.2 \
322
""".format(ifup, mac, random_mac(), random_mac())
323

    
324
    cmd = """
325
/usr/bin/kvm -name {0} -pidfile {1}/{0}.pid -balloon virtio -daemonize \
326
-monitor unix:{1}/{0}.monitor,server,nowait -usbdevice tablet -boot c \
327
{2} \
328
{3} \
329
-m {4} -smp {5} {6} \
330
""".format(hostname, env.run, disks, nics, args.mem, args.smp, graphics)
331
    print cmd
332
    os.system(cmd)
333

    
334

    
335
def dnsmasq(args, env):
336
    check_pidfile(env.run + "/dnsmasq.pid")
337
    cmd = "dnsmasq --pid-file={0}/dnsmasq.pid --conf-file={1}/conf-file"\
338
        .format(env.run, env.dns)
339
    os.system(cmd)
340

    
341

    
342
def get_packages(args, env):
343
    if env.package_url:
344
        os.system("rm {0}/*.deb".format(env.packages))
345
        os.system("wget -r --level=1 -nH --no-parent --cut-dirs=4 {0} -P {1}"
346
                  .format(env.package_url, env.packages))
347

    
348

    
349
def parse_options():
350
    parser = argparse.ArgumentParser()
351

    
352
    # Directories to load/store config
353
    parser.add_argument("-c", dest="confdir",
354
                        default="/etc/snf-deploy",
355
                        help="Directory to find default configuration")
356
    parser.add_argument("--dry-run", dest="dry_run",
357
                        default=False, action="store_true",
358
                        help="Do not execute or write anything.")
359
    parser.add_argument("-v", dest="verbose",
360
                        default=0, action="count",
361
                        help="Increase verbosity.")
362
    parser.add_argument("-d", dest="debug",
363
                        default=False, action="store_true",
364
                        help="Debug mode")
365
    parser.add_argument("--autoconf", dest="autoconf",
366
                        default=False, action="store_true",
367
                        help="In case of all in one auto conf setup")
368

    
369
    # virtual cluster related options
370
    parser.add_argument("--mem", dest="mem",
371
                        default=2024,
372
                        help="Memory for every virutal node")
373
    parser.add_argument("--smp", dest="smp",
374
                        default=1,
375
                        help="Virtual CPUs for every virtual node")
376
    parser.add_argument("--vnc", dest="vnc",
377
                        default=False, action="store_true",
378
                        help="Wheter virtual nodes will have a vnc "
379
                             "console or not")
380
    parser.add_argument("--force", dest="force",
381
                        default=False, action="store_true",
382
                        help="Force the creation of new ssh key pairs")
383

    
384
    parser.add_argument("-i", "--ssh-key", dest="ssh_key",
385
                        default=None,
386
                        help="Path of an existing ssh key to use")
387

    
388
    parser.add_argument("--no-key-inject", dest="key_inject",
389
                        default=True, action="store_false",
390
                        help="Whether to inject ssh key pairs to hosts")
391

    
392
    # backend related options
393
    parser.add_argument("--cluster-name", dest="cluster_name",
394
                        default="ganeti1",
395
                        help="The cluster name in ganeti.conf")
396

    
397
    # backend related options
398
    parser.add_argument("--cluster-node", dest="cluster_node",
399
                        default=None,
400
                        help="The node to add to the existing cluster")
401

    
402
    # available commands
403
    parser.add_argument("command", type=str,
404
                        choices=["packages", "vcluster", "prepare",
405
                                 "synnefo", "backend", "ganeti",
406
                                 "run", "cleanup", "test",
407
                                 "all", "add", "keygen"],
408
                        help="Run on of the supported deployment commands")
409

    
410
    # available actions for the run command
411
    parser.add_argument("actions", type=str, nargs="*",
412
                        help="Run one or more of the supported subcommands")
413

    
414
    # disable colors in terminal
415
    parser.add_argument("--disable-colors", dest="disable_colors",
416
                        default=False, action="store_true",
417
                        help="Disable colors in terminal")
418

    
419
    return parser.parse_args()
420

    
421

    
422
def get_actions(*args):
423
    actions = {
424
        # prepare actions
425
        "ns":  ["setup_ns", "setup_resolv_conf"],
426
        "hosts": ["setup_hosts", "add_keys"],
427
        "check": ["check_dhcp", "check_dns",
428
                  "check_connectivity", "check_ssh"],
429
        "apt": ["apt_get_update", "setup_apt"],
430
        "nfs": ["setup_nfs_server", "setup_nfs_clients"],
431
        "prepare":  [
432
            "setup_hosts", "add_keys",
433
            "setup_ns", "setup_resolv_conf",
434
            "check_dhcp", "check_dns", "check_connectivity", "check_ssh",
435
            "apt_get_update", "setup_apt",
436
            "setup_nfs_server", "setup_nfs_clients"
437
        ],
438
        # synnefo actions
439
        "synnefo": [
440
            "setup_mq", "setup_db",
441
            "setup_astakos",
442
            #TODO: astakos-quota fails if no user is added.
443
            #      add_user fails if no groups found
444
            "astakos_loaddata", "add_user", "activate_user",
445
            "astakos_register_components",
446
            "setup_cms", "cms_loaddata",
447
            "setup_pithos",
448
            "setup_vncauthproxy",
449
            "setup_cyclades", "cyclades_loaddata", "add_pools",
450
            "export_services", "import_services", "set_user_quota",
451
            "setup_kamaki", "upload_image", "register_image",
452
            "setup_burnin",
453
            "setup_stats"
454
        ],
455
        "supdate": [
456
            "apt_get_update", "setup_astakos",
457
            "setup_cms", "setup_pithos", "setup_cyclades"
458
        ],
459
        # backend actions
460
        "backend": [
461
            "setup_hosts",
462
            "update_ns_for_ganeti",
463
            "setup_ganeti", "init_cluster",
464
            "add_rapi_user", "add_nodes",
465
            "setup_image_host", "setup_image_helper",
466
            "setup_network",
467
            "setup_gtools", "add_backend", "add_network",
468
            "setup_lvm", "enable_lvm",
469
            "enable_drbd", "setup_drbd_dparams",
470
            "setup_net_infra", "setup_iptables", "setup_router",
471
        ],
472
        "bstorage": [
473
            "setup_lvm", "enable_lvm",
474
            "enable_drbd", "setup_drbd_dparams"
475
        ],
476
        "bnetwork": ["setup_net_infra", "setup_iptables", "setup_router"],
477
        "bupdate": [
478
            "apt_get_update", "setup_ganeti", "setup_image_host",
479
            "setup_image_helper", "setup_network", "setup_gtools"
480
        ],
481
        # ganeti actions
482
        "ganeti": [
483
            "update_ns_for_ganeti",
484
            "setup_ganeti", "init_cluster", "add_nodes",
485
            "setup_image_host", "setup_image_helper", "add_image_locally",
486
            "debootstrap", "setup_net_infra",
487
            "setup_lvm", "enable_lvm", "enable_drbd", "setup_drbd_dparams",
488
            "setup_ganeti_collectd"
489
        ],
490
        "gupdate": ["setup_apt", "setup_ganeti"],
491
        "gdestroy": ["destroy_cluster"],
492
    }
493

    
494
    ret = []
495
    for x in args:
496
        ret += actions[x]
497

    
498
    return ret
499

    
500

    
501
def must_create_keys(force, env):
502
    """Check if we need to create ssh keys
503

504
    If force is true we are going to overide the old keys.
505
    Else if there are already generated keys to use, don't create new ones.
506

507
    """
508
    if force:
509
        return True
510
    d = os.path.join(env.templates, "root/.ssh")
511
    auth_keys_exists = os.path.exists(os.path.join(d, "authorized_keys"))
512
    dsa_exists = os.path.exists(os.path.join(d, "id_dsa"))
513
    dsa_pub_exists = os.path.exists(os.path.join(d, "id_dsa.pub"))
514
    rsa_exists = os.path.exists(os.path.join(d, "id_rsa"))
515
    rsa_pub_exists = os.path.exists(os.path.join(d, "id_rsa.pub"))
516
    # If any of the above doesn't exist return True
517
    return not (dsa_exists and dsa_pub_exists
518
                and rsa_exists and rsa_pub_exists
519
                and auth_keys_exists)
520

    
521

    
522
def do_create_keys(args, env):
523
    d = os.path.join(env.templates, "root/.ssh")
524
    a = os.path.join(d, "authorized_keys")
525
    # Delete old keys
526
    for filename in os.listdir(d):
527
        os.remove(os.path.join(d, filename))
528
    # Generate new keys
529
    for t in ("dsa", "rsa"):
530
        f = os.path.join(d, "id_" + t)
531
        cmd = 'ssh-keygen -q -t {0} -f {1} -N ""'.format(t, f)
532
        os.system(cmd)
533
        cmd = 'cat {0}.pub >> {1}'.format(f, a)
534
        os.system(cmd)
535

    
536

    
537
def add_node(args, env):
538
    actions = [
539
        "update_ns_for_node:" + args.cluster_node,
540
    ]
541
    fabcommand(args, env, actions)
542
    actions = [
543
        "setup_resolv_conf",
544
        "apt_get_update",
545
        "setup_apt",
546
        "setup_hosts",
547
        "add_keys",
548
    ]
549
    fabcommand(args, env, actions, [args.cluster_node])
550

    
551
    actions = get_actions("check")
552
    fabcommand(args, env, actions)
553

    
554
    actions = [
555
        "setup_nfs_clients",
556
        "setup_ganeti",
557
        "setup_image_host", "setup_image_helper",
558
        "setup_network", "setup_gtools",
559
    ]
560
    fabcommand(args, env, actions, [args.cluster_node])
561

    
562
    actions = [
563
        "add_node:" + args.cluster_node,
564
    ]
565
    fabcommand(args, env, actions)
566

    
567
    actions = [
568
        "setup_lvm", "enable_drbd",
569
        "setup_net_infra", "setup_iptables",
570
    ]
571
    fabcommand(args, env, actions, [args.cluster_node])
572

    
573

    
574
def main():
575
    args = parse_options()
576

    
577
    conf = Conf(args)
578
    env = Env(conf)
579

    
580
    create_dir(env.run, False)
581
    create_dir(env.dns, False)
582

    
583
    # Check if there are keys to use
584
    if args.command == "keygen":
585
        if must_create_keys(args.force, env):
586
            do_create_keys(args, env)
587
            return 0
588
        else:
589
            print "Keys already existed.. aborting"
590
            return 1
591
    else:
592
        if (args.key_inject and (args.ssh_key is None)
593
                and must_create_keys(False, env)):
594
            print "No ssh keys to use. Run `snf-deploy keygen' first."
595
            return 1
596

    
597
    if args.command == "test":
598
        conf.print_config()
599

    
600
    if args.command == "cleanup":
601
        cleanup(args, env)
602

    
603
    if args.command == "packages":
604
        create_dir(env.packages, True)
605
        get_packages(args, env)
606

    
607
    if args.command == "vcluster":
608
        image(args, env)
609
        network(args, env)
610
        create_dnsmasq_files(args, env)
611
        dnsmasq(args, env)
612
        cluster(args, env)
613

    
614
    if args.command == "prepare":
615
        actions = get_actions("prepare")
616
        fabcommand(args, env, actions)
617

    
618
    if args.command == "synnefo":
619
        actions = get_actions("synnefo")
620
        fabcommand(args, env, actions)
621

    
622
    if args.command == "backend":
623
        actions = get_actions("backend")
624
        fabcommand(args, env, actions)
625

    
626
    if args.command == "ganeti":
627
        actions = get_actions("ganeti")
628
        fabcommand(args, env, actions)
629

    
630
    if args.command == "all":
631
        actions = get_actions("prepare", "synnefo", "backend")
632
        fabcommand(args, env, actions)
633

    
634
    if args.command == "add":
635
        if args.cluster_node:
636
            add_node(args, env)
637
        else:
638
            actions = get_actions("backend")
639
            fabcommand(args, env, actions)
640

    
641
    if args.command == "run":
642
        if not args.actions:
643
            print_available_actions(args.command)
644
        else:
645
            fabcommand(args, env, args.actions)
646

    
647

    
648
if __name__ == "__main__":
649
    sys.exit(main())