Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / management / commands / network-modify.py @ 35f2fc07

History | View | Annotate | Download (10.4 kB)

1
# Copyright 2012-2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from optparse import make_option
35

    
36
from django.core.management.base import BaseCommand, CommandError
37

    
38
from synnefo.db.models import (Network, Backend, BackendNetwork,
39
                               pooled_rapi_client)
40
from synnefo.management.common import (get_network, get_backend)
41
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
"""
55

    
56

    
57
class Command(BaseCommand):
58
    args = "<network id>"
59
    help = HELP_MSG
60
    output_transaction = True
61

    
62
    option_list = BaseCommand.option_list + (
63
        make_option(
64
            '--name',
65
            dest='name',
66
            metavar='NAME',
67
            help="Set network's name"),
68
        make_option(
69
            '--userid',
70
            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"),
84
        make_option(
85
            '--gateway6',
86
            dest='gateway6',
87
            help="Set network's IPv6 gateway"),
88
        make_option(
89
            '--dhcp',
90
            dest='dhcp',
91
            metavar="True|False",
92
            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"),
103
        make_option(
104
            '--mac-prefix',
105
            dest="mac_prefix",
106
            help="Set the MAC prefix"),
107
        make_option(
108
            '--add-reserved-ips',
109
            dest="add_reserved_ips",
110
            help="Comma seperated list of IPs to externally reserve."),
111
        make_option(
112
            '--remove-reserved-ips',
113
            dest="remove_reserved_ips",
114
            help="Comma seperated list of IPs to externally release."),
115
        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
            "--add-to-backend",
124
            dest="add_to_backend",
125
            help="Create a public network to a Ganeti backend."),
126
        make_option(
127
            "--remove-from-backend",
128
            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."),
138
    )
139

    
140
    def handle(self, *args, **options):
141
        if len(args) != 1:
142
            raise CommandError("Please provide a network ID")
143

    
144
        network = get_network(args[0])
145

    
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)
152

    
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)
160

    
161
        floating_ip_pool = options["floating_ip_pool"]
162
        if floating_ip_pool is not None:
163
            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()
193

    
194
        add_reserved_ips = options.get('add_reserved_ips')
195
        remove_reserved_ips = options.get('remove_reserved_ips')
196
        if add_reserved_ips or remove_reserved_ips:
197
            if add_reserved_ips:
198
                add_reserved_ips = add_reserved_ips.split(",")
199
            if remove_reserved_ips:
200
                remove_reserved_ips = remove_reserved_ips.split(",")
201

    
202
            for bnetwork in network.backend_networks.all():
203
                with pooled_rapi_client(bnetwork.backend) as c:
204
                    c.ModifyNetwork(network=network.backend_id,
205
                                    add_reserved_ips=add_reserved_ips,
206
                                    remove_reserved_ips=remove_reserved_ips)
207

    
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
        add_to_backend = options["add_to_backend"]
220
        if add_to_backend is not None:
221
            backend = get_backend(add_to_backend)
222
            network.create_backend_network(backend=backend)
223
            create_network(network, backend, connect=True)
224
            msg = "Sent job to create network '%s' in backend '%s'\n"
225
            self.stdout.write(msg % (network, backend))
226

    
227
        remove_from_backend = options["remove_from_backend"]
228
        if remove_from_backend is not None:
229
            backend = get_backend(remove_from_backend)
230
            if network.nics.filter(machine__backend=backend,
231
                                   machine__deleted=False).exists():
232
                msg = "Can not remove. There are still connected VMs to this"\
233
                      " network"
234
                raise CommandError(msg)
235
            network.action = "DESTROY"
236
            network.save()
237
            delete_network(network, backend, disconnect=True)
238
            msg = "Sent job to delete network '%s' from backend '%s'\n"
239
            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)