Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (17.7 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 == "vcluster":
20
    print """
21
Usage: snf-deploy vcluster
22

23
  Run the following actions concerning the local virtual cluster:
24

25
    - Download base image and create additional disk (if --create-extra-disk is passed)
26
    - Does all the network related actions (bridge, iptables, NAT)
27
    - Launches dnsmasq for dhcp server on bridge
28
    - Creates the virtual cluster (with kvm)
29

30
  """
31

    
32
  if command == "prepare":
33
    print """
34
Usage: snf-deploy prepare
35

36
  Run the following actions concerning deployment preparation:
37

38
    - Setup an internal Domain Name Server
39
    - Tweak hosts and add ssh keys
40
    - Check network setup
41
    - Setup apt repository and apt-get update
42
    - Setup the nfs server and clients among all nodes
43

44
  """
45

    
46
  if command == "backend":
47
    print """
48
Usage: snf-deploy backend [update]
49

50
  Run the following actions concerning a ganeti backend:
51

52
    - Create and add a backend to cyclades
53
    - Does all the net-infra specific actions in backend nodes
54
      (create/connect bridges, iptables..)
55
    - Does all the storage-infra specific actions in backend nodes
56
      depending on the --extra-disk option (create VG, enable lvm/drbd storage..)
57

58
    or
59

60
    - Update packages in an already registered backend in cyclades.
61

62
  """
63

    
64
  if command == "run":
65
    print """
66
Usage: snf-deploy run <action> [<action>...]
67

68
  Run any of the following fabric commands:
69

70

71
    Setup commands:        Init commands:                Admin commands:
72
      setup_apache           add_pools                     activate_user
73
      setup_apt              add_rapi_user                 add_backend
74
      setup_astakos          add_nodes                     add_image_locally
75
      setup_cms              astakos_loaddata              add_network
76
      setup_common           astakos_register_services     add_ns
77
      setup_cyclades         cms_loaddata                  add_user
78
      setup_db               cyclades_loaddata             connect_bridges
79
      setup_ganeti           enable_drbd                   create_bridges
80
      setup_gtools           init_cluster                  create_vlans
81
      setup_gunicorn         setup_nfs_clients             destroy_db
82
      setup_hosts            setup_nfs_server              get_auth_token_from_db
83
      setup_image_helper     update_ns_for_ganeti          get_service_details
84
      setup_image_host                                     gnt_instance_add
85
      setup_iptables                                       gnt_network_add
86
      setup_kamaki         Test commands:                  register_image
87
      setup_lvm              test                          restart_services
88
      setup_mq                                             setup_drbd_dparams
89
      setup_net_infra
90
      setup_network
91
      setup_ns
92
      setup_pithos
93
      setup_pithos_dir
94
      setup_router
95
      setup_vncauthproxy
96
      setup_webproject
97

98
  """
99

    
100
  sys.exit(1)
101

    
102

    
103
def create_dnsmasq_files(args, env):
104

    
105
  print("Customize dnsmasq..")
106
  out = env.dns
107

    
108
  hostsfile = open(out + "/dhcp-hostsfile", "w")
109
  optsfile = open(out + "/dhcp-optsfile", "w")
110
  conffile = open(out + "/conf-file", "w")
111

    
112
  for node, info in env.nodes_info.iteritems():
113
    # serve ip and hostname to nodes
114
    hostsfile.write("%s,%s,%s,2m\n" % (info.mac, info.ip, info.hostname))
115

    
116
  hostsfile.write("52:54:56:*:*:*,ignore\n")
117

    
118
  # Netmask
119
  optsfile.write("1,%s\n" % env.net.netmask)
120
  # Gateway
121
  optsfile.write("3,%s\n" % env.gateway)
122
  # Namesevers
123
  optsfile.write("6,%s\n" % "8.8.8.8")
124

    
125
  dnsconf = """
126
user=dnsmasq
127
bogus-priv
128
no-poll
129
no-negcache
130
leasefile-ro
131
bind-interfaces
132
except-interface=lo
133
dhcp-fqdn
134
no-resolv
135
# disable DNS
136
port=0
137
""".format(env.ns.ip)
138

    
139
  dnsconf += """
140
# serve domain and search domain for resolv.conf
141
domain={5}
142
interface={0}
143
dhcp-hostsfile={1}
144
dhcp-optsfile={2}
145
dhcp-range={0},{4},static,2m
146
""".format(env.bridge, hostsfile.name, optsfile.name,
147
           env.domain, env.net.network, env.domain)
148

    
149
  conffile.write(dnsconf)
150

    
151
  hostsfile.close()
152
  optsfile.close()
153
  conffile.close()
154

    
155

    
156
def cleanup(args, env):
157
  print("Cleaning up bridge, NAT, resolv.conf...")
158

    
159
  for f in os.listdir(env.run):
160
    if re.search(".pid$", f):
161
      check_pidfile(os.path.join(env.run, f))
162

    
163
  create_dir(env.run, True)
164
  # create_dir(env.cmd, True)
165
  cmd = """
166
  iptables -t nat -D POSTROUTING -s {0} -o {1} -j MASQUERADE
167
  echo 0 > /proc/sys/net/ipv4/ip_forward
168
  iptables -D INPUT -i {2} -j ACCEPT
169
  iptables -D FORWARD -i {2} -j ACCEPT
170
  iptables -D OUTPUT -o {2} -j ACCEPT
171
  """.format(env.subnet, get_default_route()[1], env.bridge)
172
  os.system(cmd)
173

    
174
  cmd = """
175
  ip link show {0} && ip addr del {1}/{2} dev {0}
176
  sleep 1
177
  ip link set {0} down
178
  sleep 1
179
  brctl delbr {0}
180
  """.format(env.bridge, env.gateway, env.net.prefixlen)
181
  os.system(cmd)
182

    
183

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

    
187
  cmd = """
188
  ! ip link show {0} && brctl addbr {0} && ip link set {0} up
189
  sleep 1
190
  ip link set promisc on dev {0}
191
  ip addr add {1}/{2} dev {0}
192
  """.format(env.bridge, env.gateway, env.net.prefixlen)
193
  os.system(cmd)
194

    
195
  cmd = """
196
  iptables -t nat -A POSTROUTING -s {0} -o {1} -j MASQUERADE
197
  echo 1 > /proc/sys/net/ipv4/ip_forward
198
  iptables -I INPUT 1 -i {2} -j ACCEPT
199
  iptables -I FORWARD 1 -i {2} -j ACCEPT
200
  iptables -I OUTPUT 1 -o {2} -j ACCEPT
201
  """.format(env.subnet, get_default_route()[1], env.bridge)
202
  os.system(cmd)
203

    
204

    
205
def image(args, env):
206
  if env.os == "ubuntu":
207
    url = env.ubuntu_image_url
208
  else:
209
    url = env.squeeze_image_url
210

    
211
  disk0 = "{0}/{1}.disk0".format(env.images, env.os)
212
  disk1 = "{0}/{1}.disk1".format(env.images, env.os)
213

    
214
  if url and not os.path.exists(disk0):
215
    cmd = "wget {0} -O {1}".format(url, disk0)
216
    os.system(cmd)
217

    
218
  if ast.literal_eval(env.create_extra_disk) and not os.path.exists(disk1):
219
    if env.lvg:
220
      cmd = "lvcreate -L30G -n{0}.disk1 {1}".format(env.os, env.lvg)
221
      os.system(cmd)
222
      cmd = "ln -s /dev/{0}/{1}.disk1 {2}".format(env.lvg, env.os, disk1)
223
      os.system(cmd)
224
    else:
225
      cmd = "dd if=/dev/zero of={0} bs=10M count=3000".format(disk1)
226
      os.system(cmd)
227

    
228

    
229
def fabcommand(args, env, actions, nodes=[]):
230
  levels = ["status", "aborts", "warnings", "running",
231
            "stdout", "stderr", "user", "debug"]
232

    
233
  level_aliases = {
234
    "output": ["stdout", "stderr"],
235
    "everything": ["warnings", "running", "user", "output"]
236
    }
237

    
238
  hide = ",".join(level_aliases["everything"])
239
  show = None
240

    
241
  if args.verbose == 1:
242
    show = ",".join(levels[:3])
243
    hide = ",".join(levels[3:])
244
  elif args.verbose == 2:
245
    show = ",".join(levels[:4])
246
    hide = ",".join(levels[4:])
247
  elif args.verbose >= 3 or args.debug:
248
    show = ",".join(levels)
249
    hide = None
250

    
251
  if args.ssh_key:
252
    fabcmd = "fab -i %s " % args.ssh_key
253
  else:
254
    fabcmd = "fab "
255

    
256
  fabcmd += " --fabfile {4}/fabfile.py \
257
setup_env:confdir={0},packages={1},templates={2},cluster_name={3},\
258
autoconf={5},disable_colors={6},key_inject={7} \
259
".format(args.confdir, env.packages, env.templates, args.cluster_name,
260
         env.lib, args.autoconf, args.disable_colors, args.key_inject)
