Statistics
| Branch: | Tag: | Revision:

root / snf-deploy / snfdeploy / __init__.py @ 3bae85da

History | View | Annotate | Download (19 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
from snfdeploy import fabfile
17
from fabric.api import hide, env, settings, local, roles, execute, show
18

    
19
def print_available_actions(command):
20

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

25
  Generate new ssh keys (both rsa and dsa keypairs)
26

27
  """
28

    
29
  if command == "vcluster":
30
    print """
31
Usage: snf-deploy vcluster
32

33
  Run the following actions concerning the local virtual cluster:
34

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

40
  """
41

    
42
  if command == "prepare":
43
    print """
44
Usage: snf-deploy prepare
45

46
  Run the following actions concerning deployment preparation:
47

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

54
  """
55

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

60
  Run the following actions concerning a ganeti backend:
61

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

68
    or
69

70
    - Update packages in an already registered backend in cyclades.
71

72
  """
73

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

78
  Run any of the following fabric commands:
79

80

81
    Setup commands:        Init commands:                Admin commands:
82
      setup_apache           add_pools                     activate_user
83
      setup_apt              add_rapi_user                 add_backend
84
      setup_astakos          add_nodes                     add_image_locally
85
      setup_cms              astakos_loaddata              add_network
86
      setup_common           astakos_register_components   add_ns
87
      setup_cyclades         cms_loaddata                  add_user
88
      setup_db               cyclades_loaddata             connect_bridges
89
      setup_ganeti           enable_drbd                   create_bridges
90
      setup_gtools           init_cluster                  create_vlans
91
      setup_gunicorn         setup_nfs_clients             destroy_db
92
      setup_hosts            setup_nfs_server              get_auth_token_from_db
93
      setup_image_helper     update_ns_for_ganeti          get_service_details
94
      setup_image_host                                     gnt_instance_add
95
      setup_iptables                                       gnt_network_add
96
      setup_kamaki         Test commands:                  register_image
97
      setup_lvm              test                          restart_services
98
      setup_mq                                             setup_drbd_dparams
99
      setup_net_infra
100
      setup_network
101
      setup_ns
102
      setup_pithos
103
      setup_pithos_dir
104
      setup_router
105
      setup_vncauthproxy
106
      setup_webproject
107

108
  """
109

    
110
  sys.exit(1)
111

    
112

    
113
def create_dnsmasq_files(args, env):
114

    
115
  print("Customize dnsmasq..")
116
  out = env.dns
117

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

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

    
126
  hostsfile.write("52:54:56:*:*:*,ignore\n")
127

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

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

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

    
159
  conffile.write(dnsconf)
160

    
161
  hostsfile.close()
162
  optsfile.close()
163
  conffile.close()
164

    
165

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

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

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

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

    
193

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

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

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

    
214

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

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

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

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

    
238

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

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

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

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

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

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

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

    
281

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

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

    
289

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

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

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

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

    
311

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

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

    
332

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

    
338

    
339
def get_packages(args, env):
340
  if env.package_url:
341
    os.system("rm {0}/*.deb".format(env.packages))
342
    os.system("wget -r --level=1 -nH --no-parent --cut-dirs=4 {0} -P {1}".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 console or not")
375
  parser.add_argument("--force", dest="force",
376
                      default=False, action="store_true",
377
                      help="Force the creation of new ssh key pairs")
378

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

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

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

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

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

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

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

    
413
  return parser.parse_args()
414

    
415

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

    
484
    ret = []
485
    for x in args:
486
      ret += actions[x]
487

    
488
    return ret
489

    
490

    
491
def must_create_keys(force, env):
492
    """Check if we need to create ssh keys
493

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

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

    
511

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

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

    
540
    actions = get_actions("check")
541
    fabcommand(args, env, actions)
542

    
543
    actions = [
544
      "setup_nfs_clients",
545
      "setup_ganeti",
546
      "setup_image_host", "setup_image_helper", "setup_network", "setup_gtools",
547
      ]
548
    fabcommand(args, env, actions, [args.cluster_node])
549

    
550
    actions = [
551
      "add_node:" + args.cluster_node,
552
      ]
553
    fabcommand(args, env, actions)
554

    
555
    actions = [
556
      "setup_lvm", "enable_drbd",
557
      "setup_net_infra", "setup_iptables",
558
      ]
559
    fabcommand(args, env, actions, [args.cluster_node])
560

    
561
def main():
562
  args = parse_options()
563

    
564
  conf = Conf(args)
565
  env = Env(conf)
566

    
567
  create_dir(env.run, False)
568
  create_dir(env.dns, False)
569

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

    
584
  if args.command == "test":
585
    conf.print_config()
586

    
587
  if args.command == "cleanup":
588
    cleanup(args, env)
589

    
590
  if args.command == "packages":
591
    create_dir(env.packages, True)
592
    get_packages(args, env)
593

    
594
  if args.command == "vcluster":
595
    image(args, env)
596
    network(args, env)
597
    create_dnsmasq_files(args, env)
598
    dnsmasq(args, env)
599
    cluster(args, env)
600

    
601
  if args.command == "prepare":
602
    actions = get_actions("prepare")
603
    fabcommand(args, env, actions)
604

    
605
  if args.command == "synnefo":
606
    actions = get_actions("synnefo")
607
    fabcommand(args, env, actions)
608

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

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

    
617

    
618

    
619

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

    
624
  if args.command == "add":
625
    if args.cluster_node:
626
      add_node(args, env)
627
    else:
628
      actions = get_actions("backend")
629
      fabcommand(args, env, actions)
630

    
631

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

    
638

    
639
if __name__ == "__main__":
640
  sys.exit(main())