aboutsummaryrefslogtreecommitdiffstats
path: root/app/discover/events/event_port_add.py
blob: 92200159f7a60c1ea2a570dcf6542d055b0380ba (plain)
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
###############################################################################
# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems)   #
# and others                                                                  #
#                                                                             #
# All rights reserved. This program and the accompanying materials            #
# are made available under the terms of the Apache License, Version 2.0       #
# which accompanies this distribution, and is available at                    #
# http://www.apache.org/licenses/LICENSE-2.0                                  #
###############################################################################
import datetime

from discover.events.event_base import EventBase, EventResult
from discover.fetchers.api.api_fetch_host_instances import ApiFetchHostInstances
from discover.fetchers.cli.cli_fetch_instance_vnics import CliFetchInstanceVnics
from discover.fetchers.cli.cli_fetch_instance_vnics_vpp import \
    CliFetchInstanceVnicsVpp
from discover.fetchers.cli.cli_fetch_vservice_vnics import CliFetchVserviceVnics
from discover.link_finders.find_links_for_instance_vnics import \
    FindLinksForInstanceVnics
from discover.link_finders.find_links_for_vedges import FindLinksForVedges
from discover.scanner import Scanner


class EventPortAdd(EventBase):

    def get_name_by_id(self, object_id):
        item = self.inv.get_by_id(self.env, object_id)
        if item:
            return item['name']
        return None

    def add_port_document(self, env, project_name, project_id, network_name, network_id, port):
        # add other data for port document
        port['type'] = 'port'
        port['environment'] = env

        port['parent_id'] = port['network_id'] + '-ports'
        port['parent_text'] = 'Ports'
        port['parent_type'] = 'ports_folder'

        port['name'] = port['mac_address']
        port['object'] = port['name']
        port['project'] = project_name

        port['id_path'] = "{}/{}-projects/{}/{}-networks/{}/{}-ports/{}" \
                          .format(env, env,
                                  project_id, project_id,
                                  network_id, network_id, port['id'])
        port['name_path'] = "/{}/Projects/{}/Networks/{}/Ports/{}" \
                            .format(env, project_name, network_name, port['id'])

        port['show_in_tree'] = True
        port['last_scanned'] = datetime.datetime.utcnow()
        self.inv.set(port)
        self.log.info("add port document for port: {}".format(port['id']))

    def add_ports_folder(self, env, project_id, network_id, network_name):
        port_folder = {
            "id": network_id + "-ports",
            "create_object": True,
            "name": "Ports",
            "text": "Ports",
            "type": "ports_folder",
            "parent_id": network_id,
            "parent_type": "network",
            'environment': env,
            'id_path': "{}/{}-projects/{}/{}-networks/{}/{}-ports/"
                       .format(env, env, project_id, project_id,
                               network_id, network_id),
            'name_path': "/{}/Projects/{}/Networks/{}/Ports"
                       .format(env, project_id, network_name),
            "show_in_tree": True,
            "last_scanned": datetime.datetime.utcnow(),
            "object_name": "Ports",
        }
        self.inv.set(port_folder)
        self.log.info("add ports_folder document for network: {}.".format(network_id))

    def add_network_services_folder(self, env, project_id, network_id, network_name):
        network_services_folder = {
            "create_object": True,
            "environment": env,
            "id": network_id + "-network_services",
            "id_path": "{}/{}-projects/{}/{}-networks/{}/{}-network_services/"
                       .format(env, env, project_id, project_id,
                               network_id, network_id),
            "last_scanned": datetime.datetime.utcnow(),
            "name": "Network vServices",
            "name_path": "/{}/Projects/{}/Networks/{}/Network vServices"
                         .format(env, project_id, network_name),
            "object_name": "Network vServices",
            "parent_id": network_id,
            "parent_type": "network",
            "show_in_tree": True,
            "text": "Network vServices",
            "type": "network_services_folder"
        }
        self.inv.set(network_services_folder)
        self.log.info("add network services folder for network:{}".format(network_id))

    def add_dhcp_document(self, env, host, network_id, network_name):
        dhcp_document = {
            "environment": env,
            "host": host['id'],
            "id": "qdhcp-" + network_id,
            "id_path": "{}/{}-vservices/{}-vservices-dhcps/qdhcp-{}"
                       .format(host['id_path'], host['id'],
                               host['id'], network_id),
            "last_scanned": datetime.datetime.utcnow(),
            "local_service_id": "qdhcp-" + network_id,
            "name": "dhcp-" + network_name,
            "name_path": host['name_path'] + "/Vservices/DHCP servers/dhcp-" + network_name,
            "network": [network_id],
            "object_name": "dhcp-" + network_name,
            "parent_id": host['id'] + "-vservices-dhcps",
            "parent_text": "DHCP servers",
            "parent_type": "vservice_dhcps_folder",
            "service_type": "dhcp",
            "show_in_tree": True,
            "type": "vservice"
        }
        self.inv.set(dhcp_document)
        self.log.info("add DHCP document for network: {}.".format(network_id))

    # This method has dynamic usages, take caution when changing its signature
    def add_vnics_folder(self,
                         env, host,
                         object_id, network_name='',
                         object_type="dhcp", router_name=''):
        # when vservice is DHCP, id = network_id,
        # when vservice is router, id = router_id
        type_map = {"dhcp": ('DHCP servers', 'dhcp-' + network_name),
                    "router": ('Gateways', router_name)}

        vnics_folder = {
            "environment": env,
            "id": "q{}-{}-vnics".format(object_type, object_id),
            "id_path": "{}/{}-vservices/{}-vservices-{}s/q{}-{}/q{}-{}-vnics"
                       .format(host['id_path'], host['id'], host['id'],
                               object_type, object_type, object_id,
                               object_type, object_id),
            "last_scanned": datetime.datetime.utcnow(),
            "name": "q{}-{}-vnics".format(object_type, object_id),
            "name_path": "{}/Vservices/{}/{}/vNICs"
                         .format(host['name_path'],
                                 type_map[object_type][0],
                                 type_map[object_type][1]),
            "object_name": "vNICs",
            "parent_id": "q{}-{}".format(object_type, object_id),
            "parent_type": "vservice",
            "show_in_tree": True,
            "text": "vNICs",
            "type": "vnics_folder"
        }
        self.inv.set(vnics_folder)
        self.log.info("add vnics_folder document for q{}-{}-vnics"
                      .format(object_type, object_id))

    # This method has dynamic usages, take caution when changing its signature
    def add_vnic_document(self,
                          env, host,
                          object_id, network_name='',
                          object_type='dhcp', router_name='',
                          mac_address=None):
        # when vservice is DHCP, id = network_id,
        # when vservice is router, id = router_id
        type_map = {"dhcp": ('DHCP servers', 'dhcp-' + network_name),
                    "router": ('Gateways', router_name)}

        fetcher = CliFetchVserviceVnics()
        fetcher.set_env(env)
        namespace = 'q{}-{}'.format(object_type, object_id)
        vnic_documents = fetcher.handle_service(host['id'], namespace, enable_cache=False)
        if not vnic_documents:
            self.log.info("Vnic document not found in namespace.")
            return False

        if mac_address is not None:
            for doc in vnic_documents:
                if doc['mac_address'] == mac_address:
                    # add a specific vnic document.
                    doc["environment"] = env
                    doc["id_path"] = "{}/{}-vservices/{}-vservices-{}s/{}/{}-vnics/{}"\
                                     .format(host['id_path'], host['id'],
                                             host['id'], object_type, namespace,
                                             namespace, doc["id"])
                    doc["name_path"] = "{}/Vservices/{}/{}/vNICs/{}" \
                                       .format(host['name_path'],
                                               type_map[object_type][0],
                                               type_map[object_type][1],
                                               doc["id"])
                    self.inv.set(doc)
                    self.log.info("add vnic document with mac_address: {}."
                                  .format(mac_address))
                    return True

            self.log.info("Can not find vnic document by mac_address: {}"
                          .format(mac_address))
            return False
        else:
            for doc in vnic_documents:
                # add all vnic documents.
                doc["environment"] = env
                doc["id_path"] = "{}/{}-vservices/{}-vservices-{}s/{}/{}-vnics/{}" \
                                 .format(host['id_path'], host['id'],
                                         host['id'], object_type,
                                         namespace, namespace, doc["id"])
                doc["name_path"] = "{}/Vservices/{}/{}/vNICs/{}" \
                                   .format(host['name_path'],
                                           type_map[object_type][0],
                                           type_map[object_type][1],
                                           doc["id"])
                self.inv.set(doc)
                self.log.info("add vnic document with mac_address: {}."
                              .format(doc["mac_address"]))
            return True

    def handle_dhcp_device(self, env, notification, network_id, network_name, mac_address=None):
        # add dhcp vservice document.
        host_id = notification["publisher_id"].replace("network.", "", 1)
        host = self.inv.get_by_id(env, host_id)

        self.add_dhcp_document(env, host, network_id, network_name)

        # add vnics folder.
        self.add_vnics_folder(env, host, network_id, network_name)

        # add vnic document.
        self.add_vnic_document(env, host, network_id, network_name, mac_address=mac_address)

    def handle(self, env, notification):
        project = notification['_context_project_name']
        project_id = notification['_context_project_id']
        payload = notification['payload']
        port = payload['port']
        network_id = port['network_id']
        network_name = self.get_name_by_id(network_id)
        mac_address = port['mac_address']

        # check ports folder document.
        ports_folder = self.inv.get_by_id(env, network_id + '-ports')
        if not ports_folder:
            self.log.info("ports folder not found, add ports folder first.")
            self.add_ports_folder(env, project_id, network_id, network_name)
        self.add_port_document(env, project, project_id, network_name, network_id, port)

        # update the port related documents.
        if 'compute' in port['device_owner']:
            # update the instance related document.
            host_id = port['binding:host_id']
            instance_id = port['device_id']
            old_instance_doc = self.inv.get_by_id(env, instance_id)
            instances_root_id = host_id + '-instances'
            instances_root = self.inv.get_by_id(env, instances_root_id)
            if not instances_root:
                self.log.info('instance document not found, aborting port adding')
                return EventResult(result=False, retry=True)

            # update instance
            instance_fetcher = ApiFetchHostInstances()
            instance_fetcher.set_env(env)
            instance_docs = instance_fetcher.get(host_id + '-')
            instance = next(filter(lambda i: i['id'] == instance_id, instance_docs), None)

            if instance:
                old_instance_doc['network_info'] = instance['network_info']
                old_instance_doc['network'] = instance['network']
                if old_instance_doc.get('mac_address') is None:
                    old_instance_doc['mac_address'] = mac_address

                self.inv.set(old_instance_doc)
                self.log.info("update instance document")

            # add vnic document.
            if port['binding:vif_type'] == 'vpp':
                vnic_fetcher = CliFetchInstanceVnicsVpp()
            else:
                # set ovs as default type.
                vnic_fetcher = CliFetchInstanceVnics()

            vnic_fetcher.set_env(env)
            vnic_docs = vnic_fetcher.get(instance_id + '-')
            vnic = next(filter(lambda vnic: vnic['mac_address'] == mac_address, vnic_docs), None)

            if vnic:
                vnic['environment'] = env
                vnic['type'] = 'vnic'
                vnic['name_path'] = old_instance_doc['name_path'] + '/vNICs/' + vnic['name']
                vnic['id_path'] = '{}/{}/{}'.format(old_instance_doc['id_path'],
                                                    old_instance_doc['id'],
                                                    vnic['name'])
                self.inv.set(vnic)
                self.log.info("add instance-vnic document, mac_address: {}"
                              .format(mac_address))

            self.log.info("scanning for links")
            fetchers_implementing_add_links = [FindLinksForInstanceVnics(), FindLinksForVedges()]
            for fetcher in fetchers_implementing_add_links:
                fetcher.add_links()
            scanner = Scanner()
            scanner.set_env(env)
            scanner.scan_cliques()

        port_document = self.inv.get_by_id(env, port['id'])
        if not port_document:
            self.log.error("Port {} failed to add".format(port['id']))
            return EventResult(result=False, retry=True)

        return EventResult(result=True,
                           related_object=port['id'],
                           display_context=network_id)