261

    
262
  if nodes:
263
    hosts = [env.nodes_info[n].hostname for n in nodes]
264
    actions = [a + ':hosts="%s"' % ";".join(hosts) for a in actions]
265

    
266
  extra = " ".join(actions)
267

    
268
  fabcmd += extra
269

    
270
  if show:
271
    fabcmd += " --show %s " % show
272
  if hide:
273
    fabcmd += " --hide %s " % hide
274

    
275
  # print("snf-deploy run " + " ".join(actions) + " -vvv")
276
  print(fabcmd)
277

    
278
  if not args.dry_run:
279
    os.system(fabcmd)
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("-k", "--keygen", dest="keygen",
376
                      default=False, action="store_true",
377
                      help="Whether to create 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"],
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", "astakos_register_services",
438
        "setup_cms", "cms_loaddata",
439
        "setup_pithos",
440
        "setup_cyclades", "cyclades_loaddata", "add_pools", "setup_vncauthproxy",
441
        "setup_kamaki", "upload_image", "register_image",
442
        "setup_burnin"
443
        ],
444
      "supdate": [
445
        "apt_get_update", "setup_astakos",
446
        "setup_cms", "setup_pithos", "setup_cyclades"
447
        ],
448
      # backend actions
449
      "backend": [
450
        "update_ns_for_ganeti",
451
        "setup_ganeti", "init_cluster",
452
        "add_rapi_user", "add_nodes",
453
        "setup_image_host", "setup_image_helper",
454
        "setup_network",
455
        "setup_gtools", "add_backend", "add_network",
456
        "setup_lvm", "enable_lvm",
457
        "enable_drbd", "setup_drbd_dparams",
458
        "setup_net_infra", "setup_iptables", "setup_router",
459
        ],
