root / snf-cyclades-app / synnefo / tools / add_unique_name_to_nics.py @ 88d998b9
History | View | Annotate | Download (6.5 kB)
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)
|