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