vFW and vDNS support added to azure-plugin
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / aria / modeling / service_instance.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 """
17 ARIA modeling service instance module
18 """
19
20 # pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
21
22 from sqlalchemy import (
23     Column,
24     Text,
25     Integer,
26     Enum,
27     Boolean
28 )
29 from sqlalchemy import DateTime
30 from sqlalchemy.ext.declarative import declared_attr
31 from sqlalchemy.ext.orderinglist import ordering_list
32
33 from . import (
34     relationship,
35     types as modeling_types
36 )
37 from .mixins import InstanceModelMixin
38
39 from ..utils import (
40     collections,
41     formatting
42 )
43
44
45 class ServiceBase(InstanceModelMixin):
46     """
47     Usually an instance of a :class:`ServiceTemplate` and its many associated templates (node
48     templates, group templates, policy templates, etc.). However, it can also be created
49     programmatically.
50     """
51
52     __tablename__ = 'service'
53
54     __private_fields__ = ('substitution_fk',
55                           'service_template_fk')
56
57     # region one_to_one relationships
58
59     @declared_attr
60     def substitution(cls):
61         """
62         Exposes the entire service as a single node.
63
64         :type: :class:`Substitution`
65         """
66         return relationship.one_to_one(cls, 'substitution', back_populates=relationship.NO_BACK_POP)
67
68     # endregion
69
70     # region one_to_many relationships
71
72     @declared_attr
73     def outputs(cls):
74         """
75         Output parameters.
76
77         :type: {:obj:`basestring`: :class:`Output`}
78         """
79         return relationship.one_to_many(cls, 'output', dict_key='name')
80
81     @declared_attr
82     def inputs(cls):
83         """
84         Externally provided parameters.
85
86         :type: {:obj:`basestring`: :class:`Input`}
87         """
88         return relationship.one_to_many(cls, 'input', dict_key='name')
89
90     @declared_attr
91     def updates(cls):
92         """
93         Service updates.
94
95         :type: [:class:`ServiceUpdate`]
96         """
97         return relationship.one_to_many(cls, 'service_update')
98
99     @declared_attr
100     def modifications(cls):
101         """
102         Service modifications.
103
104         :type: [:class:`ServiceModification`]
105         """
106         return relationship.one_to_many(cls, 'service_modification')
107
108     @declared_attr
109     def executions(cls):
110         """
111         Executions.
112
113         :type: [:class:`Execution`]
114         """
115         return relationship.one_to_many(cls, 'execution')
116
117     @declared_attr
118     def nodes(cls):
119         """
120         Nodes.
121
122         :type: {:obj:`basestring`, :class:`Node`}
123         """
124         return relationship.one_to_many(cls, 'node', dict_key='name')
125
126     @declared_attr
127     def groups(cls):
128         """
129         Groups.
130
131         :type: {:obj:`basestring`, :class:`Group`}
132         """
133         return relationship.one_to_many(cls, 'group', dict_key='name')
134
135     @declared_attr
136     def policies(cls):
137         """
138         Policies.
139
140         :type: {:obj:`basestring`, :class:`Policy`}
141         """
142         return relationship.one_to_many(cls, 'policy', dict_key='name')
143
144     @declared_attr
145     def workflows(cls):
146         """
147         Workflows.
148
149         :type: {:obj:`basestring`, :class:`Operation`}
150         """
151         return relationship.one_to_many(cls, 'operation', dict_key='name')
152
153     # endregion
154
155     # region many_to_one relationships
156
157     @declared_attr
158     def service_template(cls):
159         """
160         Source service template (can be ``None``).
161
162         :type: :class:`ServiceTemplate`
163         """
164         return relationship.many_to_one(cls, 'service_template')
165
166     # endregion
167
168     # region many_to_many relationships
169
170     @declared_attr
171     def meta_data(cls):
172         """
173         Associated metadata.
174
175         :type: {:obj:`basestring`, :class:`Metadata`}
176         """
177         # Warning! We cannot use the attr name "metadata" because it's used by SQLAlchemy!
178         return relationship.many_to_many(cls, 'metadata', dict_key='name')
179
180     @declared_attr
181     def plugins(cls):
182         """
183         Associated plugins.
184
185         :type: {:obj:`basestring`, :class:`Plugin`}
186         """
187         return relationship.many_to_many(cls, 'plugin', dict_key='name')
188
189     # endregion
190
191     # region association proxies
192
193     @declared_attr
194     def service_template_name(cls):
195         return relationship.association_proxy('service_template', 'name', type=':obj:`basestring`')
196
197     # endregion
198
199     # region foreign keys
200
201     @declared_attr
202     def substitution_fk(cls):
203         """Service one-to-one to Substitution"""
204         return relationship.foreign_key('substitution', nullable=True)
205
206     @declared_attr
207     def service_template_fk(cls):
208         """For Service many-to-one to ServiceTemplate"""
209         return relationship.foreign_key('service_template', nullable=True)
210
211     # endregion
212
213     description = Column(Text, doc="""
214     Human-readable description.
215
216     :type: :obj:`basestring`
217     """)
218
219     created_at = Column(DateTime, nullable=False, index=True, doc="""
220     Creation timestamp.
221
222     :type: :class:`~datetime.datetime`
223     """)
224
225     updated_at = Column(DateTime, doc="""
226     Update timestamp.
227
228     :type: :class:`~datetime.datetime`
229     """)
230
231     def get_node_by_type(self, type_name):
232         """
233         Finds the first node of a type (or descendent type).
234         """
235         service_template = self.service_template
236
237         if service_template is not None:
238             node_types = service_template.node_types
239             if node_types is not None:
240                 for node in self.nodes.itervalues():
241                     if node_types.is_descendant(type_name, node.type.name):
242                         return node
243
244         return None
245
246     def get_policy_by_type(self, type_name):
247         """
248         Finds the first policy of a type (or descendent type).
249         """
250         service_template = self.service_template
251
252         if service_template is not None:
253             policy_types = service_template.policy_types
254             if policy_types is not None:
255                 for policy in self.policies.itervalues():
256                     if policy_types.is_descendant(type_name, policy.type.name):
257                         return policy
258
259         return None
260
261     @property
262     def as_raw(self):
263         return collections.OrderedDict((
264             ('description', self.description),
265             ('metadata', formatting.as_raw_dict(self.meta_data)),
266             ('nodes', formatting.as_raw_list(self.nodes)),
267             ('groups', formatting.as_raw_list(self.groups)),
268             ('policies', formatting.as_raw_list(self.policies)),
269             ('substitution', formatting.as_raw(self.substitution)),
270             ('inputs', formatting.as_raw_dict(self.inputs)),
271             ('outputs', formatting.as_raw_dict(self.outputs)),
272             ('workflows', formatting.as_raw_list(self.workflows))))
273
274
275 class NodeBase(InstanceModelMixin):
276     """
277     Typed vertex in the service topology.
278
279     Nodes may have zero or more :class:`Relationship` instances to other nodes, together forming
280     a many-to-many node graph.
281
282     Usually an instance of a :class:`NodeTemplate`.
283     """
284
285     __tablename__ = 'node'
286
287     __private_fields__ = ('type_fk',
288                           'host_fk',
289                           'service_fk',
290                           'node_template_fk')
291
292     INITIAL = 'initial'
293     CREATING = 'creating'
294     CREATED = 'created'
295     CONFIGURING = 'configuring'
296     CONFIGURED = 'configured'
297     STARTING = 'starting'
298     STARTED = 'started'
299     STOPPING = 'stopping'
300     DELETING = 'deleting'
301     DELETED = 'deleted'
302     ERROR = 'error'
303
304     # Note: 'deleted' isn't actually part of the TOSCA spec, since according the description of the
305     # 'deleting' state: "Node is transitioning from its current state to one where it is deleted and
306     # its state is no longer tracked by the instance model." However, we prefer to be able to
307     # retrieve information about deleted nodes, so we chose to add this 'deleted' state to enable us
308     # to do so.
309
310     STATES = (INITIAL, CREATING, CREATED, CONFIGURING, CONFIGURED, STARTING, STARTED, STOPPING,
311               DELETING, DELETED, ERROR)
312
313     _OP_TO_STATE = {'create': {'transitional': CREATING, 'finished': CREATED},
314                     'configure': {'transitional': CONFIGURING, 'finished': CONFIGURED},
315                     'start': {'transitional': STARTING, 'finished': STARTED},
316                     'stop': {'transitional': STOPPING, 'finished': CONFIGURED},
317                     'delete': {'transitional': DELETING, 'finished': DELETED}}
318
319     # region one_to_one relationships
320
321     @declared_attr
322     def host(cls): # pylint: disable=method-hidden
323         """
324         Node in which we are hosted (can be ``None``).
325
326         Normally the host node is found by following the relationship graph (relationships with
327         ``host`` roles) to final nodes (with ``host`` roles).
328
329         :type: :class:`Node`
330         """
331         return relationship.one_to_one_self(cls, 'host_fk')
332
333     # endregion
334
335     # region one_to_many relationships
336
337     @declared_attr
338     def tasks(cls):
339         """
340         Associated tasks.
341
342         :type: [:class:`Task`]
343         """
344         return relationship.one_to_many(cls, 'task')
345
346     @declared_attr
347     def interfaces(cls):
348         """
349         Associated interfaces.
350
351         :type: {:obj:`basestring`: :class:`Interface`}
352         """
353         return relationship.one_to_many(cls, 'interface', dict_key='name')
354
355     @declared_attr
356     def properties(cls):
357         """
358         Associated immutable parameters.
359
360         :type: {:obj:`basestring`: :class:`Property`}
361         """
362         return relationship.one_to_many(cls, 'property', dict_key='name')
363
364     @declared_attr
365     def attributes(cls):
366         """
367         Associated mutable parameters.
368
369         :type: {:obj:`basestring`: :class:`Attribute`}
370         """
371         return relationship.one_to_many(cls, 'attribute', dict_key='name')
372
373     @declared_attr
374     def artifacts(cls):
375         """
376         Associated artifacts.
377
378         :type: {:obj:`basestring`: :class:`Artifact`}
379         """
380         return relationship.one_to_many(cls, 'artifact', dict_key='name')
381
382     @declared_attr
383     def capabilities(cls):
384         """
385         Associated exposed capabilities.
386
387         :type: {:obj:`basestring`: :class:`Capability`}
388         """
389         return relationship.one_to_many(cls, 'capability', dict_key='name')
390
391     @declared_attr
392     def outbound_relationships(cls):
393         """
394         Relationships to other nodes.
395
396         :type: [:class:`Relationship`]
397         """
398         return relationship.one_to_many(
399             cls, 'relationship', other_fk='source_node_fk', back_populates='source_node',
400             rel_kwargs=dict(
401                 order_by='Relationship.source_position',
402                 collection_class=ordering_list('source_position', count_from=0)
403             )
404         )
405
406     @declared_attr
407     def inbound_relationships(cls):
408         """
409         Relationships from other nodes.
410
411         :type: [:class:`Relationship`]
412         """
413         return relationship.one_to_many(
414             cls, 'relationship', other_fk='target_node_fk', back_populates='target_node',
415             rel_kwargs=dict(
416                 order_by='Relationship.target_position',
417                 collection_class=ordering_list('target_position', count_from=0)
418             )
419         )
420
421     # endregion
422
423     # region many_to_one relationships
424
425     @declared_attr
426     def service(cls):
427         """
428         Containing service.
429
430         :type: :class:`Service`
431         """
432         return relationship.many_to_one(cls, 'service')
433
434     @declared_attr
435     def node_template(cls):
436         """
437         Source node template (can be ``None``).
438
439         :type: :class:`NodeTemplate`
440         """
441         return relationship.many_to_one(cls, 'node_template')
442
443     @declared_attr
444     def type(cls):
445         """
446         Node type.
447
448         :type: :class:`Type`
449         """
450         return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
451
452     # endregion
453
454     # region association proxies
455
456     @declared_attr
457     def service_name(cls):
458         return relationship.association_proxy('service', 'name', type=':obj:`basestring`')
459
460     @declared_attr
461     def node_template_name(cls):
462         return relationship.association_proxy('node_template', 'name', type=':obj:`basestring`')
463
464     # endregion
465
466     # region foreign_keys
467
468     @declared_attr
469     def type_fk(cls):
470         """For Node many-to-one to Type"""
471         return relationship.foreign_key('type')
472
473     @declared_attr
474     def host_fk(cls):
475         """For Node one-to-one to Node"""
476         return relationship.foreign_key('node', nullable=True)
477
478     @declared_attr
479     def service_fk(cls):
480         """For Service one-to-many to Node"""
481         return relationship.foreign_key('service')
482
483     @declared_attr
484     def node_template_fk(cls):
485         """For Node many-to-one to NodeTemplate"""
486         return relationship.foreign_key('node_template')
487
488     # endregion
489
490     description = Column(Text, doc="""
491     Human-readable description.
492
493     :type: :obj:`basestring`
494     """)
495
496     state = Column(Enum(*STATES, name='node_state'), nullable=False, default=INITIAL, doc="""
497     TOSCA state.
498
499     :type: :obj:`basestring`
500     """)
501
502     version = Column(Integer, default=1, doc="""
503     Used by :mod:`aria.storage.instrumentation`.
504
505     :type: :obj:`int`
506     """)
507
508     __mapper_args__ = {'version_id_col': version} # Enable SQLAlchemy automatic version counting
509
510     @classmethod
511     def determine_state(cls, op_name, is_transitional):
512         """
513         :returns the state the node should be in as a result of running the operation on this node.
514
515         E.g. if we are running tosca.interfaces.node.lifecycle.Standard.create, then
516         the resulting state should either 'creating' (if the task just started) or 'created'
517         (if the task ended).
518
519         If the operation is not a standard TOSCA lifecycle operation, then we return None.
520         """
521
522         state_type = 'transitional' if is_transitional else 'finished'
523         try:
524             return cls._OP_TO_STATE[op_name][state_type]
525         except KeyError:
526             return None
527
528     def is_available(self):
529         return self.state not in (self.INITIAL, self.DELETED, self.ERROR)
530
531     def get_outbound_relationship_by_name(self, name):
532         for the_relationship in self.outbound_relationships:
533             if the_relationship.name == name:
534                 return the_relationship
535         return None
536
537     def get_inbound_relationship_by_name(self, name):
538         for the_relationship in self.inbound_relationships:
539             if the_relationship.name == name:
540                 return the_relationship
541         return None
542
543     @property
544     def host_address(self):
545         if self.host and self.host.attributes:
546             attribute = self.host.attributes.get('ip')
547             if attribute is not None:
548                 return attribute.value
549         return None
550
551     @property
552     def as_raw(self):
553         return collections.OrderedDict((
554             ('name', self.name),
555             ('type_name', self.type.name),
556             ('properties', formatting.as_raw_dict(self.properties)),
557             ('attributes', formatting.as_raw_dict(self.properties)),
558             ('interfaces', formatting.as_raw_list(self.interfaces)),
559             ('artifacts', formatting.as_raw_list(self.artifacts)),
560             ('capabilities', formatting.as_raw_list(self.capabilities)),
561             ('relationships', formatting.as_raw_list(self.outbound_relationships))))
562
563
564 class GroupBase(InstanceModelMixin):
565     """
566     Typed logical container for zero or more :class:`Node` instances.
567
568     Usually an instance of a :class:`GroupTemplate`.
569     """
570
571     __tablename__ = 'group'
572
573     __private_fields__ = ('type_fk',
574                           'service_fk',
575                           'group_template_fk')
576
577     # region one_to_many relationships
578
579     @declared_attr
580     def properties(cls):
581         """
582         Associated immutable parameters.
583
584         :type: {:obj:`basestring`: :class:`Property`}
585         """
586         return relationship.one_to_many(cls, 'property', dict_key='name')
587
588     @declared_attr
589     def interfaces(cls):
590         """
591         Associated interfaces.
592
593         :type: {:obj:`basestring`: :class:`Interface`}
594         """
595         return relationship.one_to_many(cls, 'interface', dict_key='name')
596
597     # endregion
598
599     # region many_to_one relationships
600
601     @declared_attr
602     def service(cls):
603         """
604         Containing service.
605
606         :type: :class:`Service`
607         """
608         return relationship.many_to_one(cls, 'service')
609
610     @declared_attr
611     def group_template(cls):
612         """
613         Source group template (can be ``None``).
614
615         :type: :class:`GroupTemplate`
616         """
617         return relationship.many_to_one(cls, 'group_template')
618
619     @declared_attr
620     def type(cls):
621         """
622         Group type.
623
624         :type: :class:`Type`
625         """
626         return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
627
628     # endregion
629
630     # region many_to_many relationships
631
632     @declared_attr
633     def nodes(cls):
634         """
635         Member nodes.
636
637         :type: [:class:`Node`]
638         """
639         return relationship.many_to_many(cls, 'node')
640
641     # endregion
642
643     # region foreign_keys
644
645     @declared_attr
646     def type_fk(cls):
647         """For Group many-to-one to Type"""
648         return relationship.foreign_key('type')
649
650     @declared_attr
651     def service_fk(cls):
652         """For Service one-to-many to Group"""
653         return relationship.foreign_key('service')
654
655     @declared_attr
656     def group_template_fk(cls):
657         """For Group many-to-one to GroupTemplate"""
658         return relationship.foreign_key('group_template', nullable=True)
659
660     # endregion
661
662     description = Column(Text, doc="""
663     Human-readable description.
664
665     :type: :obj:`basestring`
666     """)
667
668     @property
669     def as_raw(self):
670         return collections.OrderedDict((
671             ('name', self.name),
672             ('properties', formatting.as_raw_dict(self.properties)),
673             ('interfaces', formatting.as_raw_list(self.interfaces))))
674
675
676 class PolicyBase(InstanceModelMixin):
677     """
678     Typed set of orchestration hints applied to zero or more :class:`Node` or :class:`Group`
679     instances.
680
681     Usually an instance of a :class:`PolicyTemplate`.
682     """
683
684     __tablename__ = 'policy'
685
686     __private_fields__ = ('type_fk',
687                           'service_fk',
688                           'policy_template_fk')
689
690     # region one_to_many relationships
691
692     @declared_attr
693     def properties(cls):
694         """
695         Associated immutable parameters.
696
697         :type: {:obj:`basestring`: :class:`Property`}
698         """
699         return relationship.one_to_many(cls, 'property', dict_key='name')
700
701     # endregion
702
703     # region many_to_one relationships
704
705     @declared_attr
706     def service(cls):
707         """
708         Containing service.
709
710         :type: :class:`Service`
711         """
712         return relationship.many_to_one(cls, 'service')
713
714     @declared_attr
715     def policy_template(cls):
716         """
717         Source policy template (can be ``None``).
718
719         :type: :class:`PolicyTemplate`
720         """
721         return relationship.many_to_one(cls, 'policy_template')
722
723     @declared_attr
724     def type(cls):
725         """
726         Group type.
727
728         :type: :class:`Type`
729         """
730         return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
731
732     # endregion
733
734     # region many_to_many relationships
735
736     @declared_attr
737     def nodes(cls):
738         """
739         Policy is enacted on these nodes.
740
741         :type: {:obj:`basestring`: :class:`Node`}
742         """
743         return relationship.many_to_many(cls, 'node')
744
745     @declared_attr
746     def groups(cls):
747         """
748         Policy is enacted on nodes in these groups.
749
750         :type: {:obj:`basestring`: :class:`Group`}
751         """
752         return relationship.many_to_many(cls, 'group')
753
754     # endregion
755
756     # region foreign_keys
757
758     @declared_attr
759     def type_fk(cls):
760         """For Policy many-to-one to Type"""
761         return relationship.foreign_key('type')
762
763     @declared_attr
764     def service_fk(cls):
765         """For Service one-to-many to Policy"""
766         return relationship.foreign_key('service')
767
768     @declared_attr
769     def policy_template_fk(cls):
770         """For Policy many-to-one to PolicyTemplate"""
771         return relationship.foreign_key('policy_template', nullable=True)
772
773     # endregion
774
775     description = Column(Text, doc="""
776     Human-readable description.
777
778     :type: :obj:`basestring`
779     """)
780
781     @property
782     def as_raw(self):
783         return collections.OrderedDict((
784             ('name', self.name),
785             ('type_name', self.type.name),
786             ('properties', formatting.as_raw_dict(self.properties))))
787
788
789 class SubstitutionBase(InstanceModelMixin):
790     """
791     Exposes the entire service as a single node.
792
793     Usually an instance of a :class:`SubstitutionTemplate`.
794     """
795
796     __tablename__ = 'substitution'
797
798     __private_fields__ = ('node_type_fk',
799                           'substitution_template_fk')
800
801     # region one_to_many relationships
802
803     @declared_attr
804     def mappings(cls):
805         """
806         Map requirement and capabilities to exposed node.
807
808         :type: {:obj:`basestring`: :class:`SubstitutionMapping`}
809         """
810         return relationship.one_to_many(cls, 'substitution_mapping', dict_key='name')
811
812     # endregion
813
814     # region many_to_one relationships
815
816     @declared_attr
817     def service(cls):
818         """
819         Containing service.
820
821         :type: :class:`Service`
822         """
823         return relationship.one_to_one(cls, 'service', back_populates=relationship.NO_BACK_POP)
824
825     @declared_attr
826     def substitution_template(cls):
827         """
828         Source substitution template (can be ``None``).
829
830         :type: :class:`SubstitutionTemplate`
831         """
832         return relationship.many_to_one(cls, 'substitution_template')
833
834     @declared_attr
835     def node_type(cls):
836         """
837         Exposed node type.
838
839         :type: :class:`Type`
840         """
841         return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
842
843     # endregion
844
845     # region foreign_keys
846
847     @declared_attr
848     def node_type_fk(cls):
849         """For Substitution many-to-one to Type"""
850         return relationship.foreign_key('type')
851
852     @declared_attr
853     def substitution_template_fk(cls):
854         """For Substitution many-to-one to SubstitutionTemplate"""
855         return relationship.foreign_key('substitution_template', nullable=True)
856
857     # endregion
858
859     @property
860     def as_raw(self):
861         return collections.OrderedDict((
862             ('node_type_name', self.node_type.name),
863             ('mappings', formatting.as_raw_dict(self.mappings))))
864
865
866 class SubstitutionMappingBase(InstanceModelMixin):
867     """
868     Used by :class:`Substitution` to map a capability or a requirement to the exposed node.
869
870     The :attr:`name` field should match the capability or requirement template name on the exposed
871     node's type.
872
873     Only one of :attr:`capability` and :attr:`requirement_template` can be set. If the latter is
874     set, then :attr:`node` must also be set.
875
876     Usually an instance of a :class:`SubstitutionMappingTemplate`.
877     """
878
879     __tablename__ = 'substitution_mapping'
880
881     __private_fields__ = ('substitution_fk',
882                           'node_fk',
883                           'capability_fk',
884                           'requirement_template_fk')
885
886     # region one_to_one relationships
887
888     @declared_attr
889     def capability(cls):
890         """
891         Capability to expose (can be ``None``).
892
893         :type: :class:`Capability`
894         """
895         return relationship.one_to_one(cls, 'capability', back_populates=relationship.NO_BACK_POP)
896
897     @declared_attr
898     def requirement_template(cls):
899         """
900         Requirement template to expose (can be ``None``).
901
902         :type: :class:`RequirementTemplate`
903         """
904         return relationship.one_to_one(cls, 'requirement_template',
905                                        back_populates=relationship.NO_BACK_POP)
906
907     @declared_attr
908     def node(cls):
909         """
910         Node for which to expose :attr:`requirement_template` (can be ``None``).
911
912         :type: :class:`Node`
913         """
914         return relationship.one_to_one(cls, 'node', back_populates=relationship.NO_BACK_POP)
915
916     # endregion
917
918     # region many_to_one relationships
919
920     @declared_attr
921     def substitution(cls):
922         """
923         Containing substitution.
924
925         :type: :class:`Substitution`
926         """
927         return relationship.many_to_one(cls, 'substitution', back_populates='mappings')
928
929     # endregion
930
931     # region foreign keys
932
933     @declared_attr
934     def substitution_fk(cls):
935         """For Substitution one-to-many to SubstitutionMapping"""
936         return relationship.foreign_key('substitution')
937
938     @declared_attr
939     def capability_fk(cls):
940         """For Substitution one-to-one to Capability"""
941         return relationship.foreign_key('capability', nullable=True)
942
943     @declared_attr
944     def node_fk(cls):
945         """For Substitution one-to-one to Node"""
946         return relationship.foreign_key('node', nullable=True)
947
948     @declared_attr
949     def requirement_template_fk(cls):
950         """For Substitution one-to-one to RequirementTemplate"""
951         return relationship.foreign_key('requirement_template', nullable=True)
952
953     # endregion
954
955     @property
956     def as_raw(self):
957         return collections.OrderedDict((
958             ('name', self.name),))
959
960
961 class RelationshipBase(InstanceModelMixin):
962     """
963     Optionally-typed edge in the service topology, connecting a :class:`Node` to a
964     :class:`Capability` of another node.
965
966     Might be an instance of :class:`RelationshipTemplate` and/or :class:`RequirementTemplate`.
967     """
968
969     __tablename__ = 'relationship'
970
971     __private_fields__ = ('type_fk',
972                           'source_node_fk',
973                           'target_node_fk',
974                           'target_capability_fk',
975                           'requirement_template_fk',
976                           'relationship_template_fk',
977                           'target_position',
978                           'source_position')
979
980     # region one_to_one relationships
981
982     @declared_attr
983     def target_capability(cls):
984         """
985         Target capability.
986
987         :type: :class:`Capability`
988         """
989         return relationship.one_to_one(cls, 'capability', back_populates=relationship.NO_BACK_POP)
990
991     # endregion
992
993     # region one_to_many relationships
994
995     @declared_attr
996     def tasks(cls):
997         """
998         Associated tasks.
999
1000         :type: [:class:`Task`]
1001         """
1002         return relationship.one_to_many(cls, 'task')
1003
1004     @declared_attr
1005     def interfaces(cls):
1006         """
1007         Associated interfaces.
1008
1009         :type: {:obj:`basestring`: :class:`Interface`}
1010         """
1011         return relationship.one_to_many(cls, 'interface', dict_key='name')
1012
1013     @declared_attr
1014     def properties(cls):
1015         """
1016         Associated immutable parameters.
1017
1018         :type: {:obj:`basestring`: :class:`Property`}
1019         """
1020         return relationship.one_to_many(cls, 'property', dict_key='name')
1021
1022     # endregion
1023
1024     # region many_to_one relationships
1025
1026     @declared_attr
1027     def source_node(cls):
1028         """
1029         Source node.
1030
1031         :type: :class:`Node`
1032         """
1033         return relationship.many_to_one(
1034             cls, 'node', fk='source_node_fk', back_populates='outbound_relationships')
1035
1036     @declared_attr
1037     def target_node(cls):
1038         """
1039         Target node.
1040
1041         :type: :class:`Node`
1042         """
1043         return relationship.many_to_one(
1044             cls, 'node', fk='target_node_fk', back_populates='inbound_relationships')
1045
1046     @declared_attr
1047     def relationship_template(cls):
1048         """
1049         Source relationship template (can be ``None``).
1050
1051         :type: :class:`RelationshipTemplate`
1052         """
1053         return relationship.many_to_one(cls, 'relationship_template')
1054
1055     @declared_attr
1056     def requirement_template(cls):
1057         """
1058         Source requirement template (can be ``None``).
1059
1060         :type: :class:`RequirementTemplate`
1061         """
1062         return relationship.many_to_one(cls, 'requirement_template')
1063
1064     @declared_attr
1065     def type(cls):
1066         """
1067         Relationship type.
1068
1069         :type: :class:`Type`
1070         """
1071         return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
1072
1073     # endregion
1074
1075     # region association proxies
1076
1077     @declared_attr
1078     def source_node_name(cls):
1079         return relationship.association_proxy('source_node', 'name')
1080
1081     @declared_attr
1082     def target_node_name(cls):
1083         return relationship.association_proxy('target_node', 'name')
1084
1085     # endregion
1086
1087     # region foreign keys
1088
1089     @declared_attr
1090     def type_fk(cls):
1091         """For Relationship many-to-one to Type"""
1092         return relationship.foreign_key('type', nullable=True)
1093
1094     @declared_attr
1095     def source_node_fk(cls):
1096         """For Node one-to-many to Relationship"""
1097         return relationship.foreign_key('node')
1098
1099     @declared_attr
1100     def target_node_fk(cls):
1101         """For Node one-to-many to Relationship"""
1102         return relationship.foreign_key('node')
1103
1104     @declared_attr
1105     def target_capability_fk(cls):
1106         """For Relationship one-to-one to Capability"""
1107         return relationship.foreign_key('capability', nullable=True)
1108
1109     @declared_attr
1110     def requirement_template_fk(cls):
1111         """For Relationship many-to-one to RequirementTemplate"""
1112         return relationship.foreign_key('requirement_template', nullable=True)
1113
1114     @declared_attr
1115     def relationship_template_fk(cls):
1116         """For Relationship many-to-one to RelationshipTemplate"""
1117         return relationship.foreign_key('relationship_template', nullable=True)
1118
1119     # endregion
1120
1121     source_position = Column(Integer, doc="""
1122     Position at source.
1123
1124     :type: :obj:`int`
1125     """)
1126
1127     target_position = Column(Integer, doc="""
1128     Position at target.
1129
1130     :type: :obj:`int`
1131     """)
1132
1133     @property
1134     def as_raw(self):
1135         return collections.OrderedDict((
1136             ('name', self.name),
1137             ('target_node_id', self.target_node.name),
1138             ('type_name', self.type.name
1139              if self.type is not None else None),
1140             ('template_name', self.relationship_template.name
1141              if self.relationship_template is not None else None),
1142             ('properties', formatting.as_raw_dict(self.properties)),
1143             ('interfaces', formatting.as_raw_list(self.interfaces))))
1144
1145
1146 class CapabilityBase(InstanceModelMixin):
1147     """
1148     Typed attachment serving two purposes: to provide extra properties and attributes to a
1149     :class:`Node`, and to expose targets for :class:`Relationship` instances from other nodes.
1150
1151     Usually an instance of a :class:`CapabilityTemplate`.
1152     """
1153
1154     __tablename__ = 'capability'
1155
1156     __private_fields__ = ('capability_fk',
1157                           'node_fk',
1158                           'capability_template_fk')
1159
1160     # region one_to_many relationships
1161
1162     @declared_attr
1163     def properties(cls):
1164         """
1165         Associated immutable parameters.
1166
1167         :type: {:obj:`basestring`: :class:`Property`}
1168         """
1169         return relationship.one_to_many(cls, 'property', dict_key='name')
1170
1171     # endregion
1172
1173     # region many_to_one relationships
1174
1175     @declared_attr
1176     def node(cls):
1177         """
1178         Containing node.
1179
1180         :type: :class:`Node`
1181         """
1182         return relationship.many_to_one(cls, 'node')
1183
1184     @declared_attr
1185     def capability_template(cls):
1186         """
1187         Source capability template (can be ``None``).
1188
1189         :type: :class:`CapabilityTemplate`
1190         """
1191         return relationship.many_to_one(cls, 'capability_template')
1192
1193     @declared_attr
1194     def type(cls):
1195         """
1196         Capability type.
1197
1198         :type: :class:`Type`
1199         """
1200         return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
1201
1202     # endregion
1203
1204     # region foreign_keys
1205
1206     @declared_attr
1207     def type_fk(cls):
1208         """For Capability many-to-one to Type"""
1209         return relationship.foreign_key('type')
1210
1211     @declared_attr
1212     def node_fk(cls):
1213         """For Node one-to-many to Capability"""
1214         return relationship.foreign_key('node')
1215
1216     @declared_attr
1217     def capability_template_fk(cls):
1218         """For Capability many-to-one to CapabilityTemplate"""
1219         return relationship.foreign_key('capability_template', nullable=True)
1220
1221     # endregion
1222
1223     min_occurrences = Column(Integer, default=None, doc="""
1224     Minimum number of requirement matches required.
1225
1226     :type: :obj:`int`
1227     """)
1228
1229     max_occurrences = Column(Integer, default=None, doc="""
1230     Maximum number of requirement matches allowed.
1231
1232     :type: :obj:`int`
1233     """)
1234
1235     occurrences = Column(Integer, default=0, doc="""
1236     Number of requirement matches.
1237
1238     :type: :obj:`int`
1239     """)
1240
1241     @property
1242     def has_enough_relationships(self):
1243         if self.min_occurrences is not None:
1244             return self.occurrences >= self.min_occurrences
1245         return True
1246
1247     def relate(self):
1248         if self.max_occurrences is not None:
1249             if self.occurrences == self.max_occurrences:
1250                 return False
1251         self.occurrences += 1
1252         return True
1253
1254     @property
1255     def as_raw(self):
1256         return collections.OrderedDict((
1257             ('name', self.name),
1258             ('type_name', self.type.name),
1259             ('properties', formatting.as_raw_dict(self.properties))))
1260
1261
1262 class InterfaceBase(InstanceModelMixin):
1263     """
1264     Typed bundle of :class:`Operation` instances.
1265
1266     Can be associated with a :class:`Node`, a :class:`Group`, or a :class:`Relationship`.
1267
1268     Usually an instance of a :class:`InterfaceTemplate`.
1269     """
1270
1271     __tablename__ = 'interface'
1272
1273     __private_fields__ = ('type_fk',
1274                           'node_fk',
1275                           'group_fk',
1276                           'relationship_fk',
1277                           'interface_template_fk')
1278
1279     # region one_to_many relationships
1280
1281     @declared_attr
1282     def inputs(cls):
1283         """
1284         Parameters for all operations of the interface.
1285
1286         :type: {:obj:`basestring`: :class:`Input`}
1287         """
1288         return relationship.one_to_many(cls, 'input', dict_key='name')
1289
1290     @declared_attr
1291     def operations(cls):
1292         """
1293         Associated operations.
1294
1295         :type: {:obj:`basestring`: :class:`Operation`}
1296         """
1297         return relationship.one_to_many(cls, 'operation', dict_key='name')
1298
1299     # endregion
1300
1301     # region many_to_one relationships
1302
1303     @declared_attr
1304     def node(cls):
1305         """
1306         Containing node (can be ``None``).
1307
1308         :type: :class:`Node`
1309         """
1310         return relationship.many_to_one(cls, 'node')
1311
1312     @declared_attr
1313     def group(cls):
1314         """
1315         Containing group (can be ``None``).
1316
1317         :type: :class:`Group`
1318         """
1319         return relationship.many_to_one(cls, 'group')
1320
1321     @declared_attr
1322     def relationship(cls):
1323         """
1324         Containing relationship (can be ``None``).
1325
1326         :type: :class:`Relationship`
1327         """
1328         return relationship.many_to_one(cls, 'relationship')
1329
1330     @declared_attr
1331     def interface_template(cls):
1332         """
1333         Source interface template (can be ``None``).
1334
1335         :type: :class:`InterfaceTemplate`
1336         """
1337         return relationship.many_to_one(cls, 'interface_template')
1338
1339     @declared_attr
1340     def type(cls):
1341         """
1342         Interface type.
1343
1344         :type: :class:`Type`
1345         """
1346         return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
1347
1348     # endregion
1349
1350     # region foreign_keys
1351
1352     @declared_attr
1353     def type_fk(cls):
1354         """For Interface many-to-one to Type"""
1355         return relationship.foreign_key('type')
1356
1357     @declared_attr
1358     def node_fk(cls):
1359         """For Node one-to-many to Interface"""
1360         return relationship.foreign_key('node', nullable=True)
1361
1362     @declared_attr
1363     def group_fk(cls):
1364         """For Group one-to-many to Interface"""
1365         return relationship.foreign_key('group', nullable=True)
1366
1367     @declared_attr
1368     def relationship_fk(cls):
1369         """For Relationship one-to-many to Interface"""
1370         return relationship.foreign_key('relationship', nullable=True)
1371
1372     @declared_attr
1373     def interface_template_fk(cls):
1374         """For Interface many-to-one to InterfaceTemplate"""
1375         return relationship.foreign_key('interface_template', nullable=True)
1376
1377     # endregion
1378
1379     description = Column(Text, doc="""
1380     Human-readable description.
1381
1382     :type: :obj:`basestring`
1383     """)
1384
1385     @property
1386     def as_raw(self):
1387         return collections.OrderedDict((
1388             ('name', self.name),
1389             ('description', self.description),
1390             ('type_name', self.type.name),
1391             ('inputs', formatting.as_raw_dict(self.inputs)),
1392             ('operations', formatting.as_raw_list(self.operations))))
1393
1394
1395 class OperationBase(InstanceModelMixin):
1396     """
1397     Entry points to Python functions called as part of a workflow execution.
1398
1399     The operation signature (its :attr:`name` and its :attr:`inputs`'s names and types) is declared
1400     by the type of the :class:`Interface`, however each operation can provide its own
1401     :attr:`implementation` as well as additional inputs.
1402
1403     The Python :attr:`function` is usually provided by an associated :class:`Plugin`. Its purpose is
1404     to execute the implementation, providing it with both the operation's and interface's inputs.
1405     The :attr:`arguments` of the function should be set according to the specific signature of the
1406     function.
1407
1408     Additionally, :attr:`configuration` parameters can be provided as hints to configure the
1409     function's behavior. For example, they can be used to configure remote execution credentials.
1410
1411     Might be an instance of :class:`OperationTemplate`.
1412     """
1413
1414     __tablename__ = 'operation'
1415
1416     __private_fields__ = ('service_fk',
1417                           'interface_fk',
1418                           'plugin_fk',
1419                           'operation_template_fk')
1420
1421     # region one_to_one relationships
1422
1423     @declared_attr
1424     def plugin(cls):
1425         """
1426         Associated plugin.
1427
1428         :type: :class:`Plugin`
1429         """
1430         return relationship.one_to_one(cls, 'plugin', back_populates=relationship.NO_BACK_POP)
1431
1432     # endregion
1433
1434     # region one_to_many relationships
1435
1436     @declared_attr
1437     def inputs(cls):
1438         """
1439         Parameters provided to the :attr:`implementation`.
1440
1441         :type: {:obj:`basestring`: :class:`Input`}
1442         """
1443         return relationship.one_to_many(cls, 'input', dict_key='name')
1444
1445     @declared_attr
1446     def arguments(cls):
1447         """
1448         Arguments sent to the Python :attr:`function`.
1449
1450         :type: {:obj:`basestring`: :class:`Argument`}
1451         """
1452         return relationship.one_to_many(cls, 'argument', dict_key='name')
1453
1454     @declared_attr
1455     def configurations(cls):
1456         """
1457         Configuration parameters for the Python :attr:`function`.
1458
1459         :type: {:obj:`basestring`: :class:`Configuration`}
1460         """
1461         return relationship.one_to_many(cls, 'configuration', dict_key='name')
1462
1463     # endregion
1464
1465     # region many_to_one relationships
1466
1467     @declared_attr
1468     def service(cls):
1469         """
1470         Containing service (can be ``None``). For workflow operations.
1471
1472         :type: :class:`Service`
1473         """
1474         return relationship.many_to_one(cls, 'service', back_populates='workflows')
1475
1476     @declared_attr
1477     def interface(cls):
1478         """
1479         Containing interface (can be ``None``).
1480
1481         :type: :class:`Interface`
1482         """
1483         return relationship.many_to_one(cls, 'interface')
1484
1485     @declared_attr
1486     def operation_template(cls):
1487         """
1488         Source operation template (can be ``None``).
1489
1490         :type: :class:`OperationTemplate`
1491         """
1492         return relationship.many_to_one(cls, 'operation_template')
1493
1494     # endregion
1495
1496     # region foreign_keys
1497
1498     @declared_attr
1499     def service_fk(cls):
1500         """For Service one-to-many to Operation"""
1501         return relationship.foreign_key('service', nullable=True)
1502
1503     @declared_attr
1504     def interface_fk(cls):
1505         """For Interface one-to-many to Operation"""
1506         return relationship.foreign_key('interface', nullable=True)
1507
1508     @declared_attr
1509     def plugin_fk(cls):
1510         """For Operation one-to-one to Plugin"""
1511         return relationship.foreign_key('plugin', nullable=True)
1512
1513     @declared_attr
1514     def operation_template_fk(cls):
1515         """For Operation many-to-one to OperationTemplate"""
1516         return relationship.foreign_key('operation_template', nullable=True)
1517
1518     # endregion
1519
1520     description = Column(Text, doc="""
1521     Human-readable description.
1522
1523     :type: :obj:`basestring`
1524     """)
1525
1526     relationship_edge = Column(Boolean, doc="""
1527     When ``True`` specifies that the operation is on the relationship's target edge; ``False`` is
1528     the source edge (only used by operations on relationships)
1529
1530     :type: :obj:`bool`
1531     """)
1532
1533     implementation = Column(Text, doc="""
1534     Implementation (usually the name of an artifact).
1535
1536     :type: :obj:`basestring`
1537     """)
1538
1539     dependencies = Column(modeling_types.StrictList(item_cls=basestring), doc="""
1540     Dependencies (usually names of artifacts).
1541
1542     :type: [:obj:`basestring`]
1543     """)
1544
1545     function = Column(Text, doc="""
1546     Full path to Python function.
1547
1548     :type: :obj:`basestring`
1549     """)
1550
1551     executor = Column(Text, doc="""
1552     Name of executor.
1553
1554     :type: :obj:`basestring`
1555     """)
1556
1557     max_attempts = Column(Integer, doc="""
1558     Maximum number of attempts allowed in case of task failure.
1559
1560     :type: :obj:`int`
1561     """)
1562
1563     retry_interval = Column(Integer, doc="""
1564     Interval between task retry attempts (in seconds).
1565
1566     :type: :obj:`float`
1567     """)
1568
1569     @property
1570     def as_raw(self):
1571         return collections.OrderedDict((
1572             ('name', self.name),
1573             ('description', self.description),
1574             ('implementation', self.implementation),
1575             ('dependencies', self.dependencies),
1576             ('inputs', formatting.as_raw_dict(self.inputs))))
1577
1578
1579 class ArtifactBase(InstanceModelMixin):
1580     """
1581     Typed file, either provided in a CSAR or downloaded from a repository.
1582
1583     Usually an instance of :class:`ArtifactTemplate`.
1584     """
1585
1586     __tablename__ = 'artifact'
1587
1588     __private_fields__ = ('type_fk',
1589                           'node_fk',
1590                           'artifact_template_fk')
1591
1592     # region one_to_many relationships
1593
1594     @declared_attr
1595     def properties(cls):
1596         """
1597         Associated immutable parameters.
1598
1599         :type: {:obj:`basestring`: :class:`Property`}
1600         """
1601         return relationship.one_to_many(cls, 'property', dict_key='name')
1602
1603     # endregion
1604
1605     # region many_to_one relationships
1606
1607     @declared_attr
1608     def node(cls):
1609         """
1610         Containing node.
1611
1612         :type: :class:`Node`
1613         """
1614         return relationship.many_to_one(cls, 'node')
1615
1616     @declared_attr
1617     def artifact_template(cls):
1618         """
1619         Source artifact template (can be ``None``).
1620
1621         :type: :class:`ArtifactTemplate`
1622         """
1623         return relationship.many_to_one(cls, 'artifact_template')
1624
1625     @declared_attr
1626     def type(cls):
1627         """
1628         Artifact type.
1629
1630         :type: :class:`Type`
1631         """
1632         return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
1633
1634     # endregion
1635
1636     # region foreign_keys
1637
1638     @declared_attr
1639     def type_fk(cls):
1640         """For Artifact many-to-one to Type"""
1641         return relationship.foreign_key('type')
1642
1643     @declared_attr
1644     def node_fk(cls):
1645         """For Node one-to-many to Artifact"""
1646         return relationship.foreign_key('node')
1647
1648     @declared_attr
1649     def artifact_template_fk(cls):
1650         """For Artifact many-to-one to ArtifactTemplate"""
1651         return relationship.foreign_key('artifact_template', nullable=True)
1652
1653     # endregion
1654
1655     description = Column(Text, doc="""
1656     Human-readable description.
1657
1658     :type: :obj:`basestring`
1659     """)
1660
1661     source_path = Column(Text, doc="""
1662     Source path (in CSAR or repository).
1663
1664     :type: :obj:`basestring`
1665     """)
1666
1667     target_path = Column(Text, doc="""
1668     Path at which to install at destination.
1669
1670     :type: :obj:`basestring`
1671     """)
1672
1673     repository_url = Column(Text, doc="""
1674     Repository URL.
1675
1676     :type: :obj:`basestring`
1677     """)
1678
1679     repository_credential = Column(modeling_types.StrictDict(basestring, basestring), doc="""
1680     Credentials for accessing the repository.
1681
1682     :type: {:obj:`basestring`, :obj:`basestring`}
1683     """)
1684
1685     @property
1686     def as_raw(self):
1687         return collections.OrderedDict((
1688             ('name', self.name),
1689             ('description', self.description),
1690             ('type_name', self.type.name),
1691             ('source_path', self.source_path),
1692             ('target_path', self.target_path),
1693             ('repository_url', self.repository_url),
1694             ('repository_credential', formatting.as_agnostic(self.repository_credential)),
1695             ('properties', formatting.as_raw_dict(self.properties))))