460
      "bstorage": [
461
        "setup_lvm", "enable_lvm",
462
        "enable_drbd", "setup_drbd_dparams"
463
        ],
464
      "bnetwork": ["setup_net_infra", "setup_iptables", "setup_router"],
465
      "bupdate": [
466
        "apt_get_update", "setup_ganeti", "setup_image_host", "setup_image_helper",
467
        "setup_network", "setup_gtools"
468
        ],
469
      # ganeti actions
470
      "ganeti": [
471
        "update_ns_for_ganeti",
472
        "setup_ganeti", "init_cluster", "add_nodes",
473
        "setup_image_host", "setup_image_helper", "add_image_locally",
474
        "debootstrap", "setup_net_infra",
475
        "setup_lvm", "enable_lvm", "enable_drbd", "setup_drbd_dparams",
476
        ],
477
      "gupdate": ["setup_apt", "setup_ganeti"],
478
      "gdestroy": ["destroy_cluster"],
479
      }
480

    
481
    ret = []
482
    for x in args:
483
      ret += actions[x]
484

    
485
    return ret
486

    
487

    
488
def create_keys(args, env):
489
  d = os.path.join(env.templates, "root/.ssh")
490
  a = os.path.join(d, "authorized_keys")
491
  for t in ("dsa", "rsa"):
