Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (19.1 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
    sys.exit(os.system(fabcmd))
288

    
289

    
290
def cluster(args, env):
291
  for hostname, mac in env.node2mac.iteritems():
292
    launch_vm(args, env, hostname, mac)
293

    
294
  time.sleep(30)
295
  os.system("reset")
296

    
297

    
298
def launch_vm(args, env, hostname, mac):
299
  check_pidfile("%s/%s.pid" % (env.run, hostname))
300

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

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

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

    
319

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

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

    
340

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

    
346

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

    
352

    
353
def parse_options():
354
  parser = argparse.ArgumentParser()
355

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

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

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

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

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

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

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

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

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

    
421
  return parser.parse_args()
422

    
423

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

    
492
    ret = []
493
    for x in args:
494
      ret += actions[x]
495

    
496
    return ret
497

    
498

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

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

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

    
519

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

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

    
548
    actions = get_actions("check")
549
    fabcommand(args, env, actions)
550

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

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

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

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

    
572
  conf = Conf.configure(args.confdir, args.cluster_name, args, args.autoconf)
573
  env = Env(conf)
574

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

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

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

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

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

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

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

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

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

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

    
625

    
626

    
627

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

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

    
639

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

    
646

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