Statistics
| Branch: | Tag: | Revision:

root / snf-deploy / snfdeploy / __init__.py @ 4a769fc0

History | View | Annotate | Download (20 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_common           astakos_register_components   add_ns
84
      setup_cyclades         cms_loaddata                  add_user
85
      setup_db               cyclades_loaddata             connect_bridges
86
      setup_ganeti           enable_drbd                   create_bridges
87
      setup_gtools           init_cluster                  create_vlans
88
      setup_gunicorn         setup_nfs_clients             destroy_db
89
      setup_hosts            setup_nfs_server              \
90
get_auth_token_from_db
91
      setup_image_helper     update_ns_for_ganeti          get_service_details
92
      setup_image_host                                     gnt_instance_add
93
      setup_iptables                                       gnt_network_add
94
      setup_kamaki         Test commands:                  register_image
95
      setup_lvm              test                          restart_services
96
      setup_mq                                             setup_drbd_dparams
97
      setup_net_infra
98
      setup_network
99
      setup_ns
100
      setup_pithos
101
      setup_pithos_dir
102
      setup_router
103
      setup_vncauthproxy
104
      setup_webproject
105

106
  """
107

    
108
    sys.exit(1)
109

    
110

    
111
def create_dnsmasq_files(args, env):
112

    
113
    print("Customize dnsmasq..")
114
    out = env.dns
115

    
116
    hostsfile = open(out + "/dhcp-hostsfile", "w")
117
    optsfile = open(out + "/dhcp-optsfile", "w")
118
    conffile = open(out + "/conf-file", "w")
119

    
120
    for node, info in env.nodes_info.iteritems():
121
        # serve ip and hostname to nodes
122
        hostsfile.write("%s,%s,%s,2m\n" % (info.mac, info.ip, info.hostname))
123

    
124
    hostsfile.write("52:54:56:*:*:*,ignore\n")
125

    
126
    # Netmask
127
    optsfile.write("1,%s\n" % env.net.netmask)
128
    # Gateway
129
    optsfile.write("3,%s\n" % env.gateway)
130
    # Namesevers
131
    optsfile.write("6,%s\n" % "8.8.8.8")
132

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

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

    
157
    conffile.write(dnsconf)
158

    
159
    hostsfile.close()
160
    optsfile.close()
161
    conffile.close()
162

    
163

    
164
def cleanup(args, env):
165
    print("Cleaning up bridge, NAT, resolv.conf...")
166

    
167
    for f in os.listdir(env.run):
168
        if re.search(".pid$", f):
169
            check_pidfile(os.path.join(env.run, f))
170

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

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

    
191

    
192
def network(args, env):
193
    print("Create bridge..Add gateway IP..Activate NAT.."
194
          "Append NS options to resolv.conf")
195

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

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

    
213

    
214
def image(args, env):
215
    if env.os == "ubuntu":
216
        url = env.ubuntu_image_url
217
    else:
218
        url = env.squeeze_image_url
219

    
220
    disk0 = "{0}/{1}.disk0".format(env.images, env.os)
221
    disk1 = "{0}/{1}.disk1".format(env.images, env.os)
222

    
223
    if url and not os.path.exists(disk0):
224
        cmd = "wget {0} -O {1}".format(url, disk0)
225
        os.system(cmd)
226

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

    
237

    
238
def fabcommand(args, env, actions, nodes=[]):
239
    levels = ["status", "aborts", "warnings", "running",
240
              "stdout", "stderr", "user", "debug"]
241

    
242
    level_aliases = {
243
        "output": ["stdout", "stderr"],
244
        "everything": ["warnings", "running", "user", "output"]
245
    }
246

    
247
    lhide = level_aliases["everything"]
248
    lshow = []
249

    
250
    if args.verbose == 1:
251
        lshow = levels[:3]
252
        lhide = levels[3:]
253
    elif args.verbose == 2:
254
        lshow = levels[:4]
255
        lhide = levels[4:]
256
    elif args.verbose >= 3 or args.debug:
257
        lshow = levels
258
        lhide = []
259

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

    
266
    if nodes:
267
        ips = [env.nodes_info[n].ip for n in nodes]
268

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

    
280

    
281
def cluster(args, env):
282
    for hostname, mac in env.node2mac.iteritems():
283
        launch_vm(args, env, hostname, mac)
284

    
285
    time.sleep(30)
286
    os.system("reset")
287

    
288

    
289
def launch_vm(args, env, hostname, mac):
290
    check_pidfile("%s/%s.pid" % (env.run, hostname))
291

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

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

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

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

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

    
330

    
331
def dnsmasq(args, env):
332
    check_pidfile(env.run + "/dnsmasq.pid")
333
    cmd = "dnsmasq --pid-file={0}/dnsmasq.pid --conf-file={1}/conf-file"\
334
        .format(env.run, env.dns)
335
    os.system(cmd)
336

    
337

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

    
344

    
345
def parse_options():
346
    parser = argparse.ArgumentParser()
347

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

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

    
380
    parser.add_argument("-i", "--ssh-key", dest="ssh_key",
381
                        default=None,
382
                        help="Path of an existing ssh key to use")
383

    
384
    parser.add_argument("--no-key-inject", dest="key_inject",
385
                        default=True, action="store_false",
386
                        help="Whether to inject ssh key pairs to hosts")
387

    
388
    # backend related options
389
    parser.add_argument("--cluster-name", dest="cluster_name",
390
                        default="ganeti1",
391
                        help="The cluster name in ganeti.conf")
392

    
393
    # backend related options
394
    parser.add_argument("--cluster-node", dest="cluster_node",
395
                        default=None,
396
                        help="The node to add to the existing cluster")
397

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

    
406
    # available actions for the run command
407
    parser.add_argument("actions", type=str, nargs="*",
408
                        help="Run one or more of the supported subcommands")
409

    
410
    # disable colors in terminal
411
    parser.add_argument("--disable-colors", dest="disable_colors",
412
                        default=False, action="store_true",
413
                        help="Disable colors in terminal")
414

    
415
    return parser.parse_args()
416

    
417

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

    
488
    ret = []
489
    for x in args:
490
        ret += actions[x]
491

    
492
    return ret
493

    
494

    
495
def must_create_keys(force, env):
496
    """Check if we need to create ssh keys
497

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

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

    
515

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

    
530

    
531
def add_node(args, env):
532
    actions = [
533
        "update_ns_for_node:" + args.cluster_node,
534
    ]
535
    fabcommand(args, env, actions)
536
    actions = [
537
        "setup_resolv_conf",
538
        "apt_get_update",
539
        "setup_apt",
540
        "setup_hosts",
541
        "add_keys",
542
    ]
543
    fabcommand(args, env, actions, [args.cluster_node])
544

    
545
    actions = get_actions("check")
546
    fabcommand(args, env, actions)
547

    
548
    actions = [
549
        "setup_nfs_clients",
550
        "setup_ganeti",
551
        "setup_image_host", "setup_image_helper",
552
        "setup_network", "setup_gtools",
553
    ]
554
    fabcommand(args, env, actions, [args.cluster_node])
555

    
556
    actions = [
557
        "add_node:" + args.cluster_node,
558
    ]
559
    fabcommand(args, env, actions)
560

    
561
    actions = [
562
        "setup_lvm", "enable_drbd",
563
        "setup_net_infra", "setup_iptables",
564
    ]
565
    fabcommand(args, env, actions, [args.cluster_node])
566

    
567

    
568
def main():
569
    args = parse_options()
570

    
571
    conf = Conf(args)
572
    env = Env(conf)
573

    
574
    create_dir(env.run, False)
575
    create_dir(env.dns, False)
576

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

    
591
    if args.command == "test":
592
        conf.print_config()
593

    
594
    if args.command == "cleanup":
595
        cleanup(args, env)
596

    
597
    if args.command == "packages":
598
        create_dir(env.packages, True)
599
        get_packages(args, env)
600

    
601
    if args.command == "vcluster":
602
        image(args, env)
603
        network(args, env)
604
        create_dnsmasq_files(args, env)
605
        dnsmasq(args, env)
606
        cluster(args, env)
607

    
608
    if args.command == "prepare":
609
        actions = get_actions("prepare")
610
        fabcommand(args, env, actions)
611

    
612
    if args.command == "synnefo":
613
        actions = get_actions("synnefo")
614
        fabcommand(args, env, actions)
615

    
616
    if args.command == "backend":
617
        actions = get_actions("backend")
618
        fabcommand(args, env, actions)
619

    
620
    if args.command == "ganeti":
621
        actions = get_actions("ganeti")
622
        fabcommand(args, env, actions)
623

    
624
    if args.command == "all":
625
        actions = get_actions("prepare", "synnefo", "backend")
626
        fabcommand(args, env, actions)
627

    
628
    if args.command == "add":
629
        if args.cluster_node:
630
            add_node(args, env)
631
        else:
632
            actions = get_actions("backend")
633
            fabcommand(args, env, actions)
634

    
635
    if args.command == "run":
636
        if not args.actions:
637
            print_available_actions(args.command)
638
        else:
639
            fabcommand(args, env, args.actions)
640

    
641

    
642
if __name__ == "__main__":
643
    sys.exit(main())