1212#
1313
1414"""Subnet action implementations"""
15+ import copy
16+
17+ from json .encoder import JSONEncoder
1518
1619from openstackclient .common import command
20+ from openstackclient .common import parseractions
1721from openstackclient .common import utils
22+ from openstackclient .identity import common as identity_common
1823
1924
2025def _format_allocation_pools (data ):
@@ -23,10 +28,17 @@ def _format_allocation_pools(data):
2328 return ',' .join (pool_formatted )
2429
2530
31+ def _format_host_routes (data ):
32+ try :
33+ return '\n ' .join ([JSONEncoder ().encode (route ) for route in data ])
34+ except (TypeError , KeyError ):
35+ return ''
36+
37+
2638_formatters = {
2739 'allocation_pools' : _format_allocation_pools ,
2840 'dns_nameservers' : utils .format_list ,
29- 'host_routes' : utils . format_list ,
41+ 'host_routes' : _format_host_routes ,
3042}
3143
3244
@@ -38,6 +50,214 @@ def _get_columns(item):
3850 return tuple (sorted (columns ))
3951
4052
53+ def convert_entries_to_nexthop (entries ):
54+ # Change 'gateway' entry to 'nexthop'
55+ changed_entries = copy .deepcopy (entries )
56+ for entry in changed_entries :
57+ entry ['nexthop' ] = entry ['gateway' ]
58+ del entry ['gateway' ]
59+
60+ return changed_entries
61+
62+
63+ def convert_entries_to_gateway (entries ):
64+ # Change 'nexhop' entry to 'gateway'
65+ changed_entries = copy .deepcopy (entries )
66+ for entry in changed_entries :
67+ entry ['gateway' ] = entry ['nexthop' ]
68+ del entry ['nexthop' ]
69+
70+ return changed_entries
71+
72+
73+ def _get_attrs (client_manager , parsed_args ):
74+ attrs = {}
75+ if parsed_args .name is not None :
76+ attrs ['name' ] = str (parsed_args .name )
77+
78+ if 'project' in parsed_args and parsed_args .project is not None :
79+ identity_client = client_manager .identity
80+ project_id = identity_common .find_project (
81+ identity_client ,
82+ parsed_args .project ,
83+ parsed_args .project_domain ,
84+ ).id
85+ attrs ['tenant_id' ] = project_id
86+
87+ client = client_manager .network
88+ attrs ['network_id' ] = client .find_network (parsed_args .network ,
89+ ignore_missing = False ).id
90+
91+ if parsed_args .subnet_pool is not None :
92+ subnet_pool = client .find_subnet_pool (parsed_args .subnet_pool ,
93+ ignore_missing = False )
94+ attrs ['subnetpool_id' ] = subnet_pool .id
95+
96+ if parsed_args .use_default_subnet_pool :
97+ attrs ['use_default_subnetpool' ] = True
98+ if parsed_args .gateway .lower () != 'auto' :
99+ if parsed_args .gateway .lower () == 'none' :
100+ attrs ['gateway_ip' ] = None
101+ else :
102+ attrs ['gateway_ip' ] = parsed_args .gateway
103+ if parsed_args .prefix_length is not None :
104+ attrs ['prefixlen' ] = parsed_args .prefix_length
105+ if parsed_args .subnet_range is not None :
106+ attrs ['cidr' ] = parsed_args .subnet_range
107+ if parsed_args .ip_version is not None :
108+ attrs ['ip_version' ] = parsed_args .ip_version
109+ if parsed_args .ipv6_ra_mode is not None :
110+ attrs ['ipv6_ra_mode' ] = parsed_args .ipv6_ra_mode
111+ if parsed_args .ipv6_address_mode is not None :
112+ attrs ['ipv6_address_mode' ] = parsed_args .ipv6_address_mode
113+ if parsed_args .allocation_pools is not None :
114+ attrs ['allocation_pools' ] = parsed_args .allocation_pools
115+ if parsed_args .enable_dhcp is not None :
116+ attrs ['enable_dhcp' ] = parsed_args .enable_dhcp
117+ if parsed_args .dns_nameservers is not None :
118+ attrs ['dns_nameservers' ] = parsed_args .dns_nameservers
119+ if parsed_args .host_routes is not None :
120+ # Change 'gateway' entry to 'nexthop' to match the API
121+ attrs ['host_routes' ] = convert_entries_to_nexthop (
122+ parsed_args .host_routes )
123+
124+ return attrs
125+
126+
127+ class CreateSubnet (command .ShowOne ):
128+ """Create a subnet"""
129+
130+ def get_parser (self , prog_name ):
131+ parser = super (CreateSubnet , self ).get_parser (prog_name )
132+ parser .add_argument (
133+ 'name' ,
134+ help = 'New subnet name' ,
135+ )
136+ parser .add_argument (
137+ '--project' ,
138+ metavar = '<project>' ,
139+ help = "Owner's project (name or ID)" ,
140+ )
141+ identity_common .add_project_domain_option_to_parser (parser )
142+ subnet_pool_group = parser .add_mutually_exclusive_group ()
143+ subnet_pool_group .add_argument (
144+ '--subnet-pool' ,
145+ metavar = '<subnet-pool>' ,
146+ help = 'Subnet pool from which this subnet will obtain a CIDR '
147+ '(Name or ID)' ,
148+ )
149+ subnet_pool_group .add_argument (
150+ '--use-default-subnet-pool' ,
151+ action = 'store_true' ,
152+ help = 'Use default subnet pool for --ip-version' ,
153+ )
154+ parser .add_argument (
155+ '--prefix-length' ,
156+ metavar = '<prefix-length>' ,
157+ help = 'Prefix length for subnet allocation from subnetpool' ,
158+ )
159+ parser .add_argument (
160+ '--subnet-range' ,
161+ metavar = '<subnet-range>' ,
162+ help = 'Subnet range in CIDR notation '
163+ '(required if --subnet-pool is not specified, '
164+ 'optional otherwise)' ,
165+ )
166+ parser .add_argument (
167+ '--allocation-pool' ,
168+ metavar = 'start=<ip-address>,end=<ip-address>' ,
169+ dest = 'allocation_pools' ,
170+ action = parseractions .MultiKeyValueAction ,
171+ required_keys = ['start' , 'end' ],
172+ help = 'Allocation pool IP addresses for this subnet '
173+ 'e.g.: start=192.168.199.2,end=192.168.199.254 '
174+ '(This option can be repeated)' ,
175+ )
176+ dhcp_enable_group = parser .add_mutually_exclusive_group ()
177+ dhcp_enable_group .add_argument (
178+ '--dhcp' ,
179+ dest = 'enable_dhcp' ,
180+ action = 'store_true' ,
181+ default = True ,
182+ help = 'Enable DHCP (default)' ,
183+ )
184+ dhcp_enable_group .add_argument (
185+ '--no-dhcp' ,
186+ dest = 'enable_dhcp' ,
187+ action = 'store_false' ,
188+ help = 'Disable DHCP' ,
189+ )
190+ parser .add_argument (
191+ '--dns-nameserver' ,
192+ metavar = '<dns-nameserver>' ,
193+ action = 'append' ,
194+ dest = 'dns_nameservers' ,
195+ help = 'DNS name server for this subnet '
196+ '(This option can be repeated)' ,
197+ )
198+ parser .add_argument (
199+ '--gateway' ,
200+ metavar = '<gateway>' ,
201+ default = 'auto' ,
202+ help = "Specify a gateway for the subnet. The three options are: "
203+ " <ip-address>: Specific IP address to use as the gateway "
204+ " 'auto': Gateway address should automatically be "
205+ " chosen from within the subnet itself "
206+ " 'none': This subnet will not use a gateway "
207+ "e.g.: --gateway 192.168.9.1, --gateway auto, --gateway none"
208+ "(default is 'auto')" ,
209+ )
210+ parser .add_argument (
211+ '--host-route' ,
212+ metavar = 'destination=<subnet>,gateway=<ip-address>' ,
213+ dest = 'host_routes' ,
214+ action = parseractions .MultiKeyValueAction ,
215+ required_keys = ['destination' , 'gateway' ],
216+ help = 'Additional route for this subnet '
217+ 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 '
218+ 'destination: destination subnet (in CIDR notation) '
219+ 'gateway: nexthop IP address '
220+ '(This option can be repeated)' ,
221+ )
222+ parser .add_argument (
223+ '--ip-version' ,
224+ type = int ,
225+ default = 4 ,
226+ choices = [4 , 6 ],
227+ help = 'IP version (default is 4). Note that when subnet pool is '
228+ 'specified, IP version is determined from the subnet pool '
229+ 'and this option is ignored.' ,
230+ )
231+ parser .add_argument (
232+ '--ipv6-ra-mode' ,
233+ choices = ['dhcpv6-stateful' , 'dhcpv6-stateless' , 'slaac' ],
234+ help = 'IPv6 RA (Router Advertisement) mode, '
235+ 'valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]' ,
236+ )
237+ parser .add_argument (
238+ '--ipv6-address-mode' ,
239+ choices = ['dhcpv6-stateful' , 'dhcpv6-stateless' , 'slaac' ],
240+ help = 'IPv6 address mode, '
241+ 'valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]' ,
242+ )
243+ parser .add_argument (
244+ '--network' ,
245+ required = True ,
246+ metavar = '<network>' ,
247+ help = 'Network this subnet belongs to (name or ID)' ,
248+ )
249+
250+ return parser
251+
252+ def take_action (self , parsed_args ):
253+ client = self .app .client_manager .network
254+ attrs = _get_attrs (self .app .client_manager , parsed_args )
255+ obj = client .create_subnet (** attrs )
256+ columns = _get_columns (obj )
257+ data = utils .get_item_properties (obj , columns , formatters = _formatters )
258+ return (columns , data )
259+
260+
41261class DeleteSubnet (command .Command ):
42262 """Delete subnet"""
43263
@@ -46,7 +266,7 @@ def get_parser(self, prog_name):
46266 parser .add_argument (
47267 'subnet' ,
48268 metavar = "<subnet>" ,
49- help = "Subnet to delete (name or ID)"
269+ help = "Subnet to delete (name or ID)" ,
50270 )
51271 return parser
52272
@@ -97,7 +317,7 @@ def get_parser(self, prog_name):
97317 parser .add_argument (
98318 'subnet' ,
99319 metavar = "<subnet>" ,
100- help = "Subnet to show (name or ID)"
320+ help = "Subnet to show (name or ID)" ,
101321 )
102322 return parser
103323
0 commit comments