Open Kilda Java Documentation
link_props_utils.py
Go to the documentation of this file.
1 # Copyright 2018 Telstra Open Source
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 
16 import logging
17 import textwrap
18 
19 from topologylistener import db
20 from topologylistener import exc
21 from topologylistener import model
22 
23 logger = logging.getLogger(__name__)
24 
25 
26 def create_if_missing(tx, *batch):
27  q = textwrap.dedent("""
28  MERGE (target:link_props {
29  src_switch: $src_switch,
30  src_port: $src_port,
31  dst_port: $dst_port,
32  dst_switch: $dst_switch})
33  ON CREATE SET
34  target.time_create=$time_create,
35  target.time_modify=$time_modify
36  ON MATCH SET target.time_modify=$time_modify""")
37 
38  for link in sorted(batch):
39  p = _make_match(link)
40  time_fields = [link.time_create, link.time_modify]
41  time_fields = [
42  str(x) if isinstance(x, model.TimeProperty) else x
43  for x in time_fields]
44  p['time_create'], p['time_modify'] = time_fields
45 
46  logger.info('Ensure link property %s exists', link)
47  db.log_query('create link props', q, p)
48  tx.run(q, p)
49 
50 
51 def read(tx, subject):
52  db_subject = fetch(tx, subject)
53  return model.LinkProps.new_from_db(db_subject)
54 
55 
56 def drop(tx, subject):
57  logger.info("Delete %s request", subject)
58  q = textwrap.dedent("""
59  MATCH (target:link_props)
60  WHERE target.src_switch=$src_switch,
61  AND target.src_port=$src_port,
62  AND target.dst_port=$dst_port,
63  AND target.dst_switch=$dst_switch
64  DELETE target
65  RETURN target""")
66  p = _make_match(subject)
67 
68  db.log_query('delete link props', q, p)
69  cursor = tx.run(q, p)
70  try:
71  db_subject = db.fetch_one(cursor)['target']
72  except exc.DBEmptyResponse:
73  raise exc.DBRecordNotFound(q, p)
74 
75  return model.LinkProps.new_from_db(db_subject)
76 
77 
78 def drop_by_mask(tx, mask):
79  logger.info('Delete link props by mask %s', mask)
80 
81  p = _make_match_by_mask(mask)
82  if not p:
84  mask, 'reject to drop all link props in DB')
85 
86  where = ['target.{0}=${0}'.format(x) for x in p]
87  q = 'MATCH (target:link_props)\n'
88  if where:
89  q += 'WHERE ' + '\n AND '.join(where)
90  q += '\nRETURN target, id(target) as ref'
91  db.log_query('pre delete link props fetch', q, p)
92 
93  refs = []
94  persistent = []
95  for db_record in tx.run(q, p):
96  persistent.append(model.LinkProps.new_from_db(db_record['target']))
97  refs.append(db_record['ref'])
98 
99  q = 'MATCH (target:link_props)\n'
100  q += 'WHERE id(target) in [{}]'.format(', '.join(str(x) for x in refs))
101  q += '\nDELETE target'
102  db.log_query('delete link props', q, {})
103  tx.run(q)
104 
105  return persistent
106 
107 
109  origin, updated_props = set_props(tx, subject)
110  push_props_to_isl(tx, subject, *updated_props)
111  return origin
112 
113 
114 def set_props(tx, subject):
115  db_subject = fetch(tx, subject)
116  origin, update = db.locate_changes(db_subject, subject.props_db_view())
117  if update:
118  q = textwrap.dedent("""
119  MATCH (target:link_props)
120  WHERE id(target)=$target_id
121  """) + db.format_set_fields(
122  db.escape_fields(update), field_prefix='target.')
123  p = {'target_id': db.neo_id(db_subject)}
124 
125  db.log_query('propagate link props to ISL', q, p)
126  tx.run(q, p)
127 
128  return origin, update.keys()
129 
130 
131 # low level DB operations
132 
133 
134 def push_props_to_isl(tx, subject, *fields):
135  if not fields:
136  return
137 
138  copy_fields = {
139  name: 'source.' + name for name in fields}
140  q = textwrap.dedent("""
141  MATCH (source:link_props)
142  WHERE source.src_switch = $src_switch
143  AND source.src_port = $src_port
144  AND source.dst_switch = $dst_switch
145  AND source.dst_port = $dst_port
146  MATCH
147  (:switch {name: source.src_switch})
148  -
149  [target:isl {
150  src_switch: source.src_switch,
151  src_port: source.src_port,
152  dst_switch: source.dst_switch,
153  dst_port: source.dst_port
154  }]
155  ->
156  (:switch {name: source.dst_switch})
157  """) + db.format_set_fields(
158  db.escape_fields(copy_fields, raw_values=True),
159  field_prefix='target.')
160  p = _make_match(subject)
161  db.log_query('link props to ISL', q, p)
162  tx.run(q, p)
163 
164 
165 def fetch(tx, subject):
166  p = _make_match(subject)
167  q = textwrap.dedent("""
168  MATCH (target:link_props {
169  src_switch: $src_switch,
170  src_port: $src_port,
171  dst_switch: $dst_switch,
172  dst_port: $dst_port})
173  RETURN target""")
174 
175  db.log_query('link props update', q, p)
176  cursor = tx.run(q, p)
177 
178  try:
179  db_object = db.fetch_one(cursor)['target']
180  except exc.DBEmptyResponse:
181  raise exc.DBRecordNotFound(q, p)
182 
183  return db_object
184 
185 
186 def _make_match_by_mask(mask):
187  match = {}
188  for endpoint, prefix in (
189  (mask.source, 'src_'),
190  (mask.dest, 'dst_')):
191  if not endpoint:
192  continue
193  match.update(_make_endpoint_match(endpoint, prefix))
194 
195  return {k: v for k, v in match.items() if v is not None}
196 
197 
198 def _make_match(subject):
199  match = _make_endpoint_match(subject.source, 'src_')
200  match.update(_make_endpoint_match(subject.dest, 'dst_'))
201 
202  masked_fields = {k for k in match if match[k] is None}
203  if masked_fields:
205  subject, 'Match field(s) without value: {}'.format(
206  ', '.join(repr(x) for x in sorted(masked_fields))))
207  return match
208 
209 
210 def _make_endpoint_match(endpoint, prefix):
211  return {
212  prefix + 'switch': endpoint.dpid,
213  prefix + 'port': endpoint.port}