Revision 2a2f9ffa
b/snf-cyclades-app/synnefo/api/management/commands/network-modify.py | ||
---|---|---|
35 | 35 |
|
36 | 36 |
from django.core.management.base import BaseCommand, CommandError |
37 | 37 |
|
38 |
from synnefo.db.models import (Network, Backend, BackendNetwork, |
|
39 |
pooled_rapi_client) |
|
38 |
from synnefo.db.models import (Backend, BackendNetwork, pooled_rapi_client) |
|
40 | 39 |
from synnefo.management.common import (get_network, get_backend) |
41 | 40 |
from snf_django.management.utils import parse_bool |
42 |
from synnefo.logic import networks |
|
43 |
from synnefo.logic.backend import create_network, delete_network |
|
44 |
|
|
45 |
HELP_MSG = """Modify a network. |
|
46 |
|
|
47 |
This management command will only modify the state of the network in Cyclades |
|
48 |
DB. The state of the network in the Ganeti backends will remain unchanged. You |
|
49 |
should manually modify the network in all the backends, to synchronize the |
|
50 |
state of DB and Ganeti. |
|
51 |
|
|
52 |
The only exception is add_reserved_ips and remove_reserved_ips options, which |
|
53 |
modify the IP pool in the Ganeti backends. |
|
54 |
""" |
|
41 |
from synnefo.logic import networks, backend as backend_mod |
|
42 |
from django.db import transaction |
|
55 | 43 |
|
56 | 44 |
|
57 | 45 |
class Command(BaseCommand): |
58 | 46 |
args = "<network id>" |
59 |
help = HELP_MSG |
|
60 |
output_transaction = True |
|
47 |
help = "Modify a network." |
|
61 | 48 |
|
62 | 49 |
option_list = BaseCommand.option_list + ( |
63 | 50 |
make_option( |
64 | 51 |
'--name', |
65 | 52 |
dest='name', |
66 | 53 |
metavar='NAME', |
67 |
help="Set network's name"),
|
|
54 |
help="Rename a network"),
|
|
68 | 55 |
make_option( |
69 | 56 |
'--userid', |
70 | 57 |
dest='userid', |
71 |
help="Set the userid of the network owner"), |
|
72 |
make_option( |
|
73 |
'--subnet', |
|
74 |
dest='subnet', |
|
75 |
help="Set network's subnet"), |
|
76 |
make_option( |
|
77 |
'--gateway', |
|
78 |
dest='gateway', |
|
79 |
help="Set network's gateway"), |
|
80 |
make_option( |
|
81 |
'--subnet6', |
|
82 |
dest='subnet6', |
|
83 |
help="Set network's IPv6 subnet"), |
|
58 |
help="Change the owner of the network."), |
|
84 | 59 |
make_option( |
85 |
'--gateway6', |
|
86 |
dest='gateway6', |
|
87 |
help="Set network's IPv6 gateway"), |
|
88 |
make_option( |
|
89 |
'--dhcp', |
|
90 |
dest='dhcp', |
|
60 |
"--drained", |
|
61 |
dest="drained", |
|
91 | 62 |
metavar="True|False", |
92 | 63 |
choices=["True", "False"], |
93 |
help="Set if network will use nfdhcp"), |
|
94 |
make_option( |
|
95 |
'--state', |
|
96 |
dest='state', |
|
97 |
metavar='STATE', |
|
98 |
help="Set network's state"), |
|
99 |
make_option( |
|
100 |
'--link', |
|
101 |
dest='link', |
|
102 |
help="Set the connectivity link"), |
|
64 |
help="Set as drained to exclude for IP allocation." |
|
65 |
" Only used for public networks."), |
|
103 | 66 |
make_option( |
104 |
'--mac-prefix', |
|
105 |
dest="mac_prefix", |
|
106 |
help="Set the MAC prefix"), |
|
67 |
"--floating-ip-pool", |
|
68 |
dest="floating_ip_pool", |
|
69 |
metavar="True|False", |
|
70 |
choices=["True", "False"], |
|
71 |
help="Convert network to a floating IP pool. During this" |
|
72 |
" conversation the network will be created to all" |
|
73 |
" available Ganeti backends."), |
|
107 | 74 |
make_option( |
108 | 75 |
'--add-reserved-ips', |
109 | 76 |
dest="add_reserved_ips", |
... | ... | |
113 | 80 |
dest="remove_reserved_ips", |
114 | 81 |
help="Comma seperated list of IPs to externally release."), |
115 | 82 |
make_option( |
116 |
"--drained", |
|
117 |
dest="drained", |
|
118 |
metavar="True|False", |
|
119 |
choices=["True", "False"], |
|
120 |
help="Set as drained to exclude for IP allocation." |
|
121 |
" Only used for public networks."), |
|
122 |
make_option( |
|
123 | 83 |
"--add-to-backend", |
124 | 84 |
dest="add_to_backend", |
125 |
help="Create a public network to a Ganeti backend."), |
|
85 |
metavar="BACKEND_ID", |
|
86 |
help="Create a network to a Ganeti backend."), |
|
126 | 87 |
make_option( |
127 | 88 |
"--remove-from-backend", |
128 | 89 |
dest="remove_from_backend", |
129 |
help="Remove a public network from a Ganeti backend."), |
|
130 |
make_option( |
|
131 |
"--floating-ip-pool", |
|
132 |
dest="floating_ip_pool", |
|
133 |
metavar="True|False", |
|
134 |
choices=["True", "False"], |
|
135 |
help="Convert network to a floating IP pool. During this" |
|
136 |
" conversation the network will be created to all" |
|
137 |
" available Ganeti backends."), |
|
90 |
metavar="BACKEND_ID", |
|
91 |
help="Remove a network from a Ganeti backend."), |
|
138 | 92 |
) |
139 | 93 |
|
94 |
@transaction.commit_on_success |
|
140 | 95 |
def handle(self, *args, **options): |
141 | 96 |
if len(args) != 1: |
142 | 97 |
raise CommandError("Please provide a network ID") |
143 | 98 |
|
144 | 99 |
network = get_network(args[0]) |
145 | 100 |
|
146 |
# Validate subnet
|
|
147 |
subnet = options["subnet"] or network.subnet
|
|
148 |
gateway = options["gateway"] or network.gateway
|
|
149 |
subnet6 = options["subnet6"] or network.subnet6
|
|
150 |
gateway6 = options["gateway6"] or network.gateway6
|
|
151 |
networks.validate_network_params(subnet, gateway, subnet6, gateway6)
|
|
101 |
new_name = options.get("name")
|
|
102 |
if new_name is not None:
|
|
103 |
old_name = network.name
|
|
104 |
network = networks.rename(network, new_name)
|
|
105 |
self.stdout.write("Renamed network '%s' from '%s' to '%s'.\n" %
|
|
106 |
(network, old_name, new_name))
|
|
152 | 107 |
|
153 |
# Validate state |
|
154 |
state = options.get('state') |
|
155 |
if state: |
|
156 |
allowed = [x[0] for x in Network.OPER_STATES] |
|
157 |
if state not in allowed: |
|
158 |
msg = "Invalid state, must be one of %s" % ', '.join(allowed) |
|
159 |
raise CommandError(msg) |
|
108 |
drained = options.get("drained") |
|
109 |
if drained is not None: |
|
110 |
drained = parse_bool(drained) |
|
111 |
network.drained = drained |
|
112 |
network.save() |
|
113 |
self.stdout.write("Set network '%s' as drained=%s.\n" % |
|
114 |
(network, drained)) |
|
115 |
|
|
116 |
new_owner = options.get("userid") |
|
117 |
if new_owner is not None: |
|
118 |
if "@" in new_owner: |
|
119 |
raise CommandError("Invalid owner UUID.") |
|
120 |
old_owner = network.userid |
|
121 |
network.userid = new_owner |
|
122 |
network.save() |
|
123 |
msg = "Changed the owner of network '%s' from '%s' to '%s'.\n" |
|
124 |
self.stdout.write(msg % (network, old_owner, new_owner)) |
|
160 | 125 |
|
161 | 126 |
floating_ip_pool = options["floating_ip_pool"] |
162 | 127 |
if floating_ip_pool is not None: |
163 | 128 |
floating_ip_pool = parse_bool(floating_ip_pool) |
164 |
options["floating_ip_pool"] = floating_ip_pool |
|
165 |
if floating_ip_pool is False and network.floating_ip_pool is True: |
|
166 |
if network.floating_ips.filter(deleted=False).exists(): |
|
167 |
msg = ("Can not make network a non floating IP pool. There are" |
|
168 |
" still reserved floating IPs.") |
|
169 |
raise CommandError(msg) |
|
170 |
elif floating_ip_pool is True: |
|
171 |
existing =\ |
|
172 |
network.backend_networks.filter(operstate="ACTIVE")\ |
|
173 |
.values_list("backend", flat=True) |
|
174 |
for backend in Backend.objects.filter(offline=False)\ |
|
175 |
.exclude(id__in=existing): |
|
176 |
check_link_availability(backend, network) |
|
177 |
|
|
178 |
dhcp = options.get("dhcp") |
|
179 |
if dhcp: |
|
180 |
options["dhcp"] = parse_bool(dhcp) |
|
181 |
drained = options.get("drained") |
|
182 |
if drained: |
|
183 |
options["drained"] = parse_bool(drained) |
|
184 |
fields = ('name', 'userid', 'subnet', 'gateway', 'subnet6', 'gateway6', |
|
185 |
'dhcp', 'state', 'link', 'mac_prefix', 'drained', |
|
186 |
'floating_ip_pool') |
|
187 |
for field in fields: |
|
188 |
value = options.get(field, None) |
|
189 |
if value is not None: |
|
190 |
network.__setattr__(field, value) |
|
191 |
|
|
192 |
network.save() |
|
129 |
if floating_ip_pool is False and network.floating_ip_pool is True: |
|
130 |
if network.floating_ips.filter(deleted=False).exists(): |
|
131 |
msg = ("Can not make network a non floating IP pool." |
|
132 |
" There are still reserved floating IPs.") |
|
133 |
raise CommandError(msg) |
|
134 |
network.floating_ip_pool = floating_ip_pool |
|
135 |
network.save() |
|
136 |
self.stdout.write("Set network '%s' as floating-ip-pool=%s.\n" % |
|
137 |
(network, floating_ip_pool)) |
|
138 |
if floating_ip_pool is True: |
|
139 |
for backend in Backend.objects.filter(offline=False): |
|
140 |
try: |
|
141 |
bnet = network.backend_networks.get(backend=backend) |
|
142 |
except BackendNetwork.DoesNotExist: |
|
143 |
bnet = network.create_backend_network(backend=backend) |
|
144 |
if bnet.operstate != "ACTIVE": |
|
145 |
backend_mod.create_network(network, backend, |
|
146 |
connect=True) |
|
147 |
msg = ("Sent job to create network '%s' in backend" |
|
148 |
" '%s'\n" % (network, backend)) |
|
149 |
self.stdout.write(msg) |
|
193 | 150 |
|
194 | 151 |
add_reserved_ips = options.get('add_reserved_ips') |
195 | 152 |
remove_reserved_ips = options.get('remove_reserved_ips') |
... | ... | |
199 | 156 |
if remove_reserved_ips: |
200 | 157 |
remove_reserved_ips = remove_reserved_ips.split(",") |
201 | 158 |
|
202 |
for bnetwork in network.backend_networks.all():
|
|
159 |
for bnetwork in network.backend_networks.filter(offline=False):
|
|
203 | 160 |
with pooled_rapi_client(bnetwork.backend) as c: |
204 | 161 |
c.ModifyNetwork(network=network.backend_id, |
205 | 162 |
add_reserved_ips=add_reserved_ips, |
206 | 163 |
remove_reserved_ips=remove_reserved_ips) |
207 | 164 |
|
208 |
if floating_ip_pool is True: |
|
209 |
for backend in Backend.objects.filter(offline=False): |
|
210 |
try: |
|
211 |
bnet = network.backend_networks.get(backend=backend) |
|
212 |
except BackendNetwork.DoesNotExist: |
|
213 |
bnet = network.create_backend_network(backend=backend) |
|
214 |
if bnet.operstate != "ACTIVE": |
|
215 |
create_network(network, backend, connect=True) |
|
216 |
msg = "Sent job to create network '%s' in backend '%s'\n" |
|
217 |
self.stdout.write(msg % (network, backend)) |
|
218 |
|
|
219 | 165 |
add_to_backend = options["add_to_backend"] |
220 | 166 |
if add_to_backend is not None: |
221 | 167 |
backend = get_backend(add_to_backend) |
222 | 168 |
network.create_backend_network(backend=backend) |
223 |
create_network(network, backend, connect=True) |
|
169 |
backend_mod.create_network(network, backend, connect=True)
|
|
224 | 170 |
msg = "Sent job to create network '%s' in backend '%s'\n" |
225 | 171 |
self.stdout.write(msg % (network, backend)) |
226 | 172 |
|
... | ... | |
234 | 180 |
raise CommandError(msg) |
235 | 181 |
network.action = "DESTROY" |
236 | 182 |
network.save() |
237 |
delete_network(network, backend, disconnect=True) |
|
183 |
backend_mod.delete_network(network, backend, disconnect=True)
|
|
238 | 184 |
msg = "Sent job to delete network '%s' from backend '%s'\n" |
239 | 185 |
self.stdout.write(msg % (network, backend)) |
240 |
|
|
241 |
|
|
242 |
def check_link_availability(backend, network): |
|
243 |
"""Check if network link is available in backend.""" |
|
244 |
with pooled_rapi_client(backend) as c: |
|
245 |
ganeti_networks = c.GetNetworks(bulk=True) |
|
246 |
name = network.backend_id |
|
247 |
mode = network.mode |
|
248 |
link = network.link |
|
249 |
for gnet in ganeti_networks: |
|
250 |
if (gnet["name"] != name and |
|
251 |
reduce(lambda x, y: x or y, |
|
252 |
["(%s, %s)" % (mode, link) in gnet["group_list"]], |
|
253 |
False)): |
|
254 |
# Ganeti >= 2.7 |
|
255 |
#(mode, link) in [(m, l) for (_, m, l) in gnet["group_list"]]): |
|
256 |
msg = "Can not create network '%s' in backend '%s'. Link '%s'" \ |
|
257 |
" is already used by network '%s" % \ |
|
258 |
(network, backend, link, gnet["name"]) |
|
259 |
raise CommandError(msg) |
Also available in: Unified diff