morpheus-cli 9.0.1 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/affinity_groups_interface.rb +46 -0
- data/lib/morpheus/api/api_client.rb +17 -0
- data/lib/morpheus/api/clouds_interface.rb +42 -0
- data/lib/morpheus/api/clusters_interface.rb +55 -1
- data/lib/morpheus/api/distributed_workers_interface.rb +41 -0
- data/lib/morpheus/api/host_vm_groups_interface.rb +37 -0
- data/lib/morpheus/api/servers_interface.rb +8 -0
- data/lib/morpheus/api/virtual_switches_interface.rb +9 -0
- data/lib/morpheus/cli/commands/affinity_groups_command.rb +554 -0
- data/lib/morpheus/cli/commands/backups_command.rb +11 -0
- data/lib/morpheus/cli/commands/clouds.rb +421 -29
- data/lib/morpheus/cli/commands/clusters.rb +507 -20
- data/lib/morpheus/cli/commands/distributed_workers_command.rb +282 -0
- data/lib/morpheus/cli/commands/host_vm_groups_command.rb +423 -0
- data/lib/morpheus/cli/commands/hosts.rb +72 -1
- data/lib/morpheus/cli/commands/instances.rb +12 -1
- data/lib/morpheus/cli/commands/networks_command.rb +35 -3
- data/lib/morpheus/cli/commands/virtual_switches_command.rb +336 -0
- data/lib/morpheus/cli/mixins/affinity_helper.rb +118 -0
- data/lib/morpheus/cli/mixins/infrastructure_helper.rb +143 -4
- data/lib/morpheus/cli/option_types.rb +36 -1
- data/lib/morpheus/cli/version.rb +1 -1
- data/test/api/affinity_groups_interface_test.rb +43 -0
- data/test/api/clouds_interface_test.rb +67 -0
- data/test/api/host_vm_groups_interface_test.rb +35 -0
- data/test/cli/affinity_groups_cloud_cluster_scope_test.rb +270 -0
- data/test/cli/affinity_groups_cluster_scope_test.rb +171 -0
- data/test/cli/affinity_groups_test.rb +57 -0
- data/test/cli/affinity_groups_violations_scope_test.rb +141 -0
- data/test/cli/affinity_helper_test.rb +367 -0
- data/test/cli/backups_test.rb +212 -0
- data/test/cli/clusters_uuid_test.rb +131 -0
- data/test/cli/distributed_workers_test.rb +68 -0
- data/test/cli/host_vm_groups_cluster_scope_test.rb +187 -0
- data/test/cli/host_vm_groups_test.rb +63 -0
- data/test/cli/infrastructure_helper_cluster_resolver_test.rb +250 -0
- metadata +39 -2
|
@@ -5,6 +5,7 @@ class Morpheus::Cli::NetworksCommand
|
|
|
5
5
|
include Morpheus::Cli::WhoamiHelper
|
|
6
6
|
include Morpheus::Cli::InfrastructureHelper
|
|
7
7
|
include Morpheus::Cli::ProvisioningHelper
|
|
8
|
+
include Morpheus::Cli::AccountsHelper
|
|
8
9
|
|
|
9
10
|
set_command_name :networks
|
|
10
11
|
|
|
@@ -28,6 +29,8 @@ class Morpheus::Cli::NetworksCommand
|
|
|
28
29
|
@groups_interface = @api_client.groups
|
|
29
30
|
@clouds_interface = @api_client.clouds
|
|
30
31
|
@options_interface = @api_client.options
|
|
32
|
+
@accounts_interface = @api_client.accounts
|
|
33
|
+
@account_users_interface = @api_client.account_users
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
def handle(args)
|
|
@@ -57,6 +60,13 @@ class Morpheus::Cli::NetworksCommand
|
|
|
57
60
|
opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
|
|
58
61
|
add_query_parameter(params, 'allLabels', parse_labels(val))
|
|
59
62
|
end
|
|
63
|
+
opts.on('--include-tenants','--include-tenants', "Include sub tenant networks") do
|
|
64
|
+
options[:include_tenants] = true
|
|
65
|
+
params['includeTenants'] = true
|
|
66
|
+
end
|
|
67
|
+
opts.on('--tenant TENANT', String, "Tenant Name or ID" ) do |val|
|
|
68
|
+
options[:tenant] = val
|
|
69
|
+
end
|
|
60
70
|
build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :json, :dry_run, :remote])
|
|
61
71
|
opts.footer = "List networks."
|
|
62
72
|
end
|
|
@@ -73,6 +83,18 @@ class Morpheus::Cli::NetworksCommand
|
|
|
73
83
|
return 1 if cloud.nil?
|
|
74
84
|
params['zoneId'] = cloud['id']
|
|
75
85
|
end
|
|
86
|
+
if options[:tenant]
|
|
87
|
+
if options[:tenant].to_s !~ /\A\d{1,}\Z/
|
|
88
|
+
account = find_account_by_name_or_id(options[:tenant])
|
|
89
|
+
if account.nil?
|
|
90
|
+
return 1, "Tenant not found by name: #{options[:tenant]}"
|
|
91
|
+
else
|
|
92
|
+
params['tenantId'] = account['id']
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
params['tenantId'] = options[:tenant]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
76
98
|
@networks_interface.setopts(options)
|
|
77
99
|
if options[:dry_run]
|
|
78
100
|
print_dry_run @networks_interface.dry.list(params)
|
|
@@ -109,6 +131,7 @@ class Morpheus::Cli::NetworksCommand
|
|
|
109
131
|
type: network['type'] ? network['type']['name'] : '',
|
|
110
132
|
group: network['group'] ? network['group']['name'] : 'Shared',
|
|
111
133
|
cloud: network['zone'] ? network['zone']['name'] : '',
|
|
134
|
+
tenant: network['owner'] ? network['owner']['name'] : '',
|
|
112
135
|
cidr: network['cidr'],
|
|
113
136
|
pool: network['pool'] ? network['pool']['name'] : '',
|
|
114
137
|
dhcp: network['dhcpServer'] ? 'Yes' : 'No',
|
|
@@ -128,6 +151,7 @@ class Morpheus::Cli::NetworksCommand
|
|
|
128
151
|
type: "Subnet",
|
|
129
152
|
group: network['group'] ? network['group']['name'] : 'Shared',
|
|
130
153
|
cloud: network['zone'] ? network['zone']['name'] : '',
|
|
154
|
+
tenant: network['owner'] ? network['owner']['name'] : '',
|
|
131
155
|
cidr: subnet['cidr'],
|
|
132
156
|
pool: subnet['pool'] ? subnet['pool']['name'] : '',
|
|
133
157
|
dhcp: subnet['dhcpServer'] ? 'Yes' : 'No',
|
|
@@ -139,9 +163,13 @@ class Morpheus::Cli::NetworksCommand
|
|
|
139
163
|
end
|
|
140
164
|
end
|
|
141
165
|
end
|
|
142
|
-
columns = [:id, :name, :labels, :type, :group, :cloud, :cidr, :pool, :dhcp, :subnets, :active, :visibility, :tenants]
|
|
166
|
+
columns = [:id, :name, :labels, :type, :group, :cloud, :tenant, :cidr, :pool, :dhcp, :subnets, :active, :visibility, :tenants]
|
|
143
167
|
if options[:include_fields]
|
|
144
168
|
columns = options[:include_fields]
|
|
169
|
+
else
|
|
170
|
+
if !options[:include_tenants] && !options[:tenant]
|
|
171
|
+
columns.delete(:tenant)
|
|
172
|
+
end
|
|
145
173
|
end
|
|
146
174
|
print cyan
|
|
147
175
|
print as_pretty_table(rows, columns, options)
|
|
@@ -160,6 +188,9 @@ class Morpheus::Cli::NetworksCommand
|
|
|
160
188
|
options = {}
|
|
161
189
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
162
190
|
opts.banner = subcommand_usage("[network]")
|
|
191
|
+
opts.on('--include-tenants','--include-tenants', "Include sub tenant networks when finding network by name") do
|
|
192
|
+
options[:include_tenants] = true
|
|
193
|
+
end
|
|
163
194
|
build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
|
|
164
195
|
opts.footer = "Get details about a network." + "\n" +
|
|
165
196
|
"[network] is required. This is the name or id of a network."
|
|
@@ -177,11 +208,11 @@ class Morpheus::Cli::NetworksCommand
|
|
|
177
208
|
if args[0].to_s =~ /\A\d{1,}\Z/
|
|
178
209
|
print_dry_run @networks_interface.dry.get(args[0].to_i)
|
|
179
210
|
else
|
|
180
|
-
print_dry_run @networks_interface.dry.list({name:args[0]})
|
|
211
|
+
print_dry_run @networks_interface.dry.list({name:args[0]}, options[:include_tenants])
|
|
181
212
|
end
|
|
182
213
|
return
|
|
183
214
|
end
|
|
184
|
-
network = find_network_by_name_or_id(args[0])
|
|
215
|
+
network = find_network_by_name_or_id(args[0], options[:include_tenants])
|
|
185
216
|
return 1 if network.nil?
|
|
186
217
|
json_response = {'network' => network} # skip redundant request
|
|
187
218
|
# json_response = @networks_interface.get(network['id'])
|
|
@@ -206,6 +237,7 @@ class Morpheus::Cli::NetworksCommand
|
|
|
206
237
|
"Type" => lambda {|it| it['type'] ? it['type']['name'] : '' },
|
|
207
238
|
"Group" => lambda {|it| it['group'] ? it['group']['name'] : 'Shared' },
|
|
208
239
|
"Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
|
|
240
|
+
"Tenant" => lambda {|it| it['owner'] ? it['owner']['name'] : '' },
|
|
209
241
|
"IPv4 Enabled" => 'ipv4Enabled',
|
|
210
242
|
"IPv6 Enabled" => 'ipv6Enabled',
|
|
211
243
|
"CIDR" => 'cidr',
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
|
2
|
+
|
|
3
|
+
# CLI command Virtual Switch management
|
|
4
|
+
# UI is Tools: Virtual Switches
|
|
5
|
+
# API is /api/virtual-switches and returns virtualSwitches
|
|
6
|
+
class Morpheus::Cli::VirtualSwitchesCommand
|
|
7
|
+
include Morpheus::Cli::CliCommand
|
|
8
|
+
include Morpheus::Cli::OptionSourceHelper
|
|
9
|
+
|
|
10
|
+
set_command_name :'virtual-switches'
|
|
11
|
+
set_command_description "View and manage Virtual Switches"
|
|
12
|
+
set_command_hidden
|
|
13
|
+
register_subcommands :list, :get, :add, :update, :remove
|
|
14
|
+
|
|
15
|
+
def connect(opts)
|
|
16
|
+
@api_client = establish_remote_appliance_connection(opts)
|
|
17
|
+
@virtual_switches_interface = @api_client.virtual_switches
|
|
18
|
+
@clusters_interface = @api_client.clusters
|
|
19
|
+
@networks_interface = @api_client.networks
|
|
20
|
+
@options_interface = @api_client.options
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def handle(args)
|
|
25
|
+
handle_subcommand(args)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def list(args)
|
|
29
|
+
options = {}
|
|
30
|
+
params = {}
|
|
31
|
+
ref_ids = []
|
|
32
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
33
|
+
opts.banner = subcommand_usage("[search]")
|
|
34
|
+
opts.on( '--enabled [on|off]', String, "Filter by enabled" ) do |val|
|
|
35
|
+
params['enabled'] = (val.to_s != 'false' && val.to_s != 'off')
|
|
36
|
+
end
|
|
37
|
+
build_standard_list_options(opts, options)
|
|
38
|
+
opts.footer = "List Virtual Switches."
|
|
39
|
+
end
|
|
40
|
+
optparse.parse!(args)
|
|
41
|
+
connect(options)
|
|
42
|
+
if args.count > 0
|
|
43
|
+
options[:phrase] = args.join(" ")
|
|
44
|
+
end
|
|
45
|
+
params.merge!(parse_list_options(options))
|
|
46
|
+
@virtual_switches_interface.setopts(options)
|
|
47
|
+
if options[:dry_run]
|
|
48
|
+
print_dry_run @virtual_switches_interface.dry.list(params)
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
json_response = @virtual_switches_interface.list(params)
|
|
52
|
+
render_response(json_response, options, virtual_switch_list_key) do
|
|
53
|
+
virtual_switches = json_response[virtual_switch_list_key]
|
|
54
|
+
print_h1 "Morpheus Virtual Switches", parse_list_subtitles(options), options
|
|
55
|
+
if virtual_switches.empty?
|
|
56
|
+
print cyan,"No Virtual Switches found.",reset,"\n"
|
|
57
|
+
else
|
|
58
|
+
columns = {
|
|
59
|
+
"ID" => 'id',
|
|
60
|
+
"Name" => 'name',
|
|
61
|
+
"Type" => lambda {|it| it['type']['name'] rescue it['baseType'] },
|
|
62
|
+
"NIC Count" => lambda {|it| it['nicCount'] },
|
|
63
|
+
"Network Count" => lambda {|it| it['networkCount'] },
|
|
64
|
+
}
|
|
65
|
+
print as_pretty_table(virtual_switches, columns.upcase_keys!, options)
|
|
66
|
+
print_results_pagination(json_response)
|
|
67
|
+
end
|
|
68
|
+
print reset,"\n"
|
|
69
|
+
end
|
|
70
|
+
return 0, nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def get(args)
|
|
74
|
+
params = {}
|
|
75
|
+
options = {}
|
|
76
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
77
|
+
opts.banner = subcommand_usage("[switch]")
|
|
78
|
+
build_standard_get_options(opts, options)
|
|
79
|
+
opts.footer = <<-EOT
|
|
80
|
+
Get details about a specific Virtual Switch.
|
|
81
|
+
[switch] is required. This is the name or id of a Virtual Switch.
|
|
82
|
+
EOT
|
|
83
|
+
end
|
|
84
|
+
optparse.parse!(args)
|
|
85
|
+
verify_args!(args:args, optparse:optparse, min:1)
|
|
86
|
+
connect(options)
|
|
87
|
+
params.merge!(parse_query_options(options))
|
|
88
|
+
id_list = parse_id_list(args)
|
|
89
|
+
return run_command_for_each_arg(id_list) do |arg|
|
|
90
|
+
_get(arg, params, options)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def _get(id, params, options)
|
|
95
|
+
virtual_switch = nil
|
|
96
|
+
if id.to_s !~ /\A\d{1,}\Z/
|
|
97
|
+
virtual_switch = find_virtual_switch_by_name(id)
|
|
98
|
+
return 1, "Virtual Switch not found for #{id}" if virtual_switch.nil?
|
|
99
|
+
id = virtual_switch['id']
|
|
100
|
+
end
|
|
101
|
+
@virtual_switches_interface.setopts(options)
|
|
102
|
+
if options[:dry_run]
|
|
103
|
+
print_dry_run @virtual_switches_interface.dry.get(id, params)
|
|
104
|
+
return
|
|
105
|
+
end
|
|
106
|
+
json_response = @virtual_switches_interface.get(id, params)
|
|
107
|
+
virtual_switch = json_response[virtual_switch_object_key]
|
|
108
|
+
render_response(json_response, options, virtual_switch_object_key) do
|
|
109
|
+
print_h1 "Virtual Switch Details", [], options
|
|
110
|
+
print cyan
|
|
111
|
+
columns = {
|
|
112
|
+
"ID" => 'id',
|
|
113
|
+
"Name" => 'name',
|
|
114
|
+
"Type" => lambda {|it| it['type']['name'] rescue it['baseType'] },
|
|
115
|
+
"NIC Count" => lambda {|it| it['nicCount'] },
|
|
116
|
+
"Network Count" => lambda {|it| it['networkCount'] },
|
|
117
|
+
}
|
|
118
|
+
print_description_list(columns, virtual_switch, options)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
print reset,"\n"
|
|
122
|
+
end
|
|
123
|
+
return 0, nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def add(args)
|
|
127
|
+
options = {}
|
|
128
|
+
params = {}
|
|
129
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
130
|
+
opts.banner = subcommand_usage("[name] [options]")
|
|
131
|
+
build_option_type_options(opts, options, add_virtual_switch_option_types)
|
|
132
|
+
build_standard_add_options(opts, options)
|
|
133
|
+
opts.footer = <<-EOT
|
|
134
|
+
Create a new Virtual Switch.
|
|
135
|
+
EOT
|
|
136
|
+
end
|
|
137
|
+
optparse.parse!(args)
|
|
138
|
+
verify_args!(args:args, optparse:optparse, min:0, max:1)
|
|
139
|
+
options[:options]['name'] = args[0] if args[0]
|
|
140
|
+
if options[:options]['logo']
|
|
141
|
+
options[:options]['iconPath'] = 'custom'
|
|
142
|
+
end
|
|
143
|
+
connect(options)
|
|
144
|
+
payload = {}
|
|
145
|
+
if options[:payload]
|
|
146
|
+
payload = options[:payload]
|
|
147
|
+
payload.deep_merge!({virtual_switch_object_key => parse_passed_options(options)})
|
|
148
|
+
else
|
|
149
|
+
params.deep_merge!(parse_passed_options(options))
|
|
150
|
+
# prompt for option types
|
|
151
|
+
option_types = add_virtual_switch_option_types
|
|
152
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client, options[:params])
|
|
153
|
+
params.deep_merge!(v_prompt)
|
|
154
|
+
# convert checkbox "on" and "off" to true and false
|
|
155
|
+
params.booleanize!
|
|
156
|
+
|
|
157
|
+
# massage association params a bit
|
|
158
|
+
# params['apps'] = ...
|
|
159
|
+
payload.deep_merge!({virtual_switch_object_key => params})
|
|
160
|
+
end
|
|
161
|
+
@virtual_switches_interface.setopts(options)
|
|
162
|
+
if options[:dry_run]
|
|
163
|
+
print_dry_run @virtual_switches_interface.dry.create(payload)
|
|
164
|
+
return 0, nil
|
|
165
|
+
end
|
|
166
|
+
json_response = @virtual_switches_interface.create(payload)
|
|
167
|
+
virtual_switch = json_response[virtual_switch_object_key]
|
|
168
|
+
render_response(json_response, options, virtual_switch_object_key) do
|
|
169
|
+
print_green_success "Added virtual switch #{virtual_switch['name']}"
|
|
170
|
+
return _get(virtual_switch["id"], {}, options)
|
|
171
|
+
end
|
|
172
|
+
return 0, nil
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def update(args)
|
|
176
|
+
options = {}
|
|
177
|
+
params = {}
|
|
178
|
+
payload = {}
|
|
179
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
180
|
+
opts.banner = subcommand_usage("[switch] [options]")
|
|
181
|
+
build_option_type_options(opts, options, update_virtual_switch_option_types)
|
|
182
|
+
build_standard_update_options(opts, options)
|
|
183
|
+
opts.footer = <<-EOT
|
|
184
|
+
Update a virtual switch.
|
|
185
|
+
[switch] is required. This is the name or id of a virtual switch.
|
|
186
|
+
EOT
|
|
187
|
+
end
|
|
188
|
+
optparse.parse!(args)
|
|
189
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
190
|
+
if options[:options]['logo']
|
|
191
|
+
options[:options]['iconPath'] = 'custom'
|
|
192
|
+
end
|
|
193
|
+
connect(options)
|
|
194
|
+
virtual_switch = find_virtual_switch_by_name_or_id(args[0])
|
|
195
|
+
return 1 if virtual_switch.nil?
|
|
196
|
+
payload = {}
|
|
197
|
+
if options[:payload]
|
|
198
|
+
payload = options[:payload]
|
|
199
|
+
payload.deep_merge!({virtual_switch_object_key => parse_passed_options(options)})
|
|
200
|
+
else
|
|
201
|
+
params.deep_merge!(parse_passed_options(options))
|
|
202
|
+
# do not prompt on update
|
|
203
|
+
v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_virtual_switch_option_types, options[:options], @api_client, options[:params])
|
|
204
|
+
v_prompt.deep_compact!
|
|
205
|
+
params.deep_merge!(v_prompt)
|
|
206
|
+
# convert checkbox "on" and "off" to true and false
|
|
207
|
+
params.booleanize!
|
|
208
|
+
# massage association params a bit
|
|
209
|
+
|
|
210
|
+
# params['apps'] = ...
|
|
211
|
+
payload.deep_merge!({virtual_switch_object_key => params})
|
|
212
|
+
if payload[virtual_switch_object_key].empty? # || options[:no_prompt]
|
|
213
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
@virtual_switches_interface.setopts(options)
|
|
217
|
+
if options[:dry_run]
|
|
218
|
+
print_dry_run @virtual_switches_interface.dry.update(virtual_switch['id'], payload)
|
|
219
|
+
return
|
|
220
|
+
end
|
|
221
|
+
json_response = @virtual_switches_interface.update(virtual_switch['id'], payload)
|
|
222
|
+
virtual_switch = json_response[virtual_switch_object_key]
|
|
223
|
+
render_response(json_response, options, virtual_switch_object_key) do
|
|
224
|
+
print_green_success "Updated virtual switch #{virtual_switch['name']}"
|
|
225
|
+
return _get(virtual_switch["id"], {}, options)
|
|
226
|
+
end
|
|
227
|
+
return 0, nil
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def remove(args)
|
|
231
|
+
options = {}
|
|
232
|
+
params = {}
|
|
233
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
234
|
+
opts.banner = subcommand_usage("[switch] [options]")
|
|
235
|
+
build_standard_remove_options(opts, options)
|
|
236
|
+
opts.footer = <<-EOT
|
|
237
|
+
Delete a virtual switch.
|
|
238
|
+
[switch] is required. This is the name or id of a virtual switch.
|
|
239
|
+
EOT
|
|
240
|
+
end
|
|
241
|
+
optparse.parse!(args)
|
|
242
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
243
|
+
connect(options)
|
|
244
|
+
virtual_switch = find_virtual_switch_by_name_or_id(args[0])
|
|
245
|
+
return 1 if virtual_switch.nil?
|
|
246
|
+
@virtual_switches_interface.setopts(options)
|
|
247
|
+
if options[:dry_run]
|
|
248
|
+
print_dry_run @virtual_switches_interface.dry.destroy(virtual_switch['id'], params)
|
|
249
|
+
return
|
|
250
|
+
end
|
|
251
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the virtual switch #{virtual_switch['name']}?")
|
|
252
|
+
return 9, "aborted command"
|
|
253
|
+
end
|
|
254
|
+
json_response = @virtual_switches_interface.destroy(virtual_switch['id'], params)
|
|
255
|
+
render_response(json_response, options) do
|
|
256
|
+
print_green_success "Removed virtual switch #{virtual_switch['name']}"
|
|
257
|
+
end
|
|
258
|
+
return 0, nil
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
private
|
|
262
|
+
|
|
263
|
+
def add_virtual_switch_option_types
|
|
264
|
+
[
|
|
265
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Choose a unique name for the Virtual Switch'},
|
|
266
|
+
#{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'description' => 'Description'},
|
|
267
|
+
{'switch' => '-t', 'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'description' => 'Type of virtual switch', 'selectOptions' => [{'name' => 'VM Network', 'value' => 'general'}, {'name' => 'ISCSI', 'value' => 'iscsi'}, {'name' => 'NFS', 'value' => 'nfs'}, {'name' => 'Live Migration', 'value' => 'live_migration'}, {'name' => 'SDN', 'value' => 'sdn'}], 'required' => true},
|
|
268
|
+
]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def update_virtual_switch_option_types
|
|
272
|
+
list = add_virtual_switch_option_types.collect {|it|
|
|
273
|
+
it.delete('required')
|
|
274
|
+
it.delete('defaultValue')
|
|
275
|
+
it
|
|
276
|
+
}
|
|
277
|
+
list = list.reject {|it| ["type"].include? it['fieldName'] }
|
|
278
|
+
list
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def find_virtual_switch_by_name_or_id(val)
|
|
282
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
|
283
|
+
return find_virtual_switch_by_id(val)
|
|
284
|
+
else
|
|
285
|
+
return find_virtual_switch_by_name(val)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def find_virtual_switch_by_id(id)
|
|
290
|
+
begin
|
|
291
|
+
json_response = virtual_switches_interface.get(id.to_i)
|
|
292
|
+
return json_response[virtual_switch_object_key]
|
|
293
|
+
rescue RestClient::Exception => e
|
|
294
|
+
if e.response && e.response.code == 404
|
|
295
|
+
print_red_alert "Virtual Switch not found by id '#{id}'"
|
|
296
|
+
else
|
|
297
|
+
raise e
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def find_virtual_switch_by_name(name)
|
|
303
|
+
json_response = virtual_switches_interface.list({name: name.to_s})
|
|
304
|
+
virtual_switches = json_response[virtual_switch_list_key]
|
|
305
|
+
if virtual_switches.empty?
|
|
306
|
+
print_red_alert "Virtual Switch not found by name '#{name}'"
|
|
307
|
+
return nil
|
|
308
|
+
elsif virtual_switches.size > 1
|
|
309
|
+
print_red_alert "#{virtual_switches.size} Virtual Switches found by name '#{name}'"
|
|
310
|
+
print_error "\n"
|
|
311
|
+
puts_error as_pretty_table(virtual_switches, [:id, :name], {color:red})
|
|
312
|
+
print_red_alert "Try using ID instead"
|
|
313
|
+
print_error reset,"\n"
|
|
314
|
+
return nil
|
|
315
|
+
else
|
|
316
|
+
return virtual_switches[0]
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def format_virtual_switch_status(virtual_switch, return_color=cyan)
|
|
321
|
+
out = ""
|
|
322
|
+
status_string = virtual_switch['status'].to_s.downcase
|
|
323
|
+
if status_string
|
|
324
|
+
if ['available','ok'].include?(status_string)
|
|
325
|
+
out << "#{green}#{status_string.upcase}"
|
|
326
|
+
elsif ['unavailable','error'].include?(status_string)
|
|
327
|
+
out << "#{red}#{status_string.upcase}"
|
|
328
|
+
else
|
|
329
|
+
out << "#{return_color}#{status_string.upcase}"
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
out + return_color
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'morpheus/cli/mixins/print_helper'
|
|
2
|
+
|
|
3
|
+
# Mixin for Morpheus::Cli command classes
|
|
4
|
+
# Single source of truth for affinity group / host-vm group enums and formatting.
|
|
5
|
+
#
|
|
6
|
+
# Keep this in sync with morpheus-ui and the OpenAPI shared affinity-type schema.
|
|
7
|
+
# Defining these values here (rather than inline in each command) prevents the
|
|
8
|
+
# definitions from drifting apart between cluster-scoped, cloud-scoped and
|
|
9
|
+
# top-level flat-by-ID commands.
|
|
10
|
+
module Morpheus::Cli::AffinityHelper
|
|
11
|
+
|
|
12
|
+
def self.included(klass)
|
|
13
|
+
klass.send :include, Morpheus::Cli::PrintHelper
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# All affinity types supported by the API.
|
|
17
|
+
# The _MUST variants are mandatory placement constraints and are what the
|
|
18
|
+
# violations endpoint reports on.
|
|
19
|
+
AFFINITY_TYPES = [
|
|
20
|
+
{'name' => 'Keep Together (Should)', 'value' => 'KEEP_TOGETHER'},
|
|
21
|
+
{'name' => 'Keep Separate (Should)', 'value' => 'KEEP_SEPARATE'},
|
|
22
|
+
{'name' => 'Keep Together (Must)', 'value' => 'KEEP_TOGETHER_MUST'},
|
|
23
|
+
{'name' => 'Keep Separate (Must)', 'value' => 'KEEP_SEPARATE_MUST'},
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
# Group types, per HostVmGroup.HostVmGroupType (morpheus-ui rc-9.1.0,
|
|
27
|
+
# morpheus-domains/grails-app/domain/com/morpheus/HostVmGroup.groovy).
|
|
28
|
+
# SITE_GROUP exists in the enum but is not valid on either side of a combined
|
|
29
|
+
# VM-to-Host rule -- AffinityGroupService.applyRuleShapeFields rejects it.
|
|
30
|
+
HOST_VM_GROUP_TYPES = [
|
|
31
|
+
{'name' => 'Site Group', 'value' => 'SITE_GROUP'},
|
|
32
|
+
{'name' => 'Host Group', 'value' => 'HOST_GROUP'},
|
|
33
|
+
{'name' => 'VM Group', 'value' => 'VM_GROUP'},
|
|
34
|
+
].freeze
|
|
35
|
+
|
|
36
|
+
def affinity_type_select_options
|
|
37
|
+
AFFINITY_TYPES.map {|it| it.dup }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def host_vm_group_type_select_options
|
|
41
|
+
HOST_VM_GROUP_TYPES.map {|it| it.dup }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# affinityType is editable on update as well as create.
|
|
45
|
+
# AffinityGroupService.updateAffinityGroup applies params.affinityGroup.affinityType,
|
|
46
|
+
# so callers may switch between MUST and non-MUST behaviour during an edit.
|
|
47
|
+
def affinity_type_option_type(required: false, default: nil)
|
|
48
|
+
opt = {
|
|
49
|
+
'fieldName' => 'affinityType',
|
|
50
|
+
'fieldLabel' => 'Type',
|
|
51
|
+
'type' => 'select',
|
|
52
|
+
'selectOptions' => affinity_type_select_options,
|
|
53
|
+
'description' => 'Affinity type. The _MUST variants are mandatory placement constraints.',
|
|
54
|
+
}
|
|
55
|
+
opt['required'] = true if required
|
|
56
|
+
opt['defaultValue'] = default if default
|
|
57
|
+
opt
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def host_vm_group_type_option_type(required: false, default: nil)
|
|
61
|
+
opt = {
|
|
62
|
+
'fieldName' => 'type',
|
|
63
|
+
'fieldLabel' => 'Group Type',
|
|
64
|
+
'type' => 'select',
|
|
65
|
+
'selectOptions' => host_vm_group_type_select_options,
|
|
66
|
+
'description' => 'Choose group type.',
|
|
67
|
+
}
|
|
68
|
+
opt['required'] = true if required
|
|
69
|
+
opt['defaultValue'] = default if default
|
|
70
|
+
opt
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def format_affinity_type(affinity_type)
|
|
74
|
+
found = AFFINITY_TYPES.find {|it| it['value'] == affinity_type.to_s }
|
|
75
|
+
found ? found['name'].sub(' (Should)', '') : affinity_type.to_s
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def format_host_vm_group_type(type)
|
|
79
|
+
found = HOST_VM_GROUP_TYPES.find {|it| it['value'] == type.to_s }
|
|
80
|
+
found ? found['name'] : type.to_s
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Groups carry poolId; keep only those in the chosen cluster.
|
|
84
|
+
def filter_by_pool(rows, pool_id)
|
|
85
|
+
return rows if pool_id.nil?
|
|
86
|
+
rows.select {|r| r['poolId'].nil? || r['poolId'].to_s == pool_id.to_s }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Surface the server's own error text for affinity failures.
|
|
90
|
+
#
|
|
91
|
+
# The API returns {success:false, msg:"<human text>", errors:{<field>:"<i18n key>"}}
|
|
92
|
+
# -- there is no top-level errorCode. AffinityGroupService and
|
|
93
|
+
# VmwareComputeService.combinedRuleGroupSyncGuard already compose actionable
|
|
94
|
+
# messages (cross-cluster group refs, groups not yet synced to vSphere, rule
|
|
95
|
+
# shape violations), so print them verbatim rather than restating them here.
|
|
96
|
+
def handle_affinity_rest_exception(e, options)
|
|
97
|
+
if e.respond_to?(:response) && e.response
|
|
98
|
+
begin
|
|
99
|
+
body = JSON.parse(e.response.to_s)
|
|
100
|
+
if body.is_a?(Hash) && (body['msg'] || body['errors'])
|
|
101
|
+
print_red_alert body['msg'] if body['msg']
|
|
102
|
+
errors = body['errors']
|
|
103
|
+
if errors.is_a?(Hash) && !errors.empty?
|
|
104
|
+
# Same stream as print_red_alert, so msg and field detail stay together.
|
|
105
|
+
errors.each do |field, message|
|
|
106
|
+
$stderr.print "#{red} #{field}: #{message}#{reset}\n"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
rescue TypeError, JSON::ParserError
|
|
112
|
+
# fall through to the generic handler
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
print_rest_exception(e, options)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|