Statistics
| Branch: | Tag: | Revision:

root / snf-deploy / snfdeploy / __init__.py @ 59d1ee86

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

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

    
500
    return ret
501

    
502

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

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

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

    
523

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
629

    
630

    
631

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

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

    
643

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

    
650

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