vFW and vDNS support added to azure-plugin
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / extensions / aria_extension_tosca / simple_v1_0 / data_types.py
1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements.  See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License.  You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 import re
17 from datetime import (datetime, tzinfo, timedelta)
18 try:
19     from functools import total_ordering
20 except ImportError:
21     from total_ordering import total_ordering
22
23 from aria.parser import implements_specification
24 from aria.utils.collections import (StrictDict, OrderedDict)
25 from aria.utils.formatting import safe_repr
26
27 from .modeling.data_types import (coerce_to_data_type_class, report_issue_for_bad_format,
28                                   coerce_value)
29
30
31 class Timezone(tzinfo):
32     """
33     Timezone as fixed offset in hours and minutes east of UTC.
34     """
35
36     def __init__(self, hours=0, minutes=0):
37         super(Timezone, self).__init__()
38         self._offset = timedelta(hours=hours, minutes=minutes)
39
40     def utcoffset(self, dt): # pylint: disable=unused-argument
41         return self._offset
42
43     def tzname(self, dt): # pylint: disable=unused-argument
44         return str(self._offset)
45
46     def dst(self, dt): # pylint: disable=unused-argument
47         return Timezone._ZERO
48
49     _ZERO = timedelta(0)
50
51
52 UTC = Timezone()
53
54
55 @total_ordering
56 @implements_specification('timestamp', 'yaml-1.1')
57 class Timestamp(object):
58     '''
59     TOSCA timestamps follow the YAML specification, which in turn is a variant of ISO8601.
60
61     Long forms and short forms (without time of day and assuming UTC timezone) are supported for
62     parsing. The canonical form (for rendering) matches the long form at the UTC timezone.
63
64     See the `Timestamp Language-Independent Type for YAML Version 1.1 (Working Draft 2005-01-18)
65     <http://yaml.org/type/timestamp.html>`__
66     '''
67
68     REGULAR_SHORT = r'^(?P<year>[0-9][0-9][0-9][0-9])-(?P<month>[0-9][0-9])-(?P<day>[0-9][0-9])$'
69     REGULAR_LONG = \
70         r'^(?P<year>[0-9][0-9][0-9][0-9])-(?P<month>[0-9][0-9]?)-(?P<day>[0-9][0-9]?)' + \
71         r'([Tt]|[ \t]+)' \
72         r'(?P<hour>[0-9][0-9]?):(?P<minute>[0-9][0-9]):(?P<second>[0-9][0-9])' + \
73         r'(?P<fraction>\.[0-9]*)?' + \
74         r'(([ \t]*)Z|(?P<tzhour>[-+][0-9][0-9])?(:(?P<tzminute>[0-9][0-9])?)?)?$'
75     CANONICAL = '%Y-%m-%dT%H:%M:%S'
76
77     def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
78         value = str(value)
79         match = re.match(Timestamp.REGULAR_SHORT, value)
80         if match is not None:
81             # Parse short form
82             year = int(match.group('year'))
83             month = int(match.group('month'))
84             day = int(match.group('day'))
85             self.value = datetime(year, month, day, tzinfo=UTC)
86         else:
87             match = re.match(Timestamp.REGULAR_LONG, value)
88             if match is not None:
89                 # Parse long form
90                 year = int(match.group('year'))
91                 month = int(match.group('month'))
92                 day = int(match.group('day'))
93                 hour = match.group('hour')
94                 if hour is not None:
95                     hour = int(hour)
96                 minute = match.group('minute')
97                 if minute is not None:
98                     minute = int(minute)
99                 second = match.group('second')
100                 if second is not None:
101                     second = int(second)
102                 fraction = match.group('fraction')
103                 if fraction is not None:
104                     fraction = int(float(fraction) * 1000000.0) # convert to microseconds
105                 tzhour = match.group('tzhour')
106                 if tzhour is not None:
107                     tzhour = int(tzhour)
108                 else:
109                     tzhour = 0
110                 tzminute = match.group('tzminute')
111                 if tzminute is not None:
112                     tzminute = int(tzminute)
113                 else:
114                     tzminute = 0
115                 self.value = datetime(year, month, day, hour, minute, second, fraction,
116                                       Timezone(tzhour, tzminute))
117             else:
118                 raise ValueError(
119                     'timestamp must be formatted as YAML ISO8601 variant or "YYYY-MM-DD": %s'
120                     % safe_repr(value))
121
122     @property
123     def as_datetime_utc(self):
124         return self.value.astimezone(UTC)
125
126     @property
127     def as_raw(self):
128         return self.__str__()
129
130     def __str__(self):
131         the_datetime = self.as_datetime_utc
132         return '%s%sZ' \
133             % (the_datetime.strftime(Timestamp.CANONICAL), Timestamp._fraction_as_str(the_datetime))
134
135     def __repr__(self):
136         return repr(self.__str__())
137
138     def __eq__(self, timestamp):
139         if not isinstance(timestamp, Timestamp):
140             return False
141         return self.value == timestamp.value
142
143     def __lt__(self, timestamp):
144         return self.value < timestamp.value
145
146     @staticmethod
147     def _fraction_as_str(the_datetime):
148         return '{0:g}'.format(the_datetime.microsecond / 1000000.0).lstrip('0')
149
150
151 @total_ordering
152 @implements_specification('3.2.2', 'tosca-simple-1.0')
153 class Version(object):
154     """
155     TOSCA supports the concept of "reuse" of type definitions, as well as template definitions which
156     could be version and change over time. It is important to provide a reliable, normative means to
157     represent a version string which enables the comparison and management of types and templates
158     over time. Therefore, the TOSCA TC intends to provide a normative version type (string) for this
159     purpose in future Working Drafts of this specification.
160
161     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
162     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
163     #TYPE_TOSCA_VERSION>`__
164     """
165
166     REGEX = \
167         r'^(?P<major>\d+)\.(?P<minor>\d+)(\.(?P<fix>\d+)' + \
168         r'((\.(?P<qualifier>\d+))(\-(?P<build>\d+))?)?)?$'
169
170     @staticmethod
171     def key(version):
172         """
173         Key method for fast sorting.
174         """
175         return (version.major, version.minor, version.fix, version.qualifier, version.build)
176
177     def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
178         str_value = str(value)
179         match = re.match(Version.REGEX, str_value)
180         if match is None:
181             raise ValueError(
182                 'version must be formatted as <major_version>.<minor_version>'
183                 '[.<fix_version>[.<qualifier>[-<build_version]]]: %s'
184                 % safe_repr(value))
185
186         self.value = str_value
187
188         self.major = match.group('major')
189         self.major = int(self.major)
190         self.minor = match.group('minor')
191         self.minor = int(self.minor)
192         self.fix = match.group('fix')
193         if self.fix is not None:
194             self.fix = int(self.fix)
195         self.qualifier = match.group('qualifier')
196         if self.qualifier is not None:
197             self.qualifier = int(self.qualifier)
198         self.build = match.group('build')
199         if self.build is not None:
200             self.build = int(self.build)
201
202     @property
203     def as_raw(self):
204         return self.value
205
206     def __str__(self):
207         return self.value
208
209     def __repr__(self):
210         return repr(self.__str__())
211
212     def __eq__(self, version):
213         if not isinstance(version, Version):
214             return False
215         return (self.major, self.minor, self.fix, self.qualifier, self.build) == \
216             (version.major, version.minor, version.fix, version.qualifier, version.build)
217
218     def __lt__(self, version):
219         if self.major < version.major:
220             return True
221         elif self.major == version.major:
222             if self.minor < version.minor:
223                 return True
224             elif self.minor == version.minor:
225                 if self.fix < version.fix:
226                     return True
227                 elif self.fix == version.fix:
228                     if self.qualifier < version.qualifier:
229                         return True
230                     elif self.qualifier == version.qualifier:
231                         if self.build < version.build:
232                             return True
233         return False
234
235
236 @implements_specification('3.2.3', 'tosca-simple-1.0')
237 class Range(object):
238     """
239     The range type can be used to define numeric ranges with a lower and upper boundary. For
240     example, this allows for specifying a range of ports to be opened in a firewall.
241
242     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
243     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
244     #TYPE_TOSCA_RANGE>`__
245     """
246
247     def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
248         if not isinstance(value, list):
249             raise ValueError('range value is not a list: %s' % safe_repr(value))
250         if len(value) != 2:
251             raise ValueError('range value does not have exactly 2 elements: %s' % safe_repr(value))
252
253         def is_int(v):
254             return isinstance(v, int) and (not isinstance(v, bool)) # In Python bool is an int
255
256         if not is_int(value[0]):
257             raise ValueError('lower bound of range is not a valid integer: %s'
258                              % safe_repr(value[0]))
259
260         if value[1] != 'UNBOUNDED':
261             if not is_int(value[1]):
262                 raise ValueError('upper bound of range is not a valid integer or "UNBOUNDED": %s'
263                                  % safe_repr(value[0]))
264
265             if value[0] >= value[1]:
266                 raise ValueError(
267                     'upper bound of range is not greater than the lower bound: %s >= %s'
268                     % (safe_repr(value[0]), safe_repr(value[1])))
269
270         self.value = value
271
272     def is_in(self, value):
273         if value < self.value[0]:
274             return False
275         if (self.value[1] != 'UNBOUNDED') and (value > self.value[1]):
276             return False
277         return True
278
279     @property
280     def as_raw(self):
281         return list(self.value)
282
283
284 @implements_specification('3.2.4', 'tosca-simple-1.0')
285 class List(list):
286     """
287     The list type allows for specifying multiple values for a parameter of property. For example, if
288     an application allows for being configured to listen on multiple ports, a list of ports could be
289     configured using the list data type.
290
291     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
292     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
293     #TYPE_TOSCA_LIST>`__
294     """
295
296     @staticmethod
297     def _create(context, presentation, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
298         if not isinstance(value, list):
299             raise ValueError('"list" data type value is not a list: %s' % safe_repr(value))
300
301         entry_schema_type = entry_schema._get_type(context)
302         entry_schema_constraints = entry_schema.constraints
303
304         the_list = List()
305         for v in value:
306             v = coerce_value(context, presentation, entry_schema_type, None,
307                              entry_schema_constraints, v, aspect)
308             if v is not None:
309                 the_list.append(v)
310
311         return the_list
312
313     # Can't define as property because it's old-style Python class
314     def as_raw(self):
315         return list(self)
316
317
318 @implements_specification('3.2.5', 'tosca-simple-1.0')
319 class Map(StrictDict):
320     """
321     The map type allows for specifying multiple values for a parameter of property as a map. In
322     contrast to the list type, where each entry can only be addressed by its index in the list,
323     entries in a map are named elements that can be addressed by their keys.
324
325     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
326     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
327     #TYPE_TOSCA_MAP>`__
328     """
329
330     @staticmethod
331     def _create(context, presentation, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
332         if not isinstance(value, dict):
333             raise ValueError('"map" data type value is not a dict: %s' % safe_repr(value))
334
335         if entry_schema is None:
336             raise ValueError('"map" data type does not define "entry_schema"')
337
338         entry_schema_type = entry_schema._get_type(context)
339         entry_schema_constraints = entry_schema.constraints
340
341         the_map = Map()
342         for k, v in value.iteritems():
343             v = coerce_value(context, presentation, entry_schema_type, None,
344                              entry_schema_constraints, v, aspect)
345             if v is not None:
346                 the_map[k] = v
347
348         return the_map
349
350     def __init__(self, items=None):
351         super(Map, self).__init__(items, key_class=str)
352
353     # Can't define as property because it's old-style Python class
354     def as_raw(self):
355         return OrderedDict(self)
356
357
358 @total_ordering
359 @implements_specification('3.2.6', 'tosca-simple-1.0')
360 class Scalar(object):
361     """
362     The scalar-unit type can be used to define scalar values along with a unit from the list of
363     recognized units.
364
365     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
366     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
367     #TYPE_TOSCA_SCALAR_UNIT>`__
368     """
369
370     @staticmethod
371     def key(scalar):
372         """
373         Key method for fast sorting.
374         """
375         return scalar.value
376
377     def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
378         str_value = str(value)
379         match = re.match(self.REGEX, str_value) # pylint: disable=no-member
380         if match is None:
381             raise ValueError('scalar must be formatted as <scalar> <unit>: %s' % safe_repr(value))
382
383         self.factor = float(match.group('scalar'))
384         if self.factor < 0:
385             raise ValueError('scalar is negative: %s' % safe_repr(self.factor))
386
387         self.unit = match.group('unit')
388
389         unit_lower = self.unit.lower()
390         unit_size = None
391         for k, v in self.UNITS.iteritems(): # pylint: disable=no-member
392             if k.lower() == unit_lower:
393                 self.unit = k
394                 unit_size = v
395                 break
396         if unit_size is None:
397             raise ValueError('scalar specified with unsupported unit: %s' % safe_repr(self.unit))
398
399         self.value = self.TYPE(self.factor * unit_size) # pylint: disable=no-member
400
401     @property
402     def as_raw(self):
403         return OrderedDict((
404             ('value', self.value),
405             ('factor', self.factor),
406             ('unit', self.unit),
407             ('unit_size', self.UNITS[self.unit]))) # pylint: disable=no-member
408
409     def __str__(self):
410         return '%s %s' % (self.value, self.UNIT) # pylint: disable=no-member
411
412     def __repr__(self):
413         return repr(self.__str__())
414
415     def __eq__(self, scalar):
416         if isinstance(scalar, Scalar):
417             value = scalar.value
418         else:
419             value = self.TYPE(scalar) # pylint: disable=no-member
420         return self.value == value
421
422     def __lt__(self, scalar):
423         if isinstance(scalar, Scalar):
424             value = scalar.value
425         else:
426             value = self.TYPE(scalar) # pylint: disable=no-member
427         return self.value < value
428
429
430 @implements_specification('3.2.6.4', 'tosca-simple-1.0')
431 class ScalarSize(Scalar):
432     """
433     Integer scalar for counting bytes.
434
435     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
436     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
437     #TYPE_TOSCA_SCALAR_UNIT_SIZE>`__
438     """
439
440     # See: http://www.regular-expressions.info/floatingpoint.html
441     REGEX = \
442         r'^(?P<scalar>[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*(?P<unit>B|kB|KiB|MB|MiB|GB|GiB|TB|TiB)$'
443
444     UNITS = {
445         'B':               1,
446         'kB':           1000,
447         'KiB':          1024,
448         'MB':        1000000,
449         'MiB':       1048576,
450         'GB':     1000000000,
451         'GiB':    1073741824,
452         'TB':  1000000000000,
453         'TiB': 1099511627776}
454
455     TYPE = int
456     UNIT = 'bytes'
457
458
459 @implements_specification('3.2.6.5', 'tosca-simple-1.0')
460 class ScalarTime(Scalar):
461     """
462     Floating point scalar for counting seconds.
463
464     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
465     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
466     #TYPE_TOSCA_SCALAR_UNIT_TIME>`__
467     """
468
469     # See: http://www.regular-expressions.info/floatingpoint.html
470     REGEX = r'^(?P<scalar>[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*(?P<unit>ns|us|ms|s|m|h|d)$'
471
472     UNITS = {
473         'ns':     0.000000001,
474         'us':     0.000001,
475         'ms':     0.001,
476         's':      1.0,
477         'm':     60.0,
478         'h':   3600.0,
479         'd':  86400.0}
480
481     TYPE = float
482     UNIT = 'seconds'
483
484
485 @implements_specification('3.2.6.6', 'tosca-simple-1.0')
486 class ScalarFrequency(Scalar):
487     """
488     Floating point scalar for counting cycles per second (Hz).
489
490     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
491     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
492     #TYPE_TOSCA_SCALAR_UNIT_FREQUENCY>`__
493     """
494
495     # See: http://www.regular-expressions.info/floatingpoint.html
496     REGEX = r'^(?P<scalar>[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*(?P<unit>Hz|kHz|MHz|GHz)$'
497
498     UNITS = {
499         'Hz':           1.0,
500         'kHz':       1000.0,
501         'MHz':    1000000.0,
502         'GHz': 1000000000.0}
503
504     TYPE = float
505     UNIT = 'Hz'
506
507
508 #
509 # The following are hooked in the YAML as 'coerce_value' extensions
510 #
511
512 def coerce_timestamp(context, presentation, the_type, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
513     return coerce_to_data_type_class(context, presentation, Timestamp, entry_schema, constraints,
514                                      value, aspect)
515
516
517 def coerce_version(context, presentation, the_type, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
518     return coerce_to_data_type_class(context, presentation, Version, entry_schema, constraints,
519                                      value, aspect)
520
521
522 def coerce_range(context, presentation, the_type, entry_schema, constraints, value, aspect):
523     if aspect == 'in_range':
524         # When we're in a "in_range" constraint, the values are *not* themselves ranges, but numbers
525         try:
526             return float(value)
527         except ValueError as e:
528             report_issue_for_bad_format(context, presentation, the_type, value, aspect, e)
529         except TypeError as e:
530             report_issue_for_bad_format(context, presentation, the_type, value, aspect, e)
531     else:
532         return coerce_to_data_type_class(context, presentation, Range, entry_schema, constraints,
533                                          value, aspect)
534
535
536 def coerce_list(context, presentation, the_type, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
537     return coerce_to_data_type_class(context, presentation, List, entry_schema, constraints,
538                                      value, aspect)
539
540
541 def coerce_map_value(context, presentation, the_type, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
542     return coerce_to_data_type_class(context, presentation, Map, entry_schema, constraints, value,
543                                      aspect)
544
545
546 def coerce_scalar_unit_size(context, presentation, the_type, entry_schema, constraints, value, # pylint: disable=unused-argument
547                             aspect):
548     return coerce_to_data_type_class(context, presentation, ScalarSize, entry_schema, constraints,
549                                      value, aspect)
550
551
552 def coerce_scalar_unit_time(context, presentation, the_type, entry_schema, constraints, value, # pylint: disable=unused-argument
553                             aspect):
554     return coerce_to_data_type_class(context, presentation, ScalarTime, entry_schema, constraints,
555                                      value, aspect)
556
557
558 def coerce_scalar_unit_frequency(context, presentation, the_type, entry_schema, constraints, value, # pylint: disable=unused-argument
559                                  aspect):
560     return coerce_to_data_type_class(context, presentation, ScalarFrequency, entry_schema,
561                                      constraints, value, aspect)