Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (19.2 kB)

1
import json
2
import time
3
import ipaddr
4
import os
5
import signal
6
import time
7
import ConfigParser
8
import argparse
9
import sys
10
import re
11
import random
12
import subprocess
13
import imp
14
import ast
15
from snfdeploy.lib import *
16

    
17
def print_available_actions(command):
18

    
19
  if command == "keygen":
20
    print """
21
Usage: snf-deploy keygen [--force]
22

23
  Generate new ssh keys (both rsa and dsa keypairs)
24

25
  """
26

    
27
  if command == "vcluster":
28
    print """
29
Usage: snf-deploy vcluster
30

31
  Run the following actions concerning the local virtual cluster:
32

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

38
  """
39

    
40
  if command == "prepare":
41
    print """
42
Usage: snf-deploy prepare
43

44
  Run the following actions concerning deployment preparation:
45

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

52
  """
53

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

58
  Run the following actions concerning a ganeti backend:
59

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

66
    or
67

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

70
  """
71

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

76
  Run any of the following fabric commands:
77

78

79
    Setup commands:        Init commands:                Admin commands:
80
      setup_apache           add_pools                     activate_user
81
      setup_apt              add_rapi_user                 add_backend
82
      setup_astakos          add_nodes                     add_image_locally
83
      setup_cms              astakos_loaddata              add_network
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_gtools           init_cluster                  create_vlans
89
      setup_gunicorn         setup_nfs_clients             destroy_db
90
      setup_hosts            setup_nfs_server              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..Append NS options to resolv.conf")
194

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

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

    
212

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

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

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

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

    
236

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

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

    
246
  hide = ",".join(level_aliases["everything"])
247
  show = None
248

    
249
  if args.verbose == 1:
250
    show = ",".join(levels[:3])
251
    hide = ",".join(levels[3:])
252
  elif args.verbose == 2:
253
    show = ",".join(levels[:4])
254
    hide = ",".join(levels[4:])
255
  elif args.verbose >= 3 or args.debug:
256
    show = ",".join(levels)
257
    hide = None
258

    
259
  if args.ssh_key:
260
    fabcmd = "fab -i %s " % args.ssh_key
261
  else:
262
    fabcmd = "fab "
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
    hosts = [env.nodes_info[n].hostname for n in nodes]
272
    actions = [a + ':hosts="%s"' % ";".join(hosts) for a in actions]
273

    
274
  extra = " ".join(actions)
275

    
276
  fabcmd += extra
277

    
278
  if show:
279
    fabcmd += " --show %s " % show
280
  if hide:
281
    fabcmd += " --hide %s " % hide
282

    
283
  # print("snf-deploy run " + " ".join(actions) + " -vvv")
284
  print(fabcmd)
285

    
286
  if not args.dry_run:
287
    ret = os.system(fabcmd)
288
    if ret != 0:
289
        status = "exit with status %s" % ret
290
        sys.exit(status)
291

    
292

    
293
def cluster(args, env):
294
  for hostname, mac in env.node2mac.iteritems():
295
    launch_vm(args, env, hostname, mac)
296

    
297
  time.sleep(30)
298
  os.system("reset")
299

    
300

    
301
def launch_vm(args, env, hostname, mac):
302
  check_pidfile("%s/%s.pid" % (env.run, hostname))
303

    
304
  print("Launching cluster node {0}..".format(hostname))
305
  os.environ["BRIDGE"] = env.bridge
306
  if args.vnc:
307
    graphics = "-vnc :{0}".format(random.randint(1, 1000))
308
  else:
309
    graphics = "-nographic"
310

    
311
  disks = """ \
312
-drive file={0}/{1}.disk0,format=raw,if=none,id=drive0,snapshot=on \
313
-device virtio-blk-pci,drive=drive0,id=virtio-blk-pci.0 \
314
  """.format(env.images, env.os)
315

    
316
  if ast.literal_eval(env.create_extra_disk):
317
    disks += """ \
318
-drive file={0}/{1}.disk1,format=raw,if=none,id=drive1,snapshot=on \
319
-device virtio-blk-pci,drive=drive1,id=virtio-blk-pci.1 \
320
  """.format(env.images, env.os)
321

    
322

    
323
  ifup = env.lib + "/ifup"
324
  nics = """ \