492
    f = os.path.join(d, "id_" + t)
493
    cmd = 'ssh-keygen -q -t {0} -f {1} -N ""'.format(t, f)
494
    os.system(cmd)
495
    cmd = 'cat {0}.pub >> {1}'.format(f, a)
496
    os.system(cmd)
497

    
498
def add_node(args, env):
499
    actions = [
500
      "update_ns_for_node:" + args.cluster_node,
501
      ]
502
    fabcommand(args, env, actions)
503
    actions = [
504
      "setup_resolv_conf",
505
      "apt_get_update",
506
      "setup_apt",
507
      "setup_hosts",
508
      "add_keys",
509
      ]
510
    fabcommand(args, env, actions, [args.cluster_node])
511

    
512
    actions = get_actions("check")
513
    fabcommand(args, env, actions)
514

    
515
    actions = [
516
      "setup_nfs_clients",
517
      "setup_ganeti",
518
      "setup_image_host", "setup_image_helper", "setup_network", "setup_gtools",
519
      ]
520
    fabcommand(args, env, actions, [args.cluster_node])
521

    
522
    actions = [
523
      "add_node:" + args.cluster_node,
524
      ]
525
    fabcommand(args, env, actions)
526

    
527
    actions = [
528
      "setup_lvm", "enable_drbd",
529
      "setup_net_infra", "setup_iptables",
530
      ]
531
    fabcommand(args, env, actions, [args.cluster_node])
532

    
533
def main():
534
  args = parse_options()
535

    
536
  conf = Conf.configure(args.confdir, args.cluster_name, args, args.autoconf)
537
  env = Env(conf)
538

    
539
  create_dir(env.run, False)
540
  create_dir(env.dns, False)
541

    
542
  if args.command == "test":
543
    conf.print_config()
544

    
545
  if args.command == "cleanup":
546
    cleanup(args, env)
547

    
548
  if args.keygen:
549
    create_keys(args, env)
550

    
551
  if args.command == "packages":
552
    create_dir(env.packages, True)
553
    get_packages(args, env)
554

    
555
  if args.command == "vcluster":
556
    image(args, env)
557
    network(args, env)
558
    create_dnsmasq_files(args, env)
559
    dnsmasq(args, env)
560
    cluster(args, env)
561

    
562
  if args.command == "prepare":
563
    actions = get_actions("prepare")
564
    fabcommand(args, env, actions)
565

    
566
  if args.command == "synnefo":
567
    actions = get_actions("synnefo")
568
    fabcommand(args, env, actions)
569

    
570
  if args.command == "backend":
571
    actions = get_actions("backend")
572
    fabcommand(args, env, actions)
573

    
574
  if args.command == "ganeti":
575
    actions += get_actions("ganeti")
576
    fabcommand(args, env, actions)
577

    
578

    
579

    
580

    
581
  if args.command == "all":
582
    actions = get_actions("prepare", "synnefo", "backend")
583
    fabcommand(args, env, actions)
584

    
585
  if args.command == "add":
586
    if args.cluster_node:
587
      add_node(args, env)
588
    else:
589
      actions = get_actions("backend")
590
      fabcommand(args, env, actions)
591

    
592

    
593
  if args.command == "run":
594
    if not args.actions:
595
      print_available_actions(args.command)
596
    else:
597
      fabcommand(args, env, args.actions)
598

    
599

    
600
if __name__ == "__main__":
601
  sys.exit(main())