1 # Copyright 2020 ArctosLabs Scandinavia AB
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
20 from jinja2
import Environment
21 from jinja2
.loaders
import FileSystemLoader
, PackageLoader
, ChoiceLoader
24 class MznPlacementConductor(object):
26 Knows how to process placement req using minizinc
29 if platform
.system() == "Windows":
30 default_mzn_path
= "C:\\Program Files\\MiniZinc IDE (bundled)\\minizinc.exe"
32 default_mzn_path
= "/minizinc/bin/minizinc"
34 def __init__(self
, log
, mzn_path
=default_mzn_path
):
35 pymzn
.config
["minizinc"] = mzn_path
37 log
# FIXME what to log (besides forwarding it to MznModelGenerator) here?
40 def _run_placement_model(self
, mzn_model
, ns_desc
, mzn_model_data
={}):
42 Runs the minizinc placement model and post process the result
43 Note: in this revision we use the 'item' output mode from pymzn.minizinc since it ease
44 post processing of the solutions when we use enumerations in mzn_model
45 Note: minizinc does not support '-' in identifiers and therefore we convert back from use of '_' when we
47 Note: minizinc does not support identifiers starting with numbers and therefore we skip the leading 'vim_'
48 when we process the result
50 :param mzn_model: a minizinc model as str (note: may also be path to .mzn file)
51 :param ns_desc: network service descriptor, carries information about pinned VNFs so those can be included in
53 :param mzn_model_data: minizinc model data dictionary (typically not used with our models)
54 :return: list of dicts formatted as {'vimAccountId': '<account id>', 'member-vnf-index': <'index'>}
55 or formatted as [{}] if unsatisfiable model
57 solns
= pymzn
.minizinc(mzn_model
, data
=mzn_model_data
, output_mode
="item")
59 if "UNSATISFIABLE" in str(solns
):
62 solns_as_str
= str(solns
[0])
64 # make it easier to extract the desired information by cleaning from newline, whitespace etc.
65 solns_as_str
= solns_as_str
.replace("\n", "").replace(" ", "").rstrip(";")
67 vnf_vim_mapping
= (e
.split("=") for e
in solns_as_str
.split(";"))
70 {"vimAccountId": e
[1][3:].replace("_", "-"), "member-vnf-index": e
[0][3:]}
71 for e
in vnf_vim_mapping
76 "vimAccountId": e
["vim_account"][3:].replace("_", "-"),
77 "member-vnf-index": e
["vnf_id"],
80 if "vim_account" in e
.keys()
85 def do_placement_computation(self
, nspd
):
87 Orchestrates the placement computation
89 :param nspd: placement data
90 :return: see _run_placement_model
92 mzn_model
= MznModelGenerator(self
.log
).create_model(nspd
)
93 return self
._run
_placement
_model
(mzn_model
, nspd
["ns_desc"])
96 class MznModelGenerator(object):
98 Has the capability to generate minizinc models from information contained in
99 NsPlacementData objects. Uses jinja2 as templating language for the model
102 default_j2_template
= "osm_pla_dynamic_template.j2"
103 template_search_path
= [
106 "/pla/osm_pla/placement",
108 "/usr/lib/python3/dist-packages/osm_pla/placement",
111 def __init__(self
, log
):
115 self
.log
= log
# FIXME we do not log anything so far
117 def create_model(self
, ns_placement_data
):
119 Creates a minizinc model according to the content of nspd
120 nspd - NSPlacementData
123 self
.log
.info("ns_desc: {}".format(ns_placement_data
["ns_desc"]))
124 self
.log
.info("vld_desc: {}".format(ns_placement_data
["vld_desc"]))
125 mzn_model_template
= self
._load
_jinja
_template
()
126 mzn_model
= mzn_model_template
.render(ns_placement_data
)
127 self
.log
.info("Minizinc model: {}".format(mzn_model
))
130 def _load_jinja_template(self
, template_name
=default_j2_template
):
131 """loads the jinja template used for model generation"""
132 loader1
= FileSystemLoader(MznModelGenerator
.template_search_path
)
133 loader2
= PackageLoader("osm_pla", ".")
134 env
= Environment(loader
=ChoiceLoader([loader1
, loader2
]))
135 return env
.get_template(template_name
)
138 class NsPlacementDataFactory(object):
140 process information an network service and applicable network infrastructure resources in order to produce
141 information tailored for the minizinc model code generator
151 order_constraints
=None,
154 :param vim_accounts_info: a dictionary with vim url as key and id as value, we add a unique index to it for use
155 in the mzn array constructs and adjust the value of the id to minizinc acceptable identifier syntax
156 :param vnf_prices: a dictionary with 'vnfd-id-ref' as key and a dictionary with vim_urls: cost as value
157 :param nsd: the network service descriptor
158 :param pil_info: price list and metrics for PoP interconnection links
159 :param pinning: list of {'member-vnf-index': '<idx>', 'vim_account': '<vim-account>'}
160 :param order_constraints: any constraints provided at instantiation time
162 next_idx
= itertools
.count()
163 self
._vim
_accounts
_info
= {
164 k
: {"id": "vim" + v
.replace("-", "_"), "idx": next(next_idx
)}
165 for k
, v
in vim_accounts_info
.items()
167 self
._vnf
_prices
= vnf_prices
169 self
._pil
_info
= pil_info
170 self
._pinning
= pinning
171 self
._order
_constraints
= order_constraints
173 def _produce_trp_link_characteristics_data(self
, characteristics
):
175 :param characteristics: one of {pil_latency, pil_price, pil_jitter}
176 :return: 2d array of requested trp_link characteristics data
178 if characteristics
not in {"pil_latency", "pil_price", "pil_jitter"}:
179 raise Exception("characteristic '{}' not supported".format(characteristics
))
180 num_vims
= len(self
._vim
_accounts
_info
)
181 trp_link_characteristics
= [
182 [0 if col
== row
else 0x7FFF for col
in range(num_vims
)]
183 for row
in range(num_vims
)
185 for pil
in self
._pil
_info
["pil"]:
186 if characteristics
in pil
.keys():
187 ep1
= pil
["pil_endpoints"][0]
188 ep2
= pil
["pil_endpoints"][1]
189 # only consider links between applicable vims
190 if ep1
in self
._vim
_accounts
_info
and ep2
in self
._vim
_accounts
_info
:
191 idx1
= self
._vim
_accounts
_info
[ep1
]["idx"]
192 idx2
= self
._vim
_accounts
_info
[ep2
]["idx"]
193 trp_link_characteristics
[idx1
][idx2
] = pil
[characteristics
]
194 trp_link_characteristics
[idx2
][idx1
] = pil
[characteristics
]
196 return trp_link_characteristics
198 # TODO: Review if we should adapt this method with SOL006 /nsd/vnfd/int-virtual-link-desc/df/qos fields.
199 def _produce_vld_desc(self
):
201 Creates the expected vlds from the nsd. Includes constraints if part of nsd.
202 Overrides constraints with any syntactically correct instantiation parameters
206 all_vld_member_vnf_index_refs
= {}
207 # TODO: Change for multiple DF support
208 ns_df
= self
._nsd
.get("df", [{}])[0]
209 for vnf_profile
in ns_df
.get("vnf-profile", []):
210 for vlc
in vnf_profile
.get("virtual-link-connectivity", []):
211 vld_id
= vlc
.get("virtual-link-profile-id")
212 vld_member_vnf_index_ref
= vnf_profile
.get("id")
213 if vld_id
in all_vld_member_vnf_index_refs
:
214 all_vld_member_vnf_index_refs
[vld_id
].append(
215 vld_member_vnf_index_ref
218 all_vld_member_vnf_index_refs
[vld_id
] = [vld_member_vnf_index_ref
]
221 for vld
in self
._nsd
.get("virtual-link-desc", ()):
222 if vld
.get("mgmt-network", False) is True:
225 cp_refs
= all_vld_member_vnf_index_refs
[vld
.get("id")]
226 if len(cp_refs
) == 2:
227 vld_desc_entry
["cp_refs"] = cp_refs
228 # TODO: Change for multiple DF support
229 vld_df
= vld
.get("df", [{}])[0]
230 for constraint
in vld_df
.get("qos", {}):
231 if constraint
== "latency":
232 vld_desc_entry
["latency"] = vld_df
["qos"][constraint
]
233 elif constraint
== "packet-delay-variation":
234 vld_desc_entry
["jitter"] = vld_df
["qos"][constraint
]
235 vld_desc
.append(vld_desc_entry
)
237 # create candidates from instantiate params
238 if self
._order
_constraints
is not None:
239 candidate_vld_desc
= []
240 # use id to find the endpoints in the nsd
241 for entry
in self
._order
_constraints
.get("vld-constraints"):
242 for vld
in self
._nsd
.get("virtual-link-desc", ()):
243 if entry
["id"] == vld
.get("id"):
244 vld_desc_instantiate_entry
= {}
245 cp_refs
= all_vld_member_vnf_index_refs
[vld
.get("id")]
246 vld_desc_instantiate_entry
["cp_refs"] = cp_refs
247 # add whatever constraints that are provided to the vld_desc_entry
248 # misspelled 'link-constraints' => empty dict
249 # lack (or misspelling) of one or both supported constraints => entry not appended
250 for constraint
, value
in entry
.get(
251 "link-constraints", {}
253 if constraint
== "latency":
254 vld_desc_instantiate_entry
["latency"] = value
255 elif constraint
== "jitter":
256 vld_desc_instantiate_entry
["jitter"] = value
257 if {"latency", "jitter"}.intersection(
258 vld_desc_instantiate_entry
.keys()
260 candidate_vld_desc
.append(vld_desc_instantiate_entry
)
261 # merge with nsd originated, FIXME log any deviations?
262 for vld_d
in vld_desc
:
263 for vld_d_i
in candidate_vld_desc
:
264 if set(vld_d
["cp_refs"]) == set(vld_d_i
["cp_refs"]):
265 if vld_d_i
.get("jitter"):
266 vld_d
["jitter"] = vld_d_i
["jitter"]
267 if vld_d_i
.get("latency"):
268 vld_d
["latency"] = vld_d_i
["latency"]
272 def _produce_ns_desc(self
):
274 collect information for the ns_desc part of the placement data
275 for the vim_accounts that are applicable, collect the vnf_price
278 # TODO: Change for multiple DF support
279 ns_df
= self
._nsd
.get("df", [{}])[0]
280 for vnf_profile
in ns_df
.get("vnf-profile", []):
281 vnf_info
= {"vnf_id": vnf_profile
["id"]}
283 prices_for_vnfd
= self
._vnf
_prices
[vnf_profile
["vnfd-id"]]
284 # the list of prices must be ordered according to the indexing of the vim_accounts
285 price_list
= [_
for _
in range(len(self
._vim
_accounts
_info
))]
286 for k
in prices_for_vnfd
.keys():
287 if k
in self
._vim
_accounts
_info
.keys():
288 price_list
[self
._vim
_accounts
_info
[k
]["idx"]] = prices_for_vnfd
[k
]
289 vnf_info
["vnf_price_per_vim"] = price_list
292 if self
._pinning
is not None:
293 for pinned_vnf
in self
._pinning
:
294 if vnf_profile
["id"] == pinned_vnf
["member-vnf-index"]:
295 vnf_info
["vim_account"] = "vim" + pinned_vnf
[
299 ns_desc
.append(vnf_info
)
302 def create_ns_placement_data(self
):
303 """populate NsPlacmentData object"""
304 ns_placement_data
= {
307 for _
, vim_data
in sorted(
308 self
._vim
_accounts
_info
.items(), key
=lambda item
: item
[1]["idx"]
311 "trp_link_latency": self
._produce
_trp
_link
_characteristics
_data
(
314 "trp_link_jitter": self
._produce
_trp
_link
_characteristics
_data
(
317 "trp_link_price_list": self
._produce
_trp
_link
_characteristics
_data
(
320 "ns_desc": self
._produce
_ns
_desc
(),
321 "vld_desc": self
._produce
_vld
_desc
(),
322 "generator_data": {"file": __file__
, "time": datetime
.datetime
.now()},
325 return ns_placement_data