Statistics
| Branch: | Tag: | Revision:

root / snf-deploy / snfdeploy / __init__.py @ ca1de37b

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_cyclades", "cyclades_loaddata", "add_pools",
445
            "export_services", "import_services", "setup_vncauthproxy",
446
            "setup_kamaki", "upload_image", "register_image",
447
            "setup_burnin"
448
        ],
449
        "supdate": [
450
            "apt_get_update", "setup_astakos",
451
            "setup_cms", "setup_pithos", "setup_cyclades"
452
        ],
453
        # backend actions
454
        "backend": [
455
            "setup_hosts",
456
            "update_ns_for_ganeti",
457
            "setup_ganeti", "init_cluster",
458
            "add_rapi_user", "add_nodes",
459
            "setup_image_host", "setup_image_helper",
460
            "setup_network",
461
            "setup_gtools", "add_backend", "add_network",
462
            "setup_lvm", "enable_lvm",
463
            "enable_drbd", "setup_drbd_dparams",
464
            "setup_net_infra", "setup_iptables", "setup_router",
465
        ],
466
        "bstorage": [
467
            "setup_lvm", "enable_lvm",
468
            "enable_drbd", "setup_drbd_dparams"
469
        ],
470
        "bnetwork": ["setup_net_infra", "setup_iptables", "setup_router"],
471
        "bupdate": [
472
            "apt_get_update", "setup_ganeti", "setup_image_host",
473
            "setup_image_helper", "setup_network", "setup_gtools"
474
        ],
475
        # ganeti actions
476
        "ganeti": [
477
            "update_ns_for_ganeti",
478
            "setup_ganeti", "init_cluster", "add_nodes",
479
            "setup_image_host", "setup_image_helper", "add_image_locally",
480
            "debootstrap", "setup_net_infra",
481
            "setup_lvm", "enable_lvm", "enable_drbd", "setup_drbd_dparams",
482
        ],
483
        "gupdate": ["setup_apt", "setup_ganeti"],
484
        "gdestroy": ["destroy_cluster"],
485
    }
486

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

    
491
    return ret
492

    
493

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

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

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

    
514

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

    
529

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

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

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

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

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

    
566

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
640

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