325
-netdev tap,id=netdev0,script={0},downscript=no \
326
-device virtio-net-pci,mac={1},netdev=netdev0,id=virtio-net-pci.0 \
327
-netdev tap,id=netdev1,script={0},downscript=no \
328
-device virtio-net-pci,mac={2},netdev=netdev1,id=virtio-net-pci.1 \
329
-netdev tap,id=netdev2,script={0},downscript=no \
330
-device virtio-net-pci,mac={3},netdev=netdev2,id=virtio-net-pci.2 \
331
  """.format(ifup, mac, randomMAC(), randomMAC())
332

    
333
  cmd = """
334
/usr/bin/kvm -name {0} -pidfile {1}/{0}.pid -balloon virtio -daemonize \
335
-monitor unix:{1}/{0}.monitor,server,nowait -usbdevice tablet -boot c \
336
{2} \
337
{3} \
338
-m {4} -smp {5} {6} \
339
  """.format(hostname, env.run, disks, nics, args.mem, args.smp, graphics)
340
  print cmd
341
  os.system(cmd)
342

    
343

    
344
def dnsmasq(args, env):
345
  check_pidfile(env.run + "/dnsmasq.pid")
346
  cmd = "dnsmasq --pid-file={0}/dnsmasq.pid --conf-file={1}/conf-file".format(env.run, env.dns)
347
  os.system(cmd)
348

    
349

    
350
def get_packages(args, env):
351
  if env.package_url:
352
    os.system("rm {0}/*.deb".format(env.packages))
353
    os.system("wget -r --level=1 -nH --no-parent --cut-dirs=4 {0} -P {1}".format(env.package_url, env.packages))
354

    
355

    
356
def parse_options():
357
  parser = argparse.ArgumentParser()
358

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

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

    
390
  parser.add_argument("-i", "--ssh-key", dest="ssh_key",
391
                      default=None,
392
                      help="Path of an existing ssh key to use")
393

    
394
  parser.add_argument("--no-key-inject", dest="key_inject",
395
                      default=True, action="store_false",
396
                      help="Whether to inject ssh key pairs to hosts")
397

    
398
  # backend related options
399
  parser.add_argument("--cluster-name", dest="cluster_name",
400
                      default="ganeti1",
401
                      help="The cluster name in ganeti.conf")
402

    
403
  # backend related options
404
  parser.add_argument("--cluster-node", dest="cluster_node",
405
                      default=None,
406
                      help="The node to add to the existing cluster")
407

    
408
  # available commands
409
  parser.add_argument("command", type=str,
410
                      choices=["packages", "vcluster", "prepare",
411
                               "synnefo", "backend", "ganeti",
412
                               "run", "cleanup", "test",
413
                               "all", "add", "keygen"],
414
                      help="Run on of the supported deployment commands")
415

    
416
  # available actions for the run command
417
  parser.add_argument("actions", type=str, nargs="*",
418
                      help="Run one or more of the supported subcommands")
419

    
420
  # disable colors in terminal
421
  parser.add_argument("--disable-colors", dest="disable_colors", default=False,
422
                      action="store_true", help="Disable colors in terminal")
423

    
424
  return parser.parse_args()
425

    
426

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

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

    
499
    return ret
500

    
501

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

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

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

    
522

    
523
def do_create_keys(args, env):
524
  d = os.path.join(env.templates, "root/.ssh")
525
  a = os.path.join(d, "authorized_keys")
526
  # Delete old keys
527
  for filename in os.listdir(d):
528
      os.remove(os.path.join(d, filename))
529
  # Generate new keys
530
  for t in ("dsa", "rsa"):
531
    f = os.path.join(d, "id_" + t)
532
    cmd = 'ssh-keygen -q -t {0} -f {1} -N ""'.format(t, f)
533
    os.system(cmd)
534
    cmd = 'cat {0}.pub >> {1}'.format(f, a)
535
    os.system(cmd)
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", "setup_network", "setup_gtools",
558
      ]
559
    fabcommand(args, env, actions, [args.cluster_node])
560

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

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

    
572
def main():
573
  args = parse_options()
574

    
575
  conf = Conf.configure(args.confdir, args.cluster_name, args, args.autoconf)
576
  env = Env(conf)
577

    
578
  create_dir(env.run, False)
579
  create_dir(env.dns, False)
580

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

    
595
  if args.command == "test":
596
    conf.print_config()
597

    
598
  if args.command == "cleanup":
599
    cleanup(args, env)
600

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

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

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

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

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

    
624
  if args.command == "ganeti":
625
    actions += get_actions("ganeti")
626
    fabcommand(args, env, actions)
627

    
628

    
629

    
630

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

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

    
642

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

    
649

    
650
if __name__ == "__main__":
651
  sys.exit(main())