Revision 88d998b9
b/snf-cyclades-app/synnefo/tools/add_unique_name_to_nics.py | ||
---|---|---|
1 |
#!/usr/bin/env python |
|
2 |
"""Tool to update Ganeti instances: |
|
3 |
|
|
4 |
* add unique name to the NICs of all Ganeti instances |
|
5 |
* rename all instance tags related with network firewall profiles to include |
|
6 |
the unique name of the corresponding NIC. |
|
7 |
|
|
8 |
The name for each NIC is based on the PK of the NIC in Cyclades DB. |
|
9 |
""" |
|
10 |
|
|
11 |
FIREWALL_TAGS_PREFIX = "synnefo:network:" |
|
12 |
FIREWALL_TAGS = {"ENABLED": "synnefo:network:%s:protected", |
|
13 |
"DISABLED": "synnefo:network:%s:unprotected", |
|
14 |
"PROTECTED": "synnefo:network:%s:limited"} |
|
15 |
|
|
16 |
# Gevent patching |
|
17 |
import gevent |
|
18 |
from gevent import monkey |
|
19 |
monkey.patch_all() |
|
20 |
|
|
21 |
import sys |
|
22 |
import subprocess |
|
23 |
from optparse import OptionParser, TitledHelpFormatter |
|
24 |
|
|
25 |
# Configure Django env |
|
26 |
from synnefo import settings |
|
27 |
from django.core.management import setup_environ |
|
28 |
setup_environ(settings) |
|
29 |
|
|
30 |
from django.db import close_connection |
|
31 |
from synnefo.db.models import Backend, pooled_rapi_client |
|
32 |
from synnefo.management.common import get_backend |
|
33 |
|
|
34 |
import logging |
|
35 |
logger = logging.getLogger("migrate_nics") |
|
36 |
handler = logging.StreamHandler() |
|
37 |
|
|
38 |
formatter = logging.Formatter("[%(levelname)s] %(message)s") |
|
39 |
handler.setFormatter(formatter) |
|
40 |
logger.setLevel(logging.DEBUG) |
|
41 |
logger.addHandler(handler) |
|
42 |
logger.propagate = False |
|
43 |
|
|
44 |
DESCRIPTION = """\ |
|
45 |
Tool to update all Ganeti instances in order to add a unique name to NICs of |
|
46 |
all instances and rename the instance firewall tags to include the NIC name. |
|
47 |
""" |
|
48 |
|
|
49 |
|
|
50 |
def main(): |
|
51 |
parser = OptionParser(description=DESCRIPTION, |
|
52 |
formatter=TitledHelpFormatter()) |
|
53 |
parser.add_option("--backend-id", dest="backend_id", |
|
54 |
help="Update instances only of this Ganeti backend."), |
|
55 |
parser.add_option("--dry-run", dest="dry_run", default=False, |
|
56 |
action="store_true", |
|
57 |
help="Do not send any jobs to Ganeti backend.") |
|
58 |
parser.add_option("--ganeti-dry-run", dest="ganeti_dry_run", default=False, |
|
59 |
action="store_true", |
|
60 |
help="Pass --dry-run option to Ganeti jobs.") |
|
61 |
parser.add_option("--parallel", dest="parallel", default=False, |
|
62 |
action="store_true", |
|
63 |
help="Use a seperate process for each backend.") |
|
64 |
parser.add_option("-d", "--debug", dest="debug", default=False, |
|
65 |
action="store_true", |
|
66 |
help="Display debug information.") |
|
67 |
options, args = parser.parse_args() |
|
68 |
|
|
69 |
if options.backend_id: |
|
70 |
backends = [get_backend(options.backend_id)] |
|
71 |
else: |
|
72 |
if Backend.objects.filter(offline=True).exists(): |
|
73 |
msg = "Can not update intances. An 'offline' backend exists." |
|
74 |
raise Exception(msg) |
|
75 |
backends = Backend.objects.all() |
|
76 |
|
|
77 |
if options.debug: |
|
78 |
logger.setLevel(logging.DEBUG) |
|
79 |
|
|
80 |
if len(backends) > 1 and options.parallel: |
|
81 |
cmd = sys.argv |
|
82 |
processes = [] |
|
83 |
for backend in backends: |
|
84 |
p = subprocess.Popen(cmd + ["--backend-id=%s" % backend.id]) |
|
85 |
processes.append(p) |
|
86 |
for p in processes: |
|
87 |
p.wait() |
|
88 |
return |
|
89 |
else: |
|
90 |
[upgrade_backend(b, options.dry_run, options.ganeti_dry_run) |
|
91 |
for b in backends] |
|
92 |
return |
|
93 |
|
|
94 |
|
|
95 |
def upgrade_backend(backend, dry_run, ganeti_dry_run): |
|
96 |
jobs = [] |
|
97 |
instances_ids = get_instances_with_anonymous_nics(backend) |
|
98 |
for vm in backend.virtual_machines.filter(id__in=instances_ids): |
|
99 |
jobs.append(gevent.spawn(upgrade_vm, vm, dry_run, ganeti_dry_run)) |
|
100 |
|
|
101 |
if jobs: |
|
102 |
for job_chunk in [jobs[x:x+25] for x in range(0, len(jobs), 25)]: |
|
103 |
gevent.joinall(jobs) |
|
104 |
else: |
|
105 |
logger.info("No anonymous NICs in backend '%s'. Nothing to do!", |
|
106 |
backend.clustername) |
|
107 |
return |
|
108 |
|
|
109 |
|
|
110 |
def get_instances_with_anonymous_nics(backend): |
|
111 |
"""Get all Ganeti instances that have NICs without names.""" |
|
112 |
with pooled_rapi_client(backend) as rc: |
|
113 |
instances = rc.GetInstances(bulk=True) |
|
114 |
# Filter snf- instances |
|
115 |
instances = filter(lambda i: |
|
116 |
i["name"].startswith(settings.BACKEND_PREFIX_ID), |
|
117 |
instances) |
|
118 |
# Filter instances with anonymous NICs |
|
119 |
instances = filter(lambda i: None in i["nic.names"], instances) |
|
120 |
# Get IDs of those instances |
|
121 |
instances_ids = map(lambda i: |
|
122 |
i["name"].replace(settings.BACKEND_PREFIX_ID, "", 1), |
|
123 |
instances) |
|
124 |
return instances_ids |
|
125 |
|
|
126 |
|
|
127 |
def upgrade_vm(vm, dry_run, ganeti_dry_run): |
|
128 |
"""Add names to Ganeti NICs and update firewall Tags.""" |
|
129 |
logger.info("Updating NICs of instance %s" % vm.backend_vm_id) |
|
130 |
index_to_uuid = {} |
|
131 |
new_tags = [] |
|
132 |
# Compute new NICs names and firewall tags |
|
133 |
for nic in vm.nics.all(): |
|
134 |
if nic.index is None: |
|
135 |
msg = ("Cannot update NIC '%s'. The index of the NIC is unknown." |
|
136 |
" Please run snf-manage reconcile-servers --fix-all and" |
|
137 |
" retry!") |
|
138 |
logger.critical(msg) |
|
139 |
continue |
|
140 |
uuid = nic.backend_uuid |
|
141 |
# Map index -> UUID |
|
142 |
index_to_uuid[nic.index] = uuid |
|
143 |
|
|
144 |
# New firewall TAG with UUID |
|
145 |
firewall_profile = nic.firewall_profile |
|
146 |
if firewall_profile and firewall_profile != "DISABLED": |
|
147 |
firewall_tag = FIREWALL_TAGS[nic.firewall_profile] % uuid |
|
148 |
new_tags.append(firewall_tag) |
|
149 |
|
|
150 |
renamed_nics = [("modify", index, {"name": name}) |
|
151 |
for index, name in index_to_uuid.items()] |
|
152 |
|
|
153 |
instance = vm.backend_vm_id |
|
154 |
with pooled_rapi_client(vm) as rc: |
|
155 |
# Delete old Tags |
|
156 |
tags = rc.GetInstanceTags(instance) |
|
157 |
delete_tags = [t for t in tags if t.startswith(FIREWALL_TAGS_PREFIX)] |
|
158 |
if delete_tags: |
|
159 |
logger.debug("Deleting tags '%s' from instance '%s'", |
|
160 |
delete_tags, vm.backend_vm_id) |
|
161 |
if not dry_run: |
|
162 |
rc.DeleteInstanceTags(instance, delete_tags, |
|
163 |
dry_run=ganeti_dry_run) |
|
164 |
|
|
165 |
# Add new Tags |
|
166 |
if new_tags: |
|
167 |
logger.debug("Adding new tags '%s' to instance '%s'", |
|
168 |
new_tags, vm.backend_vm_id) |
|
169 |
if not dry_run: |
|
170 |
rc.AddInstanceTags(instance, new_tags, dry_run=ganeti_dry_run) |
|
171 |
|
|
172 |
# Add names to NICs |
|
173 |
logger.debug("Modifying NICs of instance '%s'. New NICs: '%s'", |
|
174 |
vm.backend_vm_id, renamed_nics) |
|
175 |
if not dry_run: |
|
176 |
rc.ModifyInstance(vm.backend_vm_id, |
|
177 |
nics=renamed_nics, dry_run=ganeti_dry_run) |
|
178 |
close_connection() |
|
179 |
|
|
180 |
|
|
181 |
if __name__ == "__main__": |
|
182 |
main() |
|
183 |
sys.exit(0) |
Also available in: Unified diff