forked from nuagenetworks/nuage-metroae
-
Notifications
You must be signed in to change notification settings - Fork 0
/
run_wizard.py
executable file
·2584 lines (2097 loc) · 95 KB
/
run_wizard.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import getpass
import glob
import os
import re
import subprocess
import sys
import traceback
METROAE_CONTACT = "devops@nuagenetworks.net"
YAML_LIBRARY = "PyYAML==3.13"
WIZARD_SCRIPT = """
- message:
text: |
Thank you for using MetroAE!
This wizard will walk you through the creation or modification
of a MetroAE deployment. We will walk through the process
step-by-step. You will also have the option of doing things
such as verifying that all prerequisites are satisfied,
unzipping Nuage image files, and copying ssh keys to servers.
For assistance please contact: {contact}
- list_steps: {}
description: |
The following steps will be performed:
- step: Verify proper MetroAE installation
description: |
This step will verify that the MetroAE tool has been properly installed
with all required libraries.
verify_install:
missing_msg: |
We would like to run setup to install these. The command is
"sudo ./setup.sh" if you'd like to run it yourself.
Running this command requires sudo access. You may be asked
for the sudo password.
wrong_os_msg: |
The OS is not recognized. MetroAE requires a Linux based operating
system such as CentOS or Ubuntu. A docker container version of MetroAE
is available for other operating system types.
- step: Unzip image files
description: |
This step will unzip the image files needed for the VSP components. The
source zip files can be downloaded from Nokia OLCS:
https://support.alcatel-lucent.com/portal/web/support
For upgrade: Use the directory that contains the image files you are
upgrading to.
unzip_images:
container_msg: |
MetroAE is running in a container. The zipped image files must be placed
under the metroae_data directory mount point of the container. The
mount directory was set during container setup and can be found using
the command "metroae container status". Relative paths must be provided
for both the source and destination directories that are relative to the
metroae_data mount point.
- step: Create/read deployment
description: |
This step will create a deployment or modify an existing one.
A deployment is a configuration set for MetroAE. It is a collection of
files in a deployment directory. The name of the directory is the
name of the deployment. The files in the directory describe the
properties of each component being installed, upgraded, or configured.
MetroAE supports multiple deployments, each in its own directory.
create_deployment: {}
- step: Auto-discover existing components
description: |
This optional step can beb used to discover components that are already
running in your network. By specifying the connection information of
the hypervisors, any VMs running on these systems will be analyzed. You
will be given the option to identify VMs as VSP components and to have
the information discovered automatically entered as deployment data. You
will be given the option to review and modify the information discovered
at a later stage in this wizard.
discover_components: {}
- step: Import CSV Spreadsheet
description: |
This step can optionally import MetroAE configuration via a CSV
spreadsheet. Any components defined in the spreadsheet can be skipped
in the wizard. A template for the spreadsheet can be found in the root
MetroAE directory as "deployment_spreadsheet_template.csv".
import_csv_spreadsheet: {}
- step: Common deployment parameters
description: |
This step will create or modify the common.yml file in your deployment.
This file provides global parameters that are common to all components
in your deployment.
create_common:
dns_setup_msg: |
MetroAE requires that most components have hostname-to-ip address DNS
mappings defined before running workflows. You can complete this
wizard without having DNS setup in your environment, but having DNS
setup before continuing will allow this wizard to auto-discover
component IP addresses.
vsd_fqdn_msg: |
Please enter the Fully Qualified Domain Name (FQDN) for the VSD. If
clustered, use the XMPP FQDN. For standalone, use the FQDN of the VSD.
ntp_setup_msg: |
VSP components require the use of an NTP server so that components being
installed/upgraded can keep their times synchronized.
bridge_setup_msg: |
Network bridges are required on vCenter and KVM target server
hypervisors. A network bridge will be a Distributed Virtual PortGroup
(DVPG) when deploying on vCenter or a Linux network bridge when deploying
on KVM. VSP component interfaces will be connected to these bridges so
that they can communicate with each other and to the outside. MetroAE
will not create the bridges for you. You must create and configure them
ahead of time.
There are up to three bridges that can be defined:
mgmt_bridge: Management network.
data_bridge: Internal data network.
access_bridge: External network.
- step: Setup upgrade parameters
description: |
This step will create or modify the upgrade.yml file in your deployment.
If you will not be doing an upgrade, this step can be skipped.
create_upgrade:
upgrade_msg: |
MetroAE needs the version number that your deployment is currently
running and which version to upgrade to.
- step: VSD deployment file
description: |
This step will create or modify the vsds.yml file in your deployment.
This file provides parameters for the Virtualized Services Directories
(VSDs) in your deployment. This step is only required if you are working
with VSDs in your deployment.
create_component:
schema: vsds
ha_amount: 3
item_name: VSD
upgrade_vmname: true
- step: VSC deployment file
description: |
This step will create or modify the vscs.yml file in your deployment.
This file provides parameters for the Virtualized Services Controllers
(VSCs) in your deployment. This step is only required if you are working
with VSCs in your deployment.
create_component:
schema: vscs
ha_amount: 2
item_name: VSC
upgrade_vmname: false
- step: VSTAT deployment file
description: |
This step will create or modify the vstats.yml file in your deployment.
This file provides parameters for the VSD Statistics (Elasticsearch)
nodes in your deployment. This step is only required if you are working
with VSD Statistics nodes in your deployment.
create_component:
schema: vstats
ha_amount: 3
item_name: VSTAT
upgrade_vmname: true
- step: NUH deployment file
description: |
This step will create or modify the nuhs.yml file in your deployment.
This file provides parameters for the Nuage Utility Host VMs (NUHs) in
your deployment.
create_component:
schema: nuhs
ha_amount: 2
item_name: NUH
upgrade_vmname: false
- step: VNSUtil deployment file
description: |
This step will create or modify the vnsutils.yml file in your deployment.
This file provides parameters for the VNS utility VM (Proxy) nodes
in your deployment. This step is only required if you wish to deploy a
VNS setup.
create_component:
schema: vnsutils
ha_amount: any
item_name: VNSUtil
upgrade_vmname: false
- step: NSGvs deployment file
description: |
This step will create or modify the nsgvs.yml file in your deployment.
This file provides parameters for the virtualized Network Service Gateway
(NSGv) nodes in your deployment. This step is only required if you wish
to deploy a VNS setup.
create_component:
schema: nsgvs
ha_amount: any
item_name: NSGv
upgrade_vmname: false
- step: NSGv bootstrap deployment file
description: |
This step will create or modify the nsgv_bootstrap.yml file in your
deployment. This file provides the global parameters for bootstrapping
your NSGv nodes. This step is only required if you wish
to deploy a VNS setup and you are using metro to perform a zero-factor
bootstrap.
create_bootstrap: {}
- step: Setup SSH on target servers
description: |
This step will setup password-less SSH access to the KVM target servers
(hypervisors) used by your deployment. MetroAE must have password-less
access to all the KVM target servers you will be accessing using this
deployment. If you are not using KVM target servers you may skip this
step.
setup_target_servers:
container_msg: |
MetroAE is running in a container. The SSH key being copied to the
target servers is the key belonging to the container. This is different
than the key on the Docker host. The container SSH key can be found in
your metroae_data mount directory.
- complete_wizard:
problem_msg: |
The following problems have occurred during the wizard. It may
cause MetroAE to function incorrectly. It is recommended that you
correct these and repeat any steps affected.
finish_msg: |
All steps of the wizard have been performed. You can use (b)ack to
repeat previous steps or (q)uit to exit the wizard.
complete_msg: |
The wizard is complete!
install_msg: |
You can issue the following to begin installing your components:
{metro} install everything {deployment}
upgrade_msg: |
You can issue the following to begin an upgrade of your components:
{metro} upgrade everything {deployment}
"""
STANDARD_FIELDS = ["step", "description"]
TARGET_SERVER_TYPE_LABELS = ["(K)vm", "(v)center", "(o)penstack", "(a)ws"]
TARGET_SERVER_TYPE_VALUES = ["kvm", "vcenter", "openstack", "aws"]
COMPONENT_LABELS = ["vs(d)", "vs(c)", "(e)s", "nu(h)", "vns(u)tils", "ns(g)v",
"(n)one of these - skip VM"]
COMPONENT_DEFAULT_LABELS = ["vs(D)", "vs(C)", "(E)s", "nu(H)", "vns(U)tils",
"ns(G)v", "(N)one of these - skip VM"]
COMPONENT_IMAGE_KEYWORDS = ["vsd", "vsc", "elastic", "nuage-utils", "vns-util",
"ncpe"]
COMPONENT_SCHEMAS = ["vsds", "vscs", "vstats", "nuhs", "vnsutils", "nsgvs"]
class Wizard(object):
def __init__(self, script=WIZARD_SCRIPT):
self._set_container()
self._set_directories()
self.state = dict()
self.current_action_idx = 0
self.progress_display_count = 0
self.progress_display_rate = 1
if "NON_INTERACTIVE" in os.environ:
self.args = list(sys.argv)
self.args.pop(0)
else:
self.args = list()
self._import_yaml()
import yaml
self.script = yaml.safe_load(script)
self._validate_actions()
def __call__(self):
self._run_script()
#
# Script Actions
#
def message(self, action, data):
raw_msg = self._get_field(data, "text")
format_msg = raw_msg.format(contact=METROAE_CONTACT)
self._print(format_msg)
def list_steps(self, action, data):
for step in self.script:
if "step" in step:
self._print(" - " + step["step"])
def verify_install(self, action, data):
self._print(u"\nVerifying MetroAE installation")
if self.in_container:
self._print("\nWizard is being run inside a Docker container. "
"No need to verify installation. Skipping step...")
return
if not os.path.isfile("/etc/os-release"):
self._record_problem("wrong_os", "Unsupported operating system")
self._print(self._get_field(data, "wrong_os_msg"))
choice = self._input("Do you wish to continue anyway?", 0,
["(Y)es", "(n)o"])
if choice == 1:
self._print("Quitting wizard...")
exit(0)
missing = self._verify_pip()
yum_missing = self._verify_yum()
missing.extend(yum_missing)
if len(missing) == 0:
self._unrecord_problem("install_libraries")
self._print(u"\nMetroAE Installation OK!")
else:
self._record_problem(
"install_libraries",
u"Your MetroAE installation is missing libraries")
self._print(u"\nYour MetroAE installation is missing libraries:\n")
self._print("\n".join(missing))
self._print(self._get_field(data, "missing_msg"))
choice = self._input("Do you want to run setup now?", 0,
["(Y)es", "(n)o"])
if choice != 1:
self._run_setup()
def unzip_images(self, action, data):
valid = False
while not valid:
if self.in_container:
self._print(self._get_field(data, "container_msg"))
zip_dir = self._input("Please enter the directory relative to "
"the metroae_data mount point that "
"contains your zip files", "")
if zip_dir.startswith("/"):
self._print("\nDirectory must be a relative path.")
continue
full_zip_dir = os.path.join("/metroae_data", zip_dir)
else:
zip_dir = self._input("Please enter the directory that "
"contains your zip files", "")
full_zip_dir = zip_dir
if zip_dir == "" or not os.path.exists(full_zip_dir):
choice = self._input(
"Directory not found. Would you like to skip unzipping",
0, ["(Y)es", "(n)o"])
if choice != 1:
self._print("Skipping unzip step...")
return
elif not os.path.isdir(full_zip_dir):
self._print("%s is not a directory, please enter the directory"
" containing the zipped files" % zip_dir)
else:
valid = True
if self.in_container:
valid = False
while not valid:
unzip_dir = self._input("Please enter the directory relative "
"to the metroae_data mount point to "
"unzip to")
if unzip_dir.startswith("/"):
self._print("\nDirectory must be a relative path.")
else:
valid = True
full_unzip_dir = os.path.join("/metroae_data", unzip_dir)
else:
unzip_dir = self._input("Please enter the directory to unzip to")
full_unzip_dir = unzip_dir
self.state["nuage_unzipped_files_dir"] = unzip_dir
choice = self._input(
"Unzip %s to %s" % (zip_dir, unzip_dir),
0, ["(Y)es", "(n)o"])
if choice == 0:
self._run_unzip(full_zip_dir, full_unzip_dir)
else:
self._print("Skipping unzip step...")
def create_deployment(self, action, data):
valid = False
while not valid:
deployment_name = self._input(
"Please enter the name of the deployment (will be the "
"directory name)", "default")
if "/" in deployment_name:
self._print("\nA deployment name cannot contain a slash "
"because it will be a directory name")
elif " " in deployment_name:
self._print("\nA deployment name can contain a space, but it "
"will always have to be specified with quotes")
choice = self._input("Do you want use it?", 0,
["(Y)es", "(n)o"])
if choice != 1:
valid = True
else:
valid = True
found = False
deployment_dir = os.path.join(self.base_deployment_path,
deployment_name)
if os.path.isdir(deployment_dir):
self._print("\nThe deployment directory was found")
found = True
else:
self._print("")
choice = self._input('Create deployment directory: "%s"?' %
deployment_name,
0, ["(Y)es", "(n)o"])
if choice == 1:
self._print("Skipping deployment creation.")
return
self._print("Deployment directory: " + deployment_dir)
self.state["deployment_name"] = deployment_name
self.state["deployment_dir"] = deployment_dir
if not found:
os.mkdir(deployment_dir)
def discover_components(self, action, data):
choice = 0
another_string = "a"
while choice == 0:
choice = self._input(
"Would you like to try to discover existing components "
"automatically from %s hypervisor?" % another_string, 1,
["(y)es", "(N)o"])
if choice == 1:
return
self._discover_components()
another_string = "another"
def import_csv_spreadsheet(self, action, data):
choice = self._input("Do you have a CSV spreadsheet to import?", 1,
["(y)es", "(N)o"])
if choice == 1:
return
self._import_csv()
def create_common(self, action, data):
if self._check_skip_for_csv("common"):
return
deployment_dir = self._get_deployment_dir()
if deployment_dir is None:
return
deployment_file = os.path.join(deployment_dir, "common.yml")
if os.path.isfile(deployment_file):
deployment = self._read_deployment_file(deployment_file,
is_list=False)
else:
self._print(deployment_file + " not found. It will be created.")
deployment = dict()
self._setup_unzip_dir(deployment)
self._setup_target_server_type()
if self.state["target_server_type"] == "kvm":
self._setup_bridges(deployment, data)
elif self.state["target_server_type"] == "vcenter":
self._setup_vcenter(deployment)
self._setup_dns(deployment, data)
self._setup_ntp(deployment, data)
default = self._get_value(deployment, "vsd_license_file")
if default is None:
default = ""
license = self._input("Path to VSD License file (required only for "
"NSGv bootstrapping)", default)
if license != "":
deployment["vsd_license_file"] = license
self._generate_deployment_file("common", deployment_file, deployment)
def create_upgrade(self, action, data):
if self._check_skip_for_csv("upgrade"):
return
self._print("")
choice = self._input("Will you be performing an upgrade?", 0,
["(Y)es", "(N)o"])
if choice == 1:
if "upgrade" in self.state:
del self.state["upgrade"]
self._print("Skipping step...")
return
self.state["upgrade"] = True
deployment_dir = self._get_deployment_dir()
if deployment_dir is None:
return
deployment_file = os.path.join(deployment_dir, "upgrade.yml")
if os.path.isfile(deployment_file):
deployment = self._read_deployment_file(deployment_file,
is_list=False)
else:
self._print(deployment_file + " not found. It will be created.")
deployment = dict()
self._setup_upgrade(deployment, data)
self._generate_deployment_file("upgrade", deployment_file, deployment)
def create_component(self, action, data):
schema = self._get_field(data, "schema")
item_name = self._get_field(data, "item_name")
is_vsc = (schema == "vscs")
is_nuh = (schema == "nuhs")
is_nsgv = (schema == "nsgvs")
is_vnsutil = (schema == "vnsutils")
if self._check_skip_for_csv(schema):
return
deployment_dir = self._get_deployment_dir()
if deployment_dir is None:
return
deployment_file = os.path.join(deployment_dir, schema + ".yml")
if os.path.isfile(deployment_file):
deployment = self._read_deployment_file(deployment_file,
is_list=True)
else:
self._print(deployment_file + " not found. It will be created.")
deployment = list()
self._setup_target_server_type()
self._print("\nPlease enter your %s deployment type\n" % item_name)
amount = self._get_number_components(deployment, data)
deployment = deployment[0:amount]
if not is_nsgv:
self._print("\nIf DNS is configured properly, IP addresses can be "
"auto-discovered.")
for i in range(amount):
self._print("\n%s %d\n" % (item_name, i + 1))
if len(deployment) == i:
deployment.append(dict())
deployment[i]["target_server_type"] = (
self.state["target_server_type"])
if is_nsgv:
hostname = "nsgv" + str(i + 1)
else:
hostname = self._setup_hostname(deployment, i, item_name)
if is_nsgv or is_vnsutil:
with_upgrade = False
else:
with_upgrade = (self._get_field(data, "upgrade_vmname") and
"upgrade" in self.state)
if not is_nuh:
self._setup_vmname(deployment, i, hostname, with_upgrade)
if not is_nsgv:
self._setup_ip_addresses(deployment, i, hostname, is_vsc)
else:
component = deployment[i]
self._setup_target_server(component)
self._setup_nsgv_component(component)
if is_vnsutil:
component = deployment[i]
self._setup_vnsutils(component, i)
if amount == 0:
if os.path.isfile(deployment_file):
os.remove(deployment_file)
else:
self._generate_deployment_file(schema, deployment_file, deployment)
def create_bootstrap(self, action, data):
if self._check_skip_for_csv("nsgv_bootstrap"):
return
self._print("")
if "metro_bootstrap" not in self.state:
choice = self._input("Will you be using metro to bootstrap NSGvs?",
1, ["(y)es", "(N)o"])
if choice == 1:
self._print("Skipping step...")
return
deployment_dir = self._get_deployment_dir()
if deployment_dir is None:
return
deployment_file = os.path.join(deployment_dir, "nsgv_bootstrap.yml")
if os.path.isfile(deployment_file):
deployment = self._read_deployment_file(deployment_file,
is_list=False)
else:
self._print(deployment_file + " not found. It will be created.")
deployment = dict()
self._setup_bootstrap(deployment, data)
self._generate_deployment_file("nsgv_bootstrap", deployment_file,
deployment)
def setup_target_servers(self, action, data):
if "all_target_servers" in self.state:
servers = self.state["all_target_servers"]
else:
servers = None
while servers is None:
servers = self._input(
"Enter target server (hypervisor) addresses (separate "
"multiple using commas)")
servers = self._format_ip_list(servers)
servers = self._validate_hostname_list(servers)
self.state["all_target_servers"] = servers
if "target_server_username" in self.state:
default = self.state["target_server_username"]
else:
default = "root"
username = self._input(
"Enter the username for the target servers (hypervisors)", default)
self.state["target_server_username"] = username
self._print("\nWe will now configure SSH access to the target servers"
" (hypervisors). This will likely require the SSH"
" password for each system would need to be entered.")
if self.in_container:
self._print(self._get_field(data, "container_msg"))
choice = self._input("Setup SSH now?", 0, ["(Y)es", "(n)o"])
if choice == 1:
self._print("Skipping step...")
return
for server in servers:
self._setup_ssh(username, server)
choice = self._input("Verify SSH connectivity now?", 0,
["(Y)es", "(n)o"])
if choice == 1:
return
for server in servers:
valid = self._verify_ssh(username, server)
if valid and "mgmt_bridge" in self.state:
valid = self._verify_bridge(username, server,
self.state["mgmt_bridge"])
if valid and "data_bridge" in self.state:
valid = self._verify_bridge(username, server,
self.state["data_bridge"])
if valid and "access_bridge" in self.state:
valid = self._verify_bridge(username, server,
self.state["access_bridge"])
def complete_wizard(self, action, data):
if self._has_problems():
self._print(self._get_field(data, "problem_msg"))
self._list_problems()
self._print(self._get_field(data, "finish_msg"))
choice = self._input(None, None, ["(q)uit", "(b)ack"])
if choice == 1:
self.current_action_idx -= 2
return
else:
self._print(self._get_field(data, "complete_msg"))
metro = "./metroae"
if self.in_container:
metro = "metroae"
deployment = ""
if ("deployment_name" in self.state and
self.state["deployment_name"] != "default"):
deployment = self.state["deployment_name"]
if "upgrade" in self.state:
self._print(
self._get_field(data, "upgrade_msg").format(
metro=metro,
deployment=deployment))
else:
self._print(
self._get_field(data, "install_msg").format(
metro=metro,
deployment=deployment))
exit(0)
#
# Private class internals
#
def _print(self, msg):
print msg.encode("utf-8")
def _print_progress(self):
if self.progress_display_count % self.progress_display_rate == 0:
sys.stdout.write(".")
sys.stdout.flush()
self.progress_display_count += 1
def _input(self, prompt=None, default=None, choices=None, datatype=""):
input_prompt = self._get_input_prompt(prompt, default, choices)
value = None
if "NON_INTERACTIVE" in os.environ:
if len(self.args) < 1:
raise Exception(
"Out of args for non-interactive input for %s" %
input_prompt)
user_value = self.args.pop(0)
if prompt is not None:
self._print(prompt)
self._print("From args: " + user_value)
value = self._validate_input(user_value, default,
choices, datatype)
if value is None:
raise Exception("Invalid non-interactive input for %s%s" %
(input_prompt, user_value))
else:
while value is None:
if datatype == "password":
user_value = getpass.getpass(input_prompt)
else:
user_value = raw_input(input_prompt)
value = self._validate_input(user_value, default, choices,
datatype)
return value
def _get_input_prompt(self, prompt=None, default=None, choices=None):
input_prompt = ""
if choices is not None:
input_prompt += "\n".join(choices)
input_prompt += "\n\n"
short_choices = self._get_short_choices(choices, default)
if prompt is not None:
input_prompt += prompt
input_prompt += " "
input_prompt += "[%s]" % ("/".join(short_choices))
else:
default_sep = ""
if prompt is not None:
input_prompt += prompt
default_sep = " "
if default is not None:
input_prompt += "%s[%s]" % (default_sep, default)
input_prompt += ": "
return input_prompt
def _validate_input(self, user_value, default=None, choices=None,
datatype=""):
value = None
if user_value == "":
if default is not None:
return default
else:
self._print("\nRequired field, please enter a value\n")
return None
if choices is not None:
value = self._match_choice(user_value, choices)
if value is None:
self._print(
"\nValue is not a valid choice, please reenter\n")
elif datatype == "ipaddr":
value = self._validate_ipaddr(user_value)
if value is None:
self._print("\nValue is not a valid ipaddress\n")
elif datatype == "int":
try:
value = int(user_value)
except ValueError:
self._print("\nValue is not a valid integer\n")
return None
elif datatype == "hostname":
value = self._validate_hostname(user_value)
if value is None:
self._print("\nValue is not a valid hostname\n")
elif datatype == "version":
allowed = re.compile("^[\d][.][\d][.]([A-Z\d]+)$", re.IGNORECASE)
if not allowed.match(user_value):
self._print("\nValue is not a valid version\n")
return None
value = user_value
else:
value = user_value
return value
def _validate_ipaddr(self, user_value):
try:
import netaddr
try:
netaddr.IPAddress(user_value)
return user_value
except netaddr.core.AddrFormatError:
return None
except ImportError:
self._print("\nWarning: Python netaddr library not installed. "
"Cannot validate IP address. This library is also "
"required for MetroAE to run properly.")
return user_value
def _validate_hostname(self, hostname):
if len(hostname) > 255:
return False
if hostname[-1] == ".":
hostname = hostname[:-1]
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
if all(allowed.match(x) for x in hostname.split(".")):
return hostname
else:
return None
def _get_short_choices(self, choices, default=None):
short_choices = list()
for i, choice in enumerate(choices):
start_paren_idx = choice.find("(")
if start_paren_idx >= 0:
short_value = choice[start_paren_idx + 1]
else:
short_value = choice[0]
if default == i:
short_value = short_value.upper()
else:
short_value = short_value.lower()
short_choices.append(short_value)
return short_choices
def _match_choice(self, user_value, choices):
short_choices = self._get_short_choices(choices)
for i, short_choice in enumerate(short_choices):
if user_value.lower() == short_choice:
return i
return None
def _import_yaml(self):
try:
import yaml
yaml.safe_load("")
except ImportError:
self._install_yaml()
def _install_yaml(self):
self._print("This wizard requires PyYAML library to be installed."
" Running this command requires sudo access. You may be "
"asked for the sudo password.\n")
choice = self._input("Install it now?", 0,
["(Y)es", "(n)o"])
if choice == 1:
self._print("Please install PyYAML and run the wizard again.")
exit(1)
rc, output_lines = self._run_shell("sudo pip install " + YAML_LIBRARY)
if rc != 0:
self._print("\n".join(output_lines))
self._print("Could not install PyYAML, exit code: %d" % rc)
self._print("Please install PyYAML and run the wizard again.")
exit(1)
def _get_value(self, deployment, field):
value = deployment.get(field)
if value == "":
value = None
return value
def _set_container(self):
# For Dalston container
if "RUN_MODE" in os.environ:
self.in_container = (os.environ["RUN_MODE"] == "INSIDE")
else:
self.in_container = False
self.in_container = os.path.isdir("/source/nuage-metro")
def _set_directories(self):
self.metro_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(self.metro_path)
if self.in_container:
self.base_deployment_path = os.path.join("/data",
"deployments")
# Dalston container
self.base_deployment_path = os.path.join("/metroae_data",
"deployments")
else:
self.base_deployment_path = os.path.join(self.metro_path,
"deployments")
def _validate_actions(self):
for action in self.script:
self._validate_action(action)
def _validate_action(self, action):
if type(action) == dict:
action_name = self._get_action_name(action)
if action_name.startswith("_") or not hasattr(self, action_name):
raise Exception(
"Invalid wizard script format - %s not a valid action" %
action_name)
else:
raise Exception("Invalid wizard script format - action not a dict")
def _run_script(self):
self.current_action_idx = 0
while self.current_action_idx < len(self.script):
current_action = self.script[self.current_action_idx]
if "step" in current_action:
self._display_step(current_action)
choice = self._input(None, 0, ["(C)ontinue",
"(b)ack",
"(s)kip",
"(q)uit"])
if choice == 1:
self.current_action_idx -= 1
continue
if choice == 2:
self.current_action_idx += 1
continue
if choice == 3:
self._print("Exiting MetroAE wizard. All progress made "
"has been saved.")
exit(0)
try:
self._run_action(current_action)
except KeyboardInterrupt:
self._print("\n\nInterrupt signal received. All progress made "