Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / quotas / management / commands / enforce-resources-cyclades.py @ 7e4cd6c0

History | View | Annotate | Download (11.2 kB)

1
# Copyright 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
import string
35
from optparse import make_option
36
from django.db import transaction
37

    
38
from synnefo.lib.ordereddict import OrderedDict
39
from synnefo.quotas import util
40
from synnefo.quotas import enforce
41
from synnefo.quotas import errors
42
from snf_django.management.commands import SynnefoCommand, CommandError
43
from snf_django.management.utils import pprint_table
44

    
45

    
46
DEFAULT_RESOURCES = ["cyclades.cpu",
47
                     "cyclades.ram",
48
                     "cyclades.floating_ip",
49
                     ]
50

    
51

    
52
class Command(SynnefoCommand):
53
    help = """Check and fix quota violations for Cyclades resources.
54
    """
55

    
56
    command_option_list = (
57
        make_option("--max-operations",
58
                    help="Limit operations per backend."),
59
        make_option("--users", dest="users",
60
                    help=("Enforce resources only for the specified list "
61
                          "of users, e.g uuid1,uuid2")),
62
        make_option("--exclude-users",
63
                    help=("Exclude list of users from resource enforcement")),
64
        make_option("--projects",
65
                    help=("Enforce resources only for the specified list "
66
                          "of projects, e.g uuid1,uuid2")),
67
        make_option("--exclude-projects",
68
                    help=("Exclude list of projects from resource enforcement")
69
                    ),
70
        make_option("--resources",
71
                    help="Specify resources to check, default: %s" %
72
                    ",".join(DEFAULT_RESOURCES)),
73
        make_option("--fix",
74
                    default=False,
75
                    action="store_true",
76
                    help="Fix violations"),
77
        make_option("--force",
78
                    default=False,
79
                    action="store_true",
80
                    help=("Confirm actions that may permanently "
81
                          "remove a vm")),
82
        make_option("--shutdown-timeout",
83
                    help="Force vm shutdown after given seconds."),
84
    )
85

    
86
    def confirm(self):
87
        self.stdout.write("Confirm? [y/N] ")
88
        try:
89
            response = raw_input()
90
        except EOFError:
91
            response = "ABORT"
92
        if string.lower(response) not in ['y', 'yes']:
93
            self.stderr.write("Aborted.\n")
94
            exit()
95

    
96
    def get_handlers(self, resources):
97
        def rem(v):
98
            try:
99
                resources.remove(v)
100
                return True
101
            except ValueError:
102
                return False
103

    
104
        if resources is None:
105
            resources = list(DEFAULT_RESOURCES)
106
        else:
107
            resources = resources.split(",")
108

    
109
        handlers = [h for h in enforce.RESOURCE_HANDLING if rem(h[0])]
110
        if resources:
111
            m = "No such resource '%s'" % resources[0]
112
            raise CommandError(m)
113
        return handlers
114

    
115
    @transaction.commit_on_success
116
    def handle(self, *args, **options):
117
        write = self.stderr.write
118
        fix = options["fix"]
119
        force = options["force"]
120
        handlers = self.get_handlers(options["resources"])
121
        maxops = options["max_operations"]
122
        if maxops is not None:
123
            try:
124
                maxops = int(maxops)
125
            except ValueError:
126
                m = "Expected integer max operations."
127
                raise CommandError(m)
128

    
129
        shutdown_timeout = options["shutdown_timeout"]
130
        if shutdown_timeout is not None:
131
            try:
132
                shutdown_timeout = int(shutdown_timeout)
133
            except ValueError:
134
                m = "Expected integer shutdown timeout."
135
                raise CommandError(m)
136

    
137
        excluded_users = options['exclude_users']
138
        excluded_users = set(excluded_users.split(',')
139
                             if excluded_users is not None else [])
140

    
141
        users_to_check = options['users']
142
        if users_to_check is not None:
143
            users_to_check = list(set(users_to_check.split(',')) -
144
                                  excluded_users)
145

    
146
        try:
147
            qh_holdings = util.get_qh_users_holdings(users_to_check)
148
        except errors.AstakosClientException as e:
149
            raise CommandError(e)
150

    
151
        excluded_projects = options["exclude_projects"]
152
        excluded_projects = set(excluded_projects.split(',')
153
                                if excluded_projects is not None else [])
154

    
155
        projects_to_check = options["projects"]
156
        if projects_to_check is not None:
157
            projects_to_check = list(set(projects_to_check.split(',')) -
158
                                     excluded_projects)
159

    
160
        try:
161
            qh_project_holdings = util.get_qh_project_holdings(
162
                projects_to_check)
163
        except errors.AstakosClientException as e:
164
            raise CommandError(e)
165

    
166
        qh_project_holdings = sorted(qh_project_holdings.items())
167
        qh_holdings = sorted(qh_holdings.items())
168
        resources = set(h[0] for h in handlers)
169
        dangerous = bool(resources.difference(DEFAULT_RESOURCES))
170

    
171
        opts = {"shutdown_timeout": shutdown_timeout}
172
        actions = {}
173
        overlimit = []
174
        viol_id = 0
175

    
176
        if users_to_check is None:
177
            for resource, handle_resource, resource_type in handlers:
178
                if resource_type not in actions:
179
                    actions[resource_type] = OrderedDict()
180
                actual_resources = enforce.get_actual_resources(
181
                    resource_type, projects=projects_to_check)
182
                for project, project_quota in qh_project_holdings:
183
                    if enforce.skip_check(project, projects_to_check,
184
                                          excluded_projects):
185
                        continue
186
                    try:
187
                        qh = util.transform_project_quotas(project_quota)
188
                        qh_value, qh_limit, qh_pending = qh[resource]
189
                    except KeyError:
190
                        write("Resource '%s' does not exist in Quotaholder"
191
                              " for project '%s'!\n" %
192
                              (resource, project))
193
                        continue
194
                    if qh_pending:
195
                        write("Pending commission for project '%s', "
196
                              "resource '%s'. Skipping\n" %
197
                              (project, resource))
198
                        continue
199
                    diff = qh_value - qh_limit
200
                    if diff > 0:
201
                        viol_id += 1
202
                        overlimit.append((viol_id, "project", project, "",
203
                                          resource, qh_limit, qh_value))
204
                        relevant_resources = enforce.pick_project_resources(
205
                            actual_resources[project], users=users_to_check,
206
                            excluded_users=excluded_users)
207
                        handle_resource(viol_id, resource, relevant_resources,
208
                                        diff, actions)
209

    
210
        for resource, handle_resource, resource_type in handlers:
211
            if resource_type not in actions:
212
                actions[resource_type] = OrderedDict()
213
            actual_resources = enforce.get_actual_resources(resource_type,
214
                                                            users_to_check)
215
            for user, user_quota in qh_holdings:
216
                if enforce.skip_check(user, users_to_check, excluded_users):
217
                    continue
218
                for source, source_quota in user_quota.iteritems():
219
                    if enforce.skip_check(source, projects_to_check,
220
                                          excluded_projects):
221
                        continue
222
                    try:
223
                        qh = util.transform_quotas(source_quota)
224
                        qh_value, qh_limit, qh_pending = qh[resource]
225
                    except KeyError:
226
                        write("Resource '%s' does not exist in Quotaholder"
227
                              " for user '%s' and source '%s'!\n" %
228
                              (resource, user, source))
229
                        continue
230
                    if qh_pending:
231
                        write("Pending commission for user '%s', source '%s', "
232
                              "resource '%s'. Skipping\n" %
233
                              (user, source, resource))
234
                        continue
235
                    diff = qh_value - qh_limit
236
                    if diff > 0:
237
                        viol_id += 1
238
                        overlimit.append((viol_id, "user", user, source,
239
                                          resource, qh_limit, qh_value))
240
                        relevant_resources = actual_resources[source][user]
241
                        handle_resource(viol_id, resource, relevant_resources,
242
                                        diff, actions)
243

    
244
        if not overlimit:
245
            write("No violations.\n")
246
            return
247

    
248
        headers = ("#", "Type", "Holder", "Source", "Resource", "Limit",
249
                   "Usage")
250
        pprint_table(self.stdout, overlimit, headers,
251
                     options["output_format"], title="Violations")
252

    
253
        if any(actions.values()):
254
            self.stdout.write("\n")
255
            if fix:
256
                if dangerous and not force:
257
                    write("You are enforcing resources that may permanently "
258
                          "remove a vm.\n")
259
                    self.confirm()
260
                write("Applying actions. Please wait...\n")
261
            title = "Applied Actions" if fix else "Suggested Actions"
262
            log = enforce.perform_actions(actions, maxops=maxops, fix=fix,
263
                                          options=opts)
264
            headers = ("Type", "ID", "State", "Backend", "Action", "Violation")
265
            if fix:
266
                headers += ("Result",)
267
            pprint_table(self.stdout, log, headers,
268
                         options["output_format"], title